All of lore.kernel.org
 help / color / mirror / Atom feed
* [Buildroot] [PATCH 00/30] package/python3: bump version to 3.12.0
@ 2023-10-26  9:26 Adam Duskett
  2023-10-26  9:26 ` [Buildroot] [PATCH 01/30] package/python3: use upstream build system to disable berkeleydb module Adam Duskett
                   ` (30 more replies)
  0 siblings, 31 replies; 54+ messages in thread
From: Adam Duskett @ 2023-10-26  9:26 UTC (permalink / raw)
  To: buildroot
  Cc: Adam Duskett, Andrey Smirnov, Julien Olivain, Asaf Kahlon,
	James Hilliard, Thomas Petazzoni, Mauro Condarelli

Major changes:

Drop the following packages:
  python-pygame
  python-pyxb
  python-crossbar

Remove distutils as an option

I tested every single python package test in buildroot currently
available and every single one passes on Debian 11 and FC38/39.

Adam Duskett (16):
  package/scons: bump version to 4.5.2
  package/python-wsaccel: bump version to 0.6.6
  package/python-systemd: bump version to 235
  package/python-iptables: bump version to 1.0.1
  package/libftdi: add patch to move from distutils to sysconfig
  package/python-constantly: update versioneer to 0.29
  package/python-magic-wormhole: update versioneer to 0.29
  package/python-magic-wormhole-mailbox-server: update versioneer to
    0.29
  package/python-magic-wormhole-transit-relay: update versioneer to 0.29
  package/python-spake2: update versioneer to 0.29
  package/python-iptables: use sysconfig.get_path instead of
    get_python_lib
  package/python-pathtools: add 0001-replace-imp.patch
  package/python-pyxb: Drop package
  package/python-pygame: drop package
  package/python-crossbar: drop package
  package/python3: bump version to 3.12.0

Bernd Kuhls (14):
  package/python3: use upstream build system to disable berkeleydb
    module
  package/python3: use upstream build system to disable uuid module
  package/python3: use upstream build system to disable bzip2/zlib/xz
    modules
  package/python3: use upstream build system to disable curses/readline
    modules
  package/python3: use upstream build system to disable ssl module
  package/python3: use upstream build system to disable ossaudiodev
    module
  package/python3: use upstream build system to disable unicodedata
    module
  package/python3: use upstream build system to disable nis module
  package/python3: use upstream build system to disable decimal module
  package/python3: use upstream build system to disable CJK codecs
  package/python3: use upstream build system to disable pyexpat module
  package/python3: use upstream build system to disable sqlite3 module
  package/python3: update patch and partly use upstream build system to
    disable tk module
  package/python3: Remove infrastructure to disable the build of certain
    extensions

 .checkpackageignore                           |   14 -
 Config.in.legacy                              |   30 +
 package/Config.in                             |    3 -
 ...003-move-from-distutils-to-sysconfig.patch |   32 +
 package/pkg-python.mk                         |   44 +-
 .../0001-Update-versioneer-to-0.29.patch      | 2619 +++++++++++++++++
 .../0001-Avoid-intentional-syntax-error.patch |   29 -
 ...s-min.txt-drop-indirect-dependencies.patch |   74 -
 ...ice-wap-use-markupsafe-instead-of-we.patch |   53 -
 package/python-crossbar/Config.in             |   71 -
 package/python-crossbar/python-crossbar.hash  |    5 -
 package/python-crossbar/python-crossbar.mk    |   14 -
 ...g-get_path-instead-of-get_python_lib.patch |   40 +
 package/python-iptables/python-iptables.hash  |    4 +-
 package/python-iptables/python-iptables.mk    |    4 +-
 .../0002-Update-versioneer-to-0.29.patch      | 2194 ++++++++++++++
 .../0001-Update-versioneer-to-0.29.patch      | 2194 ++++++++++++++
 .../0001-Update-versioneer-to-0.29.patch      | 2185 ++++++++++++++
 .../python-pathtools/0001-replace-imp.patch   |   55 +
 package/python-pygame/Config.in               |   50 -
 package/python-pygame/python-pygame.hash      |    3 -
 package/python-pygame/python-pygame.mk        |  111 -
 package/python-pyxb/Config.in                 |    8 -
 package/python-pyxb/python-pyxb.hash          |    4 -
 package/python-pyxb/python-pyxb.mk            |   14 -
 .../0001-Update-versioneer-to-0.29.patch      | 2194 ++++++++++++++
 package/python-systemd/python-systemd.hash    |    4 +-
 package/python-systemd/python-systemd.mk      |    6 +-
 package/python-wsaccel/python-wsaccel.hash    |    4 +-
 package/python-wsaccel/python-wsaccel.mk      |    4 +-
 ...e-the-build-of-pyc-files-conditional.patch |   24 +-
 ...taddrinfo-configure-test-when-cross-.patch |    2 +-
 ...re-to-disable-the-build-of-certain-e.patch |  108 -
 ...-header-paths-for-cross-compilation.patch} |   31 +-
 ...tch => 0004-Serial-ioctl-workaround.patch} |    0
 ...ook-in-usr-lib-termcap-for-libraries.patch |   31 -
 ...g.sh.in-ensure-sed-invocations-only.patch} |    0
 ...0006-Add-an-option-to-disable-pydoc.patch} |   50 +-
 .../0006-Don-t-add-multiarch-paths.patch      |   37 -
 .../0007-Abort-on-failed-module-build.patch   |   30 -
 ...07-Add-an-option-to-disable-lib2to3.patch} |   74 +-
 ... 0008-Add-an-option-to-disable-IDLE.patch} |   43 +-
 ...e-shebang-of-Python-scripts-for-cros.patch |   35 -
 ...hon-config.sh-don-t-reassign-prefix.patch} |    0
 ...d-an-option-to-disable-the-tk-module.patch |   79 +
 ...fix-building-on-older-distributions.patch} |   12 +-
 ...p-CC-print-multiarch-output-for-mus.patch} |    2 +-
 ...option-to-disable-the-sqlite3-module.patch |   62 -
 ...ng-doesn-t-set-errno-when-encryptio.patch} |    0
 ...d-an-option-to-disable-the-tk-module.patch |   77 -
 ...-option-to-disable-the-curses-module.patch |   61 -
 .../0016-Add-an-option-to-disable-expat.patch |   82 -
 ...-Add-an-option-to-disable-CJK-codecs.patch |   30 -
 .../0018-Add-an-option-to-disable-NIS.patch   |   33 -
 ...Add-an-option-to-disable-unicodedata.patch |   30 -
 ...021-Add-an-option-to-disable-decimal.patch |   54 -
 ...on-to-disable-the-ossaudiodev-module.patch |   30 -
 ...an-option-to-disable-openssl-support.patch |   30 -
 ...ption-to-disable-the-readline-module.patch |   30 -
 ...to-disable-zlib-bzip2-and-xz-modules.patch |   42 -
 ...Add-an-option-to-disable-uuid-module.patch |   33 -
 ...ion-to-disable-the-berkeleydb-module.patch |   30 -
 package/python3/python3.hash                  |    6 +-
 package/python3/python3.mk                    |   81 +-
 package/scons/scons.hash                      |    4 +-
 package/scons/scons.mk                        |    7 +-
 .../tests/package/sample_python_crossbar.py   |    4 -
 .../tests/package/test_python_crossbar.py     |   23 -
 68 files changed, 11778 insertions(+), 1595 deletions(-)
 create mode 100644 package/libftdi/0003-move-from-distutils-to-sysconfig.patch
 create mode 100644 package/python-constantly/0001-Update-versioneer-to-0.29.patch
 delete mode 100644 package/python-crossbar/0001-Avoid-intentional-syntax-error.patch
 delete mode 100644 package/python-crossbar/0002-requirements-min.txt-drop-indirect-dependencies.patch
 delete mode 100644 package/python-crossbar/0003-crossbar-webservice-wap-use-markupsafe-instead-of-we.patch
 delete mode 100644 package/python-crossbar/Config.in
 delete mode 100644 package/python-crossbar/python-crossbar.hash
 delete mode 100644 package/python-crossbar/python-crossbar.mk
 create mode 100644 package/python-iptables/0001-use-sysconfig-get_path-instead-of-get_python_lib.patch
 create mode 100644 package/python-magic-wormhole-mailbox-server/0002-Update-versioneer-to-0.29.patch
 create mode 100644 package/python-magic-wormhole-transit-relay/0001-Update-versioneer-to-0.29.patch
 create mode 100644 package/python-magic-wormhole/0001-Update-versioneer-to-0.29.patch
 create mode 100644 package/python-pathtools/0001-replace-imp.patch
 delete mode 100644 package/python-pygame/Config.in
 delete mode 100644 package/python-pygame/python-pygame.hash
 delete mode 100644 package/python-pygame/python-pygame.mk
 delete mode 100644 package/python-pyxb/Config.in
 delete mode 100644 package/python-pyxb/python-pyxb.hash
 delete mode 100644 package/python-pyxb/python-pyxb.mk
 create mode 100644 package/python-spake2/0001-Update-versioneer-to-0.29.patch
 delete mode 100644 package/python3/0003-Add-infrastructure-to-disable-the-build-of-certain-e.patch
 rename package/python3/{0004-Adjust-library-header-paths-for-cross-compilation.patch => 0003-Adjust-library-header-paths-for-cross-compilation.patch} (64%)
 rename package/python3/{0008-Serial-ioctl-workaround.patch => 0004-Serial-ioctl-workaround.patch} (100%)
 delete mode 100644 package/python3/0005-Don-t-look-in-usr-lib-termcap-for-libraries.patch
 rename package/python3/{0010-Misc-python-config.sh.in-ensure-sed-invocations-only.patch => 0005-Misc-python-config.sh.in-ensure-sed-invocations-only.patch} (100%)
 rename package/python3/{0011-Add-an-option-to-disable-pydoc.patch => 0006-Add-an-option-to-disable-pydoc.patch} (54%)
 delete mode 100644 package/python3/0006-Don-t-add-multiarch-paths.patch
 delete mode 100644 package/python3/0007-Abort-on-failed-module-build.patch
 rename package/python3/{0012-Add-an-option-to-disable-lib2to3.patch => 0007-Add-an-option-to-disable-lib2to3.patch} (59%)
 rename package/python3/{0020-Add-an-option-to-disable-IDLE.patch => 0008-Add-an-option-to-disable-IDLE.patch} (57%)
 delete mode 100644 package/python3/0009-Do-not-adjust-the-shebang-of-Python-scripts-for-cros.patch
 rename package/python3/{0026-python-config.sh-don-t-reassign-prefix.patch => 0009-python-config.sh-don-t-reassign-prefix.patch} (100%)
 create mode 100644 package/python3/0010-Add-an-option-to-disable-the-tk-module.patch
 rename package/python3/{0028-fix-building-on-older-distributions.patch => 0011-fix-building-on-older-distributions.patch} (82%)
 rename package/python3/{0029-configure.ac-fixup-CC-print-multiarch-output-for-mus.patch => 0012-configure.ac-fixup-CC-print-multiarch-output-for-mus.patch} (97%)
 delete mode 100644 package/python3/0013-Add-option-to-disable-the-sqlite3-module.patch
 rename package/python3/{0031-lib-crypt-uClibc-ng-doesn-t-set-errno-when-encryptio.patch => 0013-lib-crypt-uClibc-ng-doesn-t-set-errno-when-encryptio.patch} (100%)
 delete mode 100644 package/python3/0014-Add-an-option-to-disable-the-tk-module.patch
 delete mode 100644 package/python3/0015-Add-an-option-to-disable-the-curses-module.patch
 delete mode 100644 package/python3/0016-Add-an-option-to-disable-expat.patch
 delete mode 100644 package/python3/0017-Add-an-option-to-disable-CJK-codecs.patch
 delete mode 100644 package/python3/0018-Add-an-option-to-disable-NIS.patch
 delete mode 100644 package/python3/0019-Add-an-option-to-disable-unicodedata.patch
 delete mode 100644 package/python3/0021-Add-an-option-to-disable-decimal.patch
 delete mode 100644 package/python3/0022-Add-an-option-to-disable-the-ossaudiodev-module.patch
 delete mode 100644 package/python3/0023-Add-an-option-to-disable-openssl-support.patch
 delete mode 100644 package/python3/0024-Add-an-option-to-disable-the-readline-module.patch
 delete mode 100644 package/python3/0025-Add-options-to-disable-zlib-bzip2-and-xz-modules.patch
 delete mode 100644 package/python3/0027-Add-an-option-to-disable-uuid-module.patch
 delete mode 100644 package/python3/0030-Add-an-option-to-disable-the-berkeleydb-module.patch
 delete mode 100644 support/testing/tests/package/sample_python_crossbar.py
 delete mode 100644 support/testing/tests/package/test_python_crossbar.py

-- 
2.41.0

_______________________________________________
buildroot mailing list
buildroot@buildroot.org
https://lists.buildroot.org/mailman/listinfo/buildroot

^ permalink raw reply	[flat|nested] 54+ messages in thread

* [Buildroot] [PATCH 01/30] package/python3: use upstream build system to disable berkeleydb module
  2023-10-26  9:26 [Buildroot] [PATCH 00/30] package/python3: bump version to 3.12.0 Adam Duskett
@ 2023-10-26  9:26 ` Adam Duskett
  2023-10-26  9:26 ` [Buildroot] [PATCH 02/30] package/python3: use upstream build system to disable uuid module Adam Duskett
                   ` (29 subsequent siblings)
  30 siblings, 0 replies; 54+ messages in thread
From: Adam Duskett @ 2023-10-26  9:26 UTC (permalink / raw)
  To: buildroot
  Cc: Adam Duskett, Andrey Smirnov, Bernd Kuhls, Julien Olivain,
	Asaf Kahlon, James Hilliard, Thomas Petazzoni, Mauro Condarelli,
	Adam Duskett

From: Bernd Kuhls <bernd@kuhls.net>

Backport patch 0018 from python 3.12 to enhance build system.

Signed-off-by: Bernd Kuhls <bernd@kuhls.net>
Reviewed-by: Adam Duskett <aduskett@gmail.com>
Tested-by: Adam Duskett <aduskett@gmail.com>
Signed-off-by: Adam Duskett <adam.duskett@amarulasolutions.com>
---
 .checkpackageignore                           |   1 -
 ...18-Port-_dbm-module-to-PY_STDLIB_MOD.patch | 293 ++++++++++++++++++
 ...ion-to-disable-the-berkeleydb-module.patch |  30 --
 package/python3/python3.mk                    |   2 +-
 4 files changed, 294 insertions(+), 32 deletions(-)
 create mode 100644 package/python3/0018-Port-_dbm-module-to-PY_STDLIB_MOD.patch
 delete mode 100644 package/python3/0030-Add-an-option-to-disable-the-berkeleydb-module.patch

diff --git a/.checkpackageignore b/.checkpackageignore
index b1d0b93b32..85273420b7 100644
--- a/.checkpackageignore
+++ b/.checkpackageignore
@@ -1180,7 +1180,6 @@ package/python3/0026-python-config.sh-don-t-reassign-prefix.patch Upstream
 package/python3/0027-Add-an-option-to-disable-uuid-module.patch Upstream
 package/python3/0028-fix-building-on-older-distributions.patch Upstream
 package/python3/0029-configure.ac-fixup-CC-print-multiarch-output-for-mus.patch Upstream
-package/python3/0030-Add-an-option-to-disable-the-berkeleydb-module.patch Upstream
 package/python3/0031-lib-crypt-uClibc-ng-doesn-t-set-errno-when-encryptio.patch Upstream
 package/qemu/0001-tests-fp-disable-fp-bench-build-by-default.patch Upstream
 package/qemu/0002-softmmu-qemu-seccomp.c-add-missing-header-for-CLONE_.patch Upstream
diff --git a/package/python3/0018-Port-_dbm-module-to-PY_STDLIB_MOD.patch b/package/python3/0018-Port-_dbm-module-to-PY_STDLIB_MOD.patch
new file mode 100644
index 0000000000..92d2594eef
--- /dev/null
+++ b/package/python3/0018-Port-_dbm-module-to-PY_STDLIB_MOD.patch
@@ -0,0 +1,293 @@
+From ec5e253556875640b1ac514e85c545346ac3f1e0 Mon Sep 17 00:00:00 2001
+From: Christian Heimes <christian@python.org>
+Date: Fri, 1 Jul 2022 21:48:38 +0200
+Subject: [PATCH] gh-90005: Port _dbm module to PY_STDLIB_MOD (GH-94433)
+
+Upstream: https://github.com/python/cpython/commit/ec5e253556875640b1ac514e85c545346ac3f1e0
+
+[Bernd: backported to 3.11.4]
+Signed-off-by: Bernd Kuhls <bernd@kuhls.net>
+---
+ ...2-06-30-09-57-39.gh-issue-90005.9-pQyR.rst |   1 +
+ Modules/Setup.stdlib.in                       |   2 +-
+ configure                                     | 295 ++++++++++++------
+ configure.ac                                  | 100 ++++--
+ pyconfig.h.in                                 |   6 -
+ setup.py                                      |  72 +----
+ 6 files changed, 278 insertions(+), 198 deletions(-)
+ create mode 100644 Misc/NEWS.d/next/Build/2022-06-30-09-57-39.gh-issue-90005.9-pQyR.rst
+
+diff --git a/Misc/NEWS.d/next/Build/2022-06-30-09-57-39.gh-issue-90005.9-pQyR.rst b/Misc/NEWS.d/next/Build/2022-06-30-09-57-39.gh-issue-90005.9-pQyR.rst
+new file mode 100644
+index 0000000000000..9b14f767847da
+--- /dev/null
++++ b/Misc/NEWS.d/next/Build/2022-06-30-09-57-39.gh-issue-90005.9-pQyR.rst
+@@ -0,0 +1 @@
++``_dbm`` module dependencies are now detected by configure.
+diff --git a/Modules/Setup.stdlib.in b/Modules/Setup.stdlib.in
+index a199aefc51011..ad34f85e25451 100644
+--- a/Modules/Setup.stdlib.in
++++ b/Modules/Setup.stdlib.in
+@@ -68,7 +68,7 @@
+ 
+ # dbm/gdbm
+ # dbm needs either libndbm, libgdbm_compat, or libdb 5.x
+-#@MODULE__DBM_TRUE@_dbm _dbmmodule.c
++@MODULE__DBM_TRUE@_dbm _dbmmodule.c
+ # gdbm module needs -lgdbm
+ @MODULE__GDBM_TRUE@_gdbm _gdbmmodule.c
+ 
+diff --git a/configure.ac b/configure.ac
+index 12ae2ae8d87eb..b03ead3bdefa0 100644
+--- a/configure.ac
++++ b/configure.ac
+@@ -3956,17 +3956,30 @@ WITH_SAVE_ENV([
+   ], [have_gdbm=no])
+ ])
+ 
+-# check for _dbmmodule.c dependencies
++dnl check for _dbmmodule.c dependencies
++dnl ndbm, gdbm_compat, libdb
+ AC_CHECK_HEADERS([ndbm.h], [
+-  LIBS_SAVE="$LIBS"
+-  AC_CHECK_LIB([ndbm], [dbm_open])
+-  LIBS="$LIBS_SAVE"
+-  AC_CHECK_LIB([gdbm_compat], [dbm_open])
+-  LIBS="$LIBS_SAVE"
++  WITH_SAVE_ENV([
++    AC_SEARCH_LIBS([dbm_open], [ndbm gdbm_compat])
++  ])
+ ])
+ 
+-# "gdbm-ndbm.h" and "gdbm/ndbm.h" are both normalized to "gdbm_ndbm_h"
+-# unset ac_cv_header_gdbm_ndbm_h to prevent false positive cache hits.
++AC_MSG_CHECKING([for ndbm presence and linker args])
++AS_CASE([$ac_cv_search_dbm_open],
++  [*ndbm*|*gdbm_compat*], [
++    dbm_ndbm="$ac_cv_search_dbm_open"
++    have_ndbm=yes
++  ],
++  [none*], [
++    dbm_ndbm=""
++    have_ndbm=yes
++  ],
++  [no], [have_ndbm=no]
++)
++AC_MSG_RESULT([$have_ndbm ($dbm_ndbm)])
++
++dnl "gdbm-ndbm.h" and "gdbm/ndbm.h" are both normalized to "gdbm_ndbm_h"
++dnl unset ac_cv_header_gdbm_ndbm_h to prevent false positive cache hits.
+ AS_UNSET([ac_cv_header_gdbm_ndbm_h])
+ AC_CACHE_VAL([ac_cv_header_gdbm_slash_ndbm_h], [
+   AC_CHECK_HEADER(
+@@ -3991,26 +4004,26 @@ AS_VAR_IF([ac_cv_header_gdbm_dash_ndbm_h], [yes], [
+ AS_UNSET([ac_cv_header_gdbm_ndbm_h])
+ 
+ if test "$ac_cv_header_gdbm_slash_ndbm_h" = yes -o "$ac_cv_header_gdbm_dash_ndbm_h" = yes; then
+-  LIBS_SAVE="$LIBS"
+-  AC_CHECK_LIB([gdbm_compat], [dbm_open])
+-  LIBS="$LIBS_SAVE"
++  WITH_SAVE_ENV([
++    AC_SEARCH_LIBS([dbm_open], [gdbm_compat])
++  ])
+ fi
+ 
+ # Check for libdb >= 5 with dbm_open()
+ # db.h re-defines the name of the function
+ AC_CHECK_HEADERS([db.h], [
+   AC_CACHE_CHECK([for libdb], [ac_cv_have_libdb], [
+-    LIBS_SAVE="$LIBS"
+-    LIBS="$LIBS -ldb"
+-    AC_LINK_IFELSE([AC_LANG_PROGRAM([
+-      #define DB_DBM_HSEARCH 1
+-      #include <db.h>
+-      #if DB_VERSION_MAJOR < 5
+-        #error "dh.h: DB_VERSION_MAJOR < 5 is not supported."
+-      #endif
+-      ], [DBM *dbm = dbm_open(NULL, 0, 0)])
+-    ], [ac_cv_have_libdb=yes], [ac_cv_have_libdb=no])
+-    LIBS="$LIBS_SAVE"
++    WITH_SAVE_ENV([
++      LIBS="$LIBS -ldb"
++      AC_LINK_IFELSE([AC_LANG_PROGRAM([
++        #define DB_DBM_HSEARCH 1
++        #include <db.h>
++        #if DB_VERSION_MAJOR < 5
++          #error "dh.h: DB_VERSION_MAJOR < 5 is not supported."
++        #endif
++        ], [DBM *dbm = dbm_open(NULL, 0, 0)])
++      ], [ac_cv_have_libdb=yes], [ac_cv_have_libdb=no])
++    ])
+   ])
+   AS_VAR_IF([ac_cv_have_libdb], [yes], [
+     AC_DEFINE([HAVE_LIBDB], [1], [Define to 1 if you have the `db' library (-ldb).])
+@@ -4018,7 +4031,7 @@ AC_CHECK_HEADERS([db.h], [
+ ])
+ 
+ # Check for --with-dbmliborder
+-AC_MSG_CHECKING(for --with-dbmliborder)
++AC_MSG_CHECKING([for --with-dbmliborder])
+ AC_ARG_WITH(dbmliborder,
+             AS_HELP_STRING([--with-dbmliborder=db1:db2:...], [override order to check db backends for dbm; a valid value is a colon separated string with the backend names `ndbm', `gdbm' and `bdb'.]),
+ [], [with_dbmliborder=gdbm:ndbm:bdb])
+@@ -4038,7 +4051,42 @@ IFS=$as_save_IFS
+ AS_VAR_IF([with_dbmliborder], [error], [
+   AC_MSG_ERROR([proper usage is --with-dbmliborder=db1:db2:... (gdbm:ndbm:bdb)])
+ ])
+-AC_MSG_RESULT($with_dbmliborder)
++AC_MSG_RESULT([$with_dbmliborder])
++
++AC_MSG_CHECKING([for _dbm module CFLAGS and LIBS])
++have_dbm=no
++as_save_IFS=$IFS
++IFS=:
++for db in $with_dbmliborder; do
++  case "$db" in
++    ndbm)
++      if test "$have_ndbm" = yes; then
++        DBM_CFLAGS="-DUSE_NDBM"
++        DBM_LIBS="$dbm_ndbm"
++        have_dbm=yes
++        break
++      fi
++      ;;
++    gdbm)
++      if test "$have_gdbm_compat" = yes; then
++        DBM_CFLAGS="-DUSE_GDBM_COMPAT"
++        DBM_LIBS="-lgdbm_compat"
++        have_dbm=yes
++        break
++      fi
++      ;;
++    bdb)
++      if test "$ac_cv_have_libdb" = yes; then
++        DBM_CFLAGS="-DUSE_BERKDB"
++        DBM_LIBS="-ldb"
++        have_dbm=yes
++        break
++      fi
++     ;;
++  esac
++done
++IFS=$as_save_IFS
++AC_MSG_RESULT([$DBM_CFLAGS $DBM_LIBS])
+ 
+ # Templates for things AC_DEFINEd more than once.
+ # For a single AC_DEFINE, no template is needed.
+@@ -6940,7 +6988,9 @@ PY_STDLIB_MOD([_ctypes],
+ dnl PY_STDLIB_MOD([_curses], [], [], [], [])
+ dnl PY_STDLIB_MOD([_curses_panel], [], [], [], [])
+ PY_STDLIB_MOD([_decimal], [], [], [$LIBMPDEC_CFLAGS], [$LIBMPDEC_LDFLAGS])
+-dnl PY_STDLIB_MOD([_dbm], [], [], [], [])
++PY_STDLIB_MOD([_dbm],
++  [test -n "$with_dbmliborder"], [test "$have_dbm" != "no"],
++  [$DBM_CFLAGS], [$DBM_LIBS])
+ PY_STDLIB_MOD([_gdbm],
+   [test "$have_gdbm_dbmliborder" = yes], [test "$have_gdbm" = yes],
+   [$GDBM_CFLAGS], [$GDBM_LIBS])
+diff --git a/pyconfig.h.in b/pyconfig.h.in
+index 15933e75b1b07..b05ddd41c2bba 100644
+--- a/pyconfig.h.in
++++ b/pyconfig.h.in
+@@ -640,18 +640,12 @@
+ /* Define to 1 if you have the `dld' library (-ldld). */
+ #undef HAVE_LIBDLD
+ 
+-/* Define to 1 if you have the `gdbm_compat' library (-lgdbm_compat). */
+-#undef HAVE_LIBGDBM_COMPAT
+-
+ /* Define to 1 if you have the `ieee' library (-lieee). */
+ #undef HAVE_LIBIEEE
+ 
+ /* Define to 1 if you have the <libintl.h> header file. */
+ #undef HAVE_LIBINTL_H
+ 
+-/* Define to 1 if you have the `ndbm' library (-lndbm). */
+-#undef HAVE_LIBNDBM
+-
+ /* Define to build the readline module. */
+ #undef HAVE_LIBREADLINE
+ 
+diff --git a/setup.py b/setup.py
+index 843ec35effe10..cc11dedee1b2e 100644
+--- a/setup.py
++++ b/setup.py
+@@ -1163,77 +1163,7 @@ def detect_crypt(self):
+         self.addext(Extension('_crypt', ['_cryptmodule.c']))
+ 
+     def detect_dbm_gdbm(self):
+-        # Modules that provide persistent dictionary-like semantics.  You will
+-        # probably want to arrange for at least one of them to be available on
+-        # your machine, though none are defined by default because of library
+-        # dependencies.  The Python module dbm/__init__.py provides an
+-        # implementation independent wrapper for these; dbm/dumb.py provides
+-        # similar functionality (but slower of course) implemented in Python.
+-
+-        dbm_setup_debug = False   # verbose debug prints from this script?
+-        dbm_order = ['gdbm']
+-
+-        # libdb, gdbm and ndbm headers and libraries
+-        have_ndbm_h = sysconfig.get_config_var("HAVE_NDBM_H")
+-        have_gdbm_ndbm_h = sysconfig.get_config_var("HAVE_GDBM_NDBM_H")
+-        have_gdbm_dash_ndbm_h = sysconfig.get_config_var("HAVE_GDBM_DASH_NDBM_H")
+-        have_libndbm = sysconfig.get_config_var("HAVE_LIBNDBM")
+-        have_libgdbm_compat = sysconfig.get_config_var("HAVE_LIBGDBM_COMPAT")
+-        have_libdb = sysconfig.get_config_var("HAVE_LIBDB")
+-
+-        # The standard Unix dbm module:
+-        if not CYGWIN:
+-            config_args = [arg.strip("'")
+-                           for arg in sysconfig.get_config_var("CONFIG_ARGS").split()]
+-            dbm_args = [arg for arg in config_args
+-                        if arg.startswith('--with-dbmliborder=')]
+-            if dbm_args:
+-                dbm_order = [arg.split('=')[-1] for arg in dbm_args][-1].split(":")
+-            else:
+-                dbm_order = "gdbm:ndbm:bdb".split(":")
+-            dbmext = None
+-            for cand in dbm_order:
+-                if cand == "ndbm":
+-                    if have_ndbm_h:
+-                        # Some systems have -lndbm, others have -lgdbm_compat,
+-                        # others don't have either
+-                        if have_libndbm:
+-                            ndbm_libs = ['ndbm']
+-                        elif have_libgdbm_compat:
+-                            ndbm_libs = ['gdbm_compat']
+-                        else:
+-                            ndbm_libs = []
+-                        if dbm_setup_debug: print("building dbm using ndbm")
+-                        dbmext = Extension(
+-                            '_dbm', ['_dbmmodule.c'],
+-                            define_macros=[('USE_NDBM', None)],
+-                            libraries=ndbm_libs
+-                        )
+-                        break
+-                elif cand == "gdbm":
+-                    # dbm_open() is provided by libgdbm_compat, which wraps libgdbm
+-                    if have_libgdbm_compat and (have_gdbm_ndbm_h or have_gdbm_dash_ndbm_h):
+-                        if dbm_setup_debug: print("building dbm using gdbm")
+-                        dbmext = Extension(
+-                            '_dbm', ['_dbmmodule.c'],
+-                            define_macros=[('USE_GDBM_COMPAT', None)],
+-                            libraries=['gdbm_compat']
+-                        )
+-                        break
+-                elif cand == "bdb":
+-                    if have_libdb:
+-                        if dbm_setup_debug: print("building dbm using bdb")
+-                        dbmext = Extension(
+-                            '_dbm', ['_dbmmodule.c'],
+-                            define_macros=[('USE_BERKDB', None)],
+-                            libraries=['db']
+-                        )
+-                        break
+-            if dbmext is not None:
+-                self.add(dbmext)
+-            else:
+-                self.missing.append('_dbm')
+-
++        self.addext(Extension('_dbm', ['_dbmmodule.c']))
+         # Anthony Baxter's gdbm module.  GNU dbm(3) will require -lgdbm:
+         self.addext(Extension('_gdbm', ['_gdbmmodule.c']))
+ 
diff --git a/package/python3/0030-Add-an-option-to-disable-the-berkeleydb-module.patch b/package/python3/0030-Add-an-option-to-disable-the-berkeleydb-module.patch
deleted file mode 100644
index 5fb436db59..0000000000
--- a/package/python3/0030-Add-an-option-to-disable-the-berkeleydb-module.patch
+++ /dev/null
@@ -1,30 +0,0 @@
-From 67e9793d070ac5c8e83abbe95b9208533ffeadd0 Mon Sep 17 00:00:00 2001
-From: Bernd Kuhls <bernd.kuhls@t-online.de>
-Date: Sat, 11 Apr 2020 22:01:40 +0200
-Subject: [PATCH] Add an option to disable the berkeleydb module
-
-Signed-off-by: Bernd Kuhls <bernd.kuhls@t-online.de>
----
- configure.ac | 6 ++++++
- 1 file changed, 6 insertions(+)
-
-diff --git a/configure.ac b/configure.ac
-index 841fd6732c..06c9a81f95 100644
---- a/configure.ac
-+++ b/configure.ac
-@@ -4280,6 +4280,12 @@ if test "$UUID" = "no"; then
-    DISABLED_EXTENSIONS="${DISABLED_EXTENSIONS} _uuid"
- fi
- 
-+AC_ARG_ENABLE(berkeleydb,
-+	AS_HELP_STRING([--disable-berkeleydb], [disable berkeleydb]),
-+	[ if test "$enableval" = "no"; then
-+	     DISABLED_EXTENSIONS="${DISABLED_EXTENSIONS} _dbm"
-+	  fi])
-+
- AC_SUBST(PYDOC)
- 
- AC_ARG_ENABLE(pydoc,
--- 
-2.34.1
-
diff --git a/package/python3/python3.mk b/package/python3/python3.mk
index b9c5054a21..8166d306c5 100644
--- a/package/python3/python3.mk
+++ b/package/python3/python3.mk
@@ -72,7 +72,7 @@ endif
 ifeq ($(BR2_PACKAGE_PYTHON3_BERKELEYDB),y)
 PYTHON3_DEPENDENCIES += berkeleydb
 else
-PYTHON3_CONF_OPTS += --disable-berkeleydb
+PYTHON3_CONF_ENV += py_cv_module__dbm=n/a
 endif
 
 ifeq ($(BR2_PACKAGE_PYTHON3_READLINE),y)
-- 
2.41.0

_______________________________________________
buildroot mailing list
buildroot@buildroot.org
https://lists.buildroot.org/mailman/listinfo/buildroot

^ permalink raw reply related	[flat|nested] 54+ messages in thread

* [Buildroot] [PATCH 02/30] package/python3: use upstream build system to disable uuid module
  2023-10-26  9:26 [Buildroot] [PATCH 00/30] package/python3: bump version to 3.12.0 Adam Duskett
  2023-10-26  9:26 ` [Buildroot] [PATCH 01/30] package/python3: use upstream build system to disable berkeleydb module Adam Duskett
@ 2023-10-26  9:26 ` Adam Duskett
  2023-10-26  9:26 ` [Buildroot] [PATCH 03/30] package/python3: use upstream build system to disable bzip2/zlib/xz modules Adam Duskett
                   ` (28 subsequent siblings)
  30 siblings, 0 replies; 54+ messages in thread
From: Adam Duskett @ 2023-10-26  9:26 UTC (permalink / raw)
  To: buildroot
  Cc: Adam Duskett, Andrey Smirnov, Bernd Kuhls, Julien Olivain,
	Asaf Kahlon, James Hilliard, Thomas Petazzoni, Mauro Condarelli

From: Bernd Kuhls <bernd@kuhls.net>

Signed-off-by: Bernd Kuhls <bernd@kuhls.net>
Signed-off-by: Adam Duskett <adam.duskett@amarulasolutions.com>
---
 .checkpackageignore                           |  1 -
 ...Add-an-option-to-disable-uuid-module.patch | 33 -------------------
 package/python3/python3.mk                    |  4 +--
 3 files changed, 2 insertions(+), 36 deletions(-)
 delete mode 100644 package/python3/0027-Add-an-option-to-disable-uuid-module.patch

diff --git a/.checkpackageignore b/.checkpackageignore
index 85273420b7..4425eb6859 100644
--- a/.checkpackageignore
+++ b/.checkpackageignore
@@ -1177,7 +1177,6 @@ package/python3/0023-Add-an-option-to-disable-openssl-support.patch Upstream
 package/python3/0024-Add-an-option-to-disable-the-readline-module.patch Upstream
 package/python3/0025-Add-options-to-disable-zlib-bzip2-and-xz-modules.patch Upstream
 package/python3/0026-python-config.sh-don-t-reassign-prefix.patch Upstream
-package/python3/0027-Add-an-option-to-disable-uuid-module.patch Upstream
 package/python3/0028-fix-building-on-older-distributions.patch Upstream
 package/python3/0029-configure.ac-fixup-CC-print-multiarch-output-for-mus.patch Upstream
 package/python3/0031-lib-crypt-uClibc-ng-doesn-t-set-errno-when-encryptio.patch Upstream
diff --git a/package/python3/0027-Add-an-option-to-disable-uuid-module.patch b/package/python3/0027-Add-an-option-to-disable-uuid-module.patch
deleted file mode 100644
index a9501ec189..0000000000
--- a/package/python3/0027-Add-an-option-to-disable-uuid-module.patch
+++ /dev/null
@@ -1,33 +0,0 @@
-From 58027d25c3cabcf654cb0b31a61d7cbd53dc68c0 Mon Sep 17 00:00:00 2001
-From: Thomas Petazzoni <thomas.petazzoni@bootlin.com>
-Date: Sat, 18 Aug 2018 10:54:56 +0200
-Subject: [PATCH] Add an option to disable uuid module
-
-Signed-off-by: Thomas Petazzoni <thomas.petazzoni@bootlin.com>
----
- configure.ac | 9 +++++++++
- 1 file changed, 9 insertions(+)
-
-diff --git a/configure.ac b/configure.ac
-index ca6c16491a..ed03b27fb1 100644
---- a/configure.ac
-+++ b/configure.ac
-@@ -4267,6 +4267,15 @@ if test "$CURSES" = "no"; then
-    DISABLED_EXTENSIONS="${DISABLED_EXTENSIONS} _curses _curses_panel"
- fi
- 
-+AC_SUBST(UUID)
-+AC_ARG_ENABLE(uuid,
-+	AS_HELP_STRING([--disable-uuid], [disable uuid]),
-+	[ UUID="${enableval}" ], [ UUID=yes ])
-+
-+if test "$UUID" = "no"; then
-+   DISABLED_EXTENSIONS="${DISABLED_EXTENSIONS} _uuid"
-+fi
-+
- AC_SUBST(PYDOC)
- 
- AC_ARG_ENABLE(pydoc,
--- 
-2.34.1
-
diff --git a/package/python3/python3.mk b/package/python3/python3.mk
index 8166d306c5..7f31f3308c 100644
--- a/package/python3/python3.mk
+++ b/package/python3/python3.mk
@@ -28,7 +28,6 @@ HOST_PYTHON3_CONF_OPTS += \
 	--enable-unicodedata \
 	--disable-test-modules \
 	--disable-idle3 \
-	--disable-uuid \
 	--disable-ossaudiodev
 
 # Make sure that LD_LIBRARY_PATH overrides -rpath.
@@ -38,6 +37,7 @@ HOST_PYTHON3_CONF_OPTS += \
 # communicate over the network during the build.
 HOST_PYTHON3_CONF_ENV += \
 	LDFLAGS="$(HOST_LDFLAGS) -Wl,--enable-new-dtags" \
+	py_cv_module__uuid=n/a \
 	ac_cv_prog_HAS_HG=/bin/false
 
 PYTHON3_DEPENDENCIES = host-python3 libffi
@@ -125,7 +125,7 @@ endif
 # Disable auto-detection of uuid.h (util-linux)
 # which would add _uuid module support, instead
 # default to the pure python implementation
-PYTHON3_CONF_OPTS += --disable-uuid
+PYTHON3_CONF_ENV += py_cv_module__uuid=n/a
 
 ifeq ($(BR2_PACKAGE_PYTHON3_BZIP2),y)
 PYTHON3_DEPENDENCIES += bzip2
-- 
2.41.0

_______________________________________________
buildroot mailing list
buildroot@buildroot.org
https://lists.buildroot.org/mailman/listinfo/buildroot

^ permalink raw reply related	[flat|nested] 54+ messages in thread

* [Buildroot] [PATCH 03/30] package/python3: use upstream build system to disable bzip2/zlib/xz modules
  2023-10-26  9:26 [Buildroot] [PATCH 00/30] package/python3: bump version to 3.12.0 Adam Duskett
  2023-10-26  9:26 ` [Buildroot] [PATCH 01/30] package/python3: use upstream build system to disable berkeleydb module Adam Duskett
  2023-10-26  9:26 ` [Buildroot] [PATCH 02/30] package/python3: use upstream build system to disable uuid module Adam Duskett
@ 2023-10-26  9:26 ` Adam Duskett
  2023-10-26  9:26 ` [Buildroot] [PATCH 04/30] package/python3: use upstream build system to disable curses/readline modules Adam Duskett
                   ` (27 subsequent siblings)
  30 siblings, 0 replies; 54+ messages in thread
From: Adam Duskett @ 2023-10-26  9:26 UTC (permalink / raw)
  To: buildroot
  Cc: Adam Duskett, Andrey Smirnov, Bernd Kuhls, Julien Olivain,
	Asaf Kahlon, James Hilliard, Thomas Petazzoni, Mauro Condarelli

From: Bernd Kuhls <bernd@kuhls.net>

Signed-off-by: Bernd Kuhls <bernd@kuhls.net>
Signed-off-by: Adam Duskett <adam.duskett@amarulasolutions.com>
---
 .checkpackageignore                           |  1 -
 ...to-disable-zlib-bzip2-and-xz-modules.patch | 42 -------------------
 package/python3/python3.mk                    |  8 ++--
 3 files changed, 4 insertions(+), 47 deletions(-)
 delete mode 100644 package/python3/0025-Add-options-to-disable-zlib-bzip2-and-xz-modules.patch

diff --git a/.checkpackageignore b/.checkpackageignore
index 4425eb6859..f8216dd255 100644
--- a/.checkpackageignore
+++ b/.checkpackageignore
@@ -1175,7 +1175,6 @@ package/python3/0021-Add-an-option-to-disable-decimal.patch Upstream
 package/python3/0022-Add-an-option-to-disable-the-ossaudiodev-module.patch Upstream
 package/python3/0023-Add-an-option-to-disable-openssl-support.patch Upstream
 package/python3/0024-Add-an-option-to-disable-the-readline-module.patch Upstream
-package/python3/0025-Add-options-to-disable-zlib-bzip2-and-xz-modules.patch Upstream
 package/python3/0026-python-config.sh-don-t-reassign-prefix.patch Upstream
 package/python3/0028-fix-building-on-older-distributions.patch Upstream
 package/python3/0029-configure.ac-fixup-CC-print-multiarch-output-for-mus.patch Upstream
diff --git a/package/python3/0025-Add-options-to-disable-zlib-bzip2-and-xz-modules.patch b/package/python3/0025-Add-options-to-disable-zlib-bzip2-and-xz-modules.patch
deleted file mode 100644
index 70a0d6cd15..0000000000
--- a/package/python3/0025-Add-options-to-disable-zlib-bzip2-and-xz-modules.patch
+++ /dev/null
@@ -1,42 +0,0 @@
-From 988a335cb34b5fc25ea345ba04ff5ddffe2e946c Mon Sep 17 00:00:00 2001
-From: Thomas Petazzoni <thomas.petazzoni@free-electrons.com>
-Date: Tue, 7 Mar 2017 23:31:11 +0100
-Subject: [PATCH] Add options to disable zlib, bzip2 and xz modules
-
-Signed-off-by: Thomas Petazzoni <thomas.petazzoni@free-electrons.com>
----
- configure.ac | 18 ++++++++++++++++++
- 1 file changed, 18 insertions(+)
-
-diff --git a/configure.ac b/configure.ac
-index 19875d7d30..ca6c16491a 100644
---- a/configure.ac
-+++ b/configure.ac
-@@ -4231,6 +4231,24 @@ AC_ARG_ENABLE(readline,
- 	     DISABLED_EXTENSIONS="${DISABLED_EXTENSIONS} readline"
- 	  fi])
- 
-+AC_ARG_ENABLE(bzip2,
-+	AS_HELP_STRING([--disable-bzip2], [disable bzip2]),
-+	[ if test "$enableval" = "no"; then
-+	     DISABLED_EXTENSIONS="${DISABLED_EXTENSIONS} _bz2"
-+	  fi])
-+
-+AC_ARG_ENABLE(zlib,
-+	AS_HELP_STRING([--disable-zlib], [disable zlib]),
-+	[ if test "$enableval" = "no"; then
-+	     DISABLED_EXTENSIONS="${DISABLED_EXTENSIONS} zlib"
-+	  fi])
-+
-+AC_ARG_ENABLE(xz,
-+	AS_HELP_STRING([--disable-xz], [disable xz]),
-+	[ if test "$enableval" = "no"; then
-+	     DISABLED_EXTENSIONS="${DISABLED_EXTENSIONS} _lzma"
-+	  fi])
-+
- AC_SUBST(TK)
- AC_ARG_ENABLE(tk,
- 	AS_HELP_STRING([--disable-tk], [disable tk]),
--- 
-2.34.1
-
diff --git a/package/python3/python3.mk b/package/python3/python3.mk
index 7f31f3308c..555afdab4f 100644
--- a/package/python3/python3.mk
+++ b/package/python3/python3.mk
@@ -52,7 +52,7 @@ HOST_PYTHON3_DEPENDENCIES = \
 ifeq ($(BR2_PACKAGE_HOST_PYTHON3_BZIP2),y)
 HOST_PYTHON3_DEPENDENCIES += host-bzip2
 else
-HOST_PYTHON3_CONF_OPTS += --disable-bzip2
+HOST_PYTHON3_CONF_ENV += py_cv_module__bz2=n/a
 endif
 
 ifeq ($(BR2_PACKAGE_HOST_PYTHON3_SSL),y)
@@ -130,19 +130,19 @@ PYTHON3_CONF_ENV += py_cv_module__uuid=n/a
 ifeq ($(BR2_PACKAGE_PYTHON3_BZIP2),y)
 PYTHON3_DEPENDENCIES += bzip2
 else
-PYTHON3_CONF_OPTS += --disable-bzip2
+PYTHON3_CONF_ENV += py_cv_module__bz2=n/a
 endif
 
 ifeq ($(BR2_PACKAGE_PYTHON3_XZ),y)
 PYTHON3_DEPENDENCIES += xz
 else
-PYTHON3_CONF_OPTS += --disable-xz
+PYTHON3_CONF_ENV += py_cv_module__lzma=n/a
 endif
 
 ifeq ($(BR2_PACKAGE_PYTHON3_ZLIB),y)
 PYTHON3_DEPENDENCIES += zlib
 else
-PYTHON3_CONF_OPTS += --disable-zlib
+PYTHON3_CONF_ENV += py_cv_module_zlib=n/a
 endif
 
 ifeq ($(BR2_PACKAGE_PYTHON3_OSSAUDIODEV),y)
-- 
2.41.0

_______________________________________________
buildroot mailing list
buildroot@buildroot.org
https://lists.buildroot.org/mailman/listinfo/buildroot

^ permalink raw reply related	[flat|nested] 54+ messages in thread

* [Buildroot] [PATCH 04/30] package/python3: use upstream build system to disable curses/readline modules
  2023-10-26  9:26 [Buildroot] [PATCH 00/30] package/python3: bump version to 3.12.0 Adam Duskett
                   ` (2 preceding siblings ...)
  2023-10-26  9:26 ` [Buildroot] [PATCH 03/30] package/python3: use upstream build system to disable bzip2/zlib/xz modules Adam Duskett
@ 2023-10-26  9:26 ` Adam Duskett
  2023-10-26  9:26 ` [Buildroot] [PATCH 05/30] package/python3: use upstream build system to disable ssl module Adam Duskett
                   ` (26 subsequent siblings)
  30 siblings, 0 replies; 54+ messages in thread
From: Adam Duskett @ 2023-10-26  9:26 UTC (permalink / raw)
  To: buildroot
  Cc: Adam Duskett, Andrey Smirnov, Bernd Kuhls, Julien Olivain,
	Asaf Kahlon, James Hilliard, Thomas Petazzoni, Mauro Condarelli

From: Bernd Kuhls <bernd@kuhls.net>

Backported patch 0020 from python 3.12 to enhance build system, for
easier backporting backported patch 0019 as well which was applied
upstream before patch 0020.

Signed-off-by: Bernd Kuhls <bernd@kuhls.net>
Signed-off-by: Adam Duskett <adam.duskett@amarulasolutions.com>
---
 .checkpackageignore                           |   2 -
 ...-option-to-disable-the-curses-module.patch |  61 --
 .../0019-Port-_ctypes-to-PY_STDLIB_MOD.patch  | 441 +++++++++++
 ...readline-and-curses-to-PY_STDLIB_MOD.patch | 718 ++++++++++++++++++
 ...ption-to-disable-the-readline-module.patch |  30 -
 package/python3/python3.mk                    |  10 +-
 6 files changed, 1166 insertions(+), 96 deletions(-)
 delete mode 100644 package/python3/0015-Add-an-option-to-disable-the-curses-module.patch
 create mode 100644 package/python3/0019-Port-_ctypes-to-PY_STDLIB_MOD.patch
 create mode 100644 package/python3/0020-Port-readline-and-curses-to-PY_STDLIB_MOD.patch
 delete mode 100644 package/python3/0024-Add-an-option-to-disable-the-readline-module.patch

diff --git a/.checkpackageignore b/.checkpackageignore
index f8216dd255..ee67e9a3bf 100644
--- a/.checkpackageignore
+++ b/.checkpackageignore
@@ -1165,7 +1165,6 @@ package/python3/0011-Add-an-option-to-disable-pydoc.patch Upstream
 package/python3/0012-Add-an-option-to-disable-lib2to3.patch Upstream
 package/python3/0013-Add-option-to-disable-the-sqlite3-module.patch Upstream
 package/python3/0014-Add-an-option-to-disable-the-tk-module.patch Upstream
-package/python3/0015-Add-an-option-to-disable-the-curses-module.patch Upstream
 package/python3/0016-Add-an-option-to-disable-expat.patch Upstream
 package/python3/0017-Add-an-option-to-disable-CJK-codecs.patch Upstream
 package/python3/0018-Add-an-option-to-disable-NIS.patch Upstream
@@ -1174,7 +1173,6 @@ package/python3/0020-Add-an-option-to-disable-IDLE.patch Upstream
 package/python3/0021-Add-an-option-to-disable-decimal.patch Upstream
 package/python3/0022-Add-an-option-to-disable-the-ossaudiodev-module.patch Upstream
 package/python3/0023-Add-an-option-to-disable-openssl-support.patch Upstream
-package/python3/0024-Add-an-option-to-disable-the-readline-module.patch Upstream
 package/python3/0026-python-config.sh-don-t-reassign-prefix.patch Upstream
 package/python3/0028-fix-building-on-older-distributions.patch Upstream
 package/python3/0029-configure.ac-fixup-CC-print-multiarch-output-for-mus.patch Upstream
diff --git a/package/python3/0015-Add-an-option-to-disable-the-curses-module.patch b/package/python3/0015-Add-an-option-to-disable-the-curses-module.patch
deleted file mode 100644
index da6f891104..0000000000
--- a/package/python3/0015-Add-an-option-to-disable-the-curses-module.patch
+++ /dev/null
@@ -1,61 +0,0 @@
-From 03e28cdd46dac1b7e4e9c8bbd2ea44b09e514205 Mon Sep 17 00:00:00 2001
-From: Thomas Petazzoni <thomas.petazzoni@free-electrons.com>
-Date: Wed, 22 Feb 2017 17:31:51 -0800
-Subject: [PATCH] Add an option to disable the curses module
-
-Signed-off-by: Thomas Petazzoni <thomas.petazzoni@free-electrons.com>
-Signed-off-by: Samuel Martin <s.martin49@gmail.com>
-[ Andrey Smirnov: ported to Python 3.6 ]
-Signed-off-by: Andrey Smirnov <andrew.smirnov@gmail.com>
-[ Adam Duskett: ported to Python 3.10.0 ]
-Signed-off-by: Adam Duskett <aduskett@gmail.com>
----
- Makefile.pre.in | 4 +++-
- configure.ac    | 9 +++++++++
- 2 files changed, 12 insertions(+), 1 deletion(-)
-
-diff --git a/Makefile.pre.in b/Makefile.pre.in
-index 4f83911200..8e879b35c6 100644
---- a/Makefile.pre.in
-+++ b/Makefile.pre.in
-@@ -1905,7 +1905,6 @@ LIBSUBDIRS=	asyncio \
- 		concurrent concurrent/futures \
- 		csv \
- 		ctypes ctypes/macholib \
--		curses \
- 		dbm \
- 		distutils distutils/command \
- 		email email/mime \
-@@ -2024,6 +2023,9 @@ TESTSUBDIRS += tkinter/test tkinter/test/test_tkinter \
- 	tkinter/test/test_ttk
- endif
- 
-+ifeq (@CURSES@,yes)
-+LIBSUBDIRS += curses
-+endif
- 
- TEST_MODULES=@TEST_MODULES@
- libinstall:	all $(srcdir)/Modules/xxmodule.c
-diff --git a/configure.ac b/configure.ac
-index f4ce506801..0ae9863cd6 100644
---- a/configure.ac
-+++ b/configure.ac
-@@ -4189,6 +4189,15 @@ if test "$TK" = "no"; then
-    DISABLED_EXTENSIONS="${DISABLED_EXTENSIONS} _tkinter"
- fi
- 
-+AC_SUBST(CURSES)
-+AC_ARG_ENABLE(curses,
-+	AS_HELP_STRING([--disable-curses], [disable curses]),
-+	[ CURSES="${enableval}" ], [ CURSES=yes ])
-+
-+if test "$CURSES" = "no"; then
-+   DISABLED_EXTENSIONS="${DISABLED_EXTENSIONS} _curses _curses_panel"
-+fi
-+
- AC_SUBST(PYDOC)
- 
- AC_ARG_ENABLE(pydoc,
--- 
-2.34.1
-
diff --git a/package/python3/0019-Port-_ctypes-to-PY_STDLIB_MOD.patch b/package/python3/0019-Port-_ctypes-to-PY_STDLIB_MOD.patch
new file mode 100644
index 0000000000..f8e3e43927
--- /dev/null
+++ b/package/python3/0019-Port-_ctypes-to-PY_STDLIB_MOD.patch
@@ -0,0 +1,441 @@
+From bb8b931385ba9df4e01f7dd3ce4575d49f60efdf Mon Sep 17 00:00:00 2001
+From: Christian Heimes <christian@python.org>
+Date: Sun, 26 Jun 2022 13:04:43 +0200
+Subject: [PATCH] gh-90005: Port _ctypes to PY_STDLIB_MOD (GH-32229)
+
+Co-authored-by: Erlend Egeberg Aasland <erlend.aasland@innova.no>
+
+Automerge-Triggered-By: GH:tiran
+
+Upstream: https://github.com/python/cpython/commit/bb8b931385ba9df4e01f7dd3ce4575d49f60efdf
+
+[Bernd: backported to 3.11.4]
+Signed-off-by: Bernd Kuhls <bernd@kuhls.net>
+---
+ Makefile.pre.in                               |   9 +-
+ ...2-04-01-12-35-44.gh-issue-90005.pvaLHQ.rst |   1 +
+ Modules/Setup.stdlib.in                       |   2 +-
+ Modules/_ctypes/callproc.c                    |   3 +
+ Modules/_ctypes/malloc_closure.c              |   3 +
+ configure                                     | 540 +++++++++++++++++-
+ configure.ac                                  | 125 +++-
+ pyconfig.h.in                                 |   9 +
+ setup.py                                      | 115 +---
+ 9 files changed, 660 insertions(+), 147 deletions(-)
+ create mode 100644 Misc/NEWS.d/next/Library/2022-04-01-12-35-44.gh-issue-90005.pvaLHQ.rst
+
+diff --git a/Makefile.pre.in b/Makefile.pre.in
+index 102cd752c39cd..c0333cea48cd7 100644
+--- a/Makefile.pre.in
++++ b/Makefile.pre.in
+@@ -340,10 +340,6 @@ IO_OBJS=	\
+ 		Modules/_io/bytesio.o \
+ 		Modules/_io/stringio.o
+ 
+-##########################################################################
+-
+-LIBFFI_INCLUDEDIR=	@LIBFFI_INCLUDEDIR@
+-
+ ##########################################################################
+ # Parser
+ 
+@@ -2595,7 +2595,8 @@
+ MODULE_PYEXPAT_DEPS=@LIBEXPAT_INTERNAL@
+ MODULE_UNICODEDATA_DEPS=$(srcdir)/Modules/unicodedata_db.h $(srcdir)/Modules/unicodename_db.h
+ MODULE__BLAKE2_DEPS=$(srcdir)/Modules/_blake2/impl/blake2-config.h $(srcdir)/Modules/_blake2/impl/blake2-impl.h $(srcdir)/Modules/_blake2/impl/blake2.h $(srcdir)/Modules/_blake2/impl/blake2b-load-sse2.h $(srcdir)/Modules/_blake2/impl/blake2b-load-sse41.h $(srcdir)/Modules/_blake2/impl/blake2b-ref.c $(srcdir)/Modules/_blake2/impl/blake2b-round.h $(srcdir)/Modules/_blake2/impl/blake2b.c $(srcdir)/Modules/_blake2/impl/blake2s-load-sse2.h $(srcdir)/Modules/_blake2/impl/blake2s-load-sse41.h $(srcdir)/Modules/_blake2/impl/blake2s-load-xop.h $(srcdir)/Modules/_blake2/impl/blake2s-ref.c $(srcdir)/Modules/_blake2/impl/blake2s-round.h $(srcdir)/Modules/_blake2/impl/blake2s.c $(srcdir)/Modules/_blake2/blake2module.h $(srcdir)/Modules/hashlib.h
+-MODULE__CTYPES_DEPS=$(srcdir)/Modules/_ctypes/ctypes.h
++MODULE__CTYPES_DEPS=$(srcdir)/Modules/_ctypes/ctypes.h $(srcdir)/Modules/_ctypes/darwin/dlfcn.h
++MODULE__CTYPES_MALLOC_CLOSURE=@MODULE__CTYPES_MALLOC_CLOSURE@
+ MODULE__DECIMAL_DEPS=$(srcdir)/Modules/_decimal/docstrings.h @LIBMPDEC_INTERNAL@
+ MODULE__ELEMENTTREE_DEPS=$(srcdir)/Modules/pyexpat.c @LIBEXPAT_INTERNAL@
+ MODULE__HASHLIB_DEPS=$(srcdir)/Modules/hashlib.h
+diff --git a/Misc/NEWS.d/next/Library/2022-04-01-12-35-44.gh-issue-90005.pvaLHQ.rst b/Misc/NEWS.d/next/Library/2022-04-01-12-35-44.gh-issue-90005.pvaLHQ.rst
+new file mode 100644
+index 0000000000000..ef6a881a4d094
+--- /dev/null
++++ b/Misc/NEWS.d/next/Library/2022-04-01-12-35-44.gh-issue-90005.pvaLHQ.rst
+@@ -0,0 +1 @@
++:mod:`ctypes` dependency ``libffi`` is now detected with ``pkg-config``.
+diff --git a/Modules/Setup.stdlib.in b/Modules/Setup.stdlib.in
+index 2730030a15650..a199aefc51011 100644
+--- a/Modules/Setup.stdlib.in
++++ b/Modules/Setup.stdlib.in
+@@ -136,7 +136,7 @@
+ #
+ 
+ # needs -lffi and -ldl
+-#@MODULE__CTYPES_TRUE@_ctypes _ctypes/_ctypes.c _ctypes/callbacks.c _ctypes/callproc.c _ctypes/stgdict.c _ctypes/cfield.c
++@MODULE__CTYPES_TRUE@_ctypes _ctypes/_ctypes.c _ctypes/callbacks.c _ctypes/callproc.c _ctypes/stgdict.c _ctypes/cfield.c @MODULE__CTYPES_MALLOC_CLOSURE@
+ 
+ # needs -lncurses, -lncursesw or -lcurses, sometimes -ltermcap
+ #@MODULE__CURSES_TRUE@_curses _cursesmodule.c
+diff --git a/Modules/_ctypes/callproc.c b/Modules/_ctypes/callproc.c
+index 3fab9ad0c1e7b..fa1dfac6c7d94 100644
+--- a/Modules/_ctypes/callproc.c
++++ b/Modules/_ctypes/callproc.c
+@@ -54,6 +54,9 @@
+ 
+  */
+ 
++#ifndef Py_BUILD_CORE_BUILTIN
++#  define Py_BUILD_CORE_MODULE 1
++#endif
+ #define NEEDS_PY_IDENTIFIER
+ 
+ #include "Python.h"
+diff --git a/Modules/_ctypes/malloc_closure.c b/Modules/_ctypes/malloc_closure.c
+index 38edc90e70763..d47153f1d7f3e 100644
+--- a/Modules/_ctypes/malloc_closure.c
++++ b/Modules/_ctypes/malloc_closure.c
+@@ -1,3 +1,6 @@
++#ifndef Py_BUILD_CORE_BUILTIN
++#  define Py_BUILD_CORE_MODULE 1
++#endif
+ #include <Python.h>
+ #include <ffi.h>
+ #ifdef MS_WIN32
+diff --git a/configure.ac b/configure.ac
+index f9abd851ea5cb..6a8a0a963afa2 100644
+--- a/configure.ac
++++ b/configure.ac
+@@ -49,6 +49,26 @@ AC_DEFUN([WITH_SAVE_ENV],
+ [RESTORE_ENV]
+ )dnl
+ 
++dnl PY_CHECK_FUNC(FUNCTION, [INCLUDES], [AC_DEFINE-VAR])
++AC_DEFUN([PY_CHECK_FUNC],
++[ AS_VAR_PUSHDEF([py_var], [ac_cv_func_$1])
++  AS_VAR_PUSHDEF([py_define], m4_ifblank([$3], [[HAVE_]m4_toupper($1)], [$3]))
++  AC_CACHE_CHECK(
++    [for $1],
++    [py_var],
++    [AC_COMPILE_IFELSE(
++      [AC_LANG_PROGRAM([$2], [void *x=$1])],
++      [AS_VAR_SET([py_var], [yes])],
++      [AS_VAR_SET([py_var], [no])])]
++  )
++  AS_VAR_IF(
++    [py_var],
++    [yes],
++    [AC_DEFINE([py_define], [1], [Define if you have the '$1' function.])])
++  AS_VAR_POPDEF([py_var])
++  AS_VAR_POPDEF([py_define])
++])
++
+ AC_SUBST(BASECPPFLAGS)
+ if test "$srcdir" != . -a "$srcdir" != "$(pwd)"; then
+     # If we're building out-of-tree, we need to make sure the following
+@@ -713,6 +733,21 @@ fi
+ 
+ if test "$ac_sys_system" = "Darwin"
+ then
++  dnl look for SDKROOT
++  AC_CHECK_PROG([HAS_XCRUN], [xcrun], [yes], [missing])
++  AC_MSG_CHECKING([macOS SDKROOT])
++  if test -z "$SDKROOT"; then
++    dnl SDKROOT not set
++    if test "$HAS_XCRUN" = "yes"; then
++      dnl detect with Xcode
++      SDKROOT=$(xcrun --show-sdk-path)
++    else
++      dnl default to root
++      SDKROOT="/"
++    fi
++  fi
++  AC_MSG_RESULT([$SDKROOT])
++
+ 	# Compiler selection on MacOSX is more complicated than
+ 	# AC_PROG_CC can handle, see Mac/README for more
+ 	# information
+@@ -1101,7 +1136,7 @@ AC_DEFINE_UNQUOTED([PY_SUPPORT_TIER], [$PY_SUPPORT_TIER], [PEP 11 Support tier (
+ 
+ AC_CACHE_CHECK([for -Wl,--no-as-needed], [ac_cv_wl_no_as_needed], [
+   save_LDFLAGS="$LDFLAGS"
+-  AS_VAR_APPEND([LDFLAGS], [-Wl,--no-as-needed])
++  AS_VAR_APPEND([LDFLAGS], [" -Wl,--no-as-needed"])
+   AC_LINK_IFELSE([AC_LANG_PROGRAM([[]], [[]])],
+     [NO_AS_NEEDED="-Wl,--no-as-needed"
+      ac_cv_wl_no_as_needed=yes],
+@@ -3564,12 +3599,60 @@ else
+     with_system_ffi="yes"
+ fi
+ 
+-if test "$with_system_ffi" = "yes" && test -n "$PKG_CONFIG"; then
+-    LIBFFI_INCLUDEDIR="`"$PKG_CONFIG" libffi --cflags-only-I 2>/dev/null | sed -e 's/^-I//;s/ *$//'`"
+-else
+-    LIBFFI_INCLUDEDIR=""
+-fi
+-AC_SUBST(LIBFFI_INCLUDEDIR)
++dnl detect libffi
++have_libffi=missing
++AS_VAR_IF([with_system_ffi], [yes], [
++  PKG_CHECK_MODULES([LIBFFI], [libffi], [have_libffi=yes], [
++    AC_CHECK_HEADER([ffi.h], [
++      WITH_SAVE_ENV([
++        AC_CHECK_LIB([ffi], [ffi_call], [have_libffi=yes], [have_libffi=no])
++      ])
++    ])
++  ])
++], [
++  AS_VAR_IF([ac_sys_system], [Darwin], [
++    WITH_SAVE_ENV([
++      CFLAGS="-I${SDKROOT}/usr/include/ffi $CFLAGS"
++      AC_CHECK_HEADER([ffi.h], [
++        AC_CHECK_LIB([ffi], [ffi_call], [
++          dnl use ffi from SDK root
++          have_libffi=yes
++          LIBFFI_CFLAGS="-I${SDKROOT}/usr/include/ffi -DUSING_APPLE_OS_LIBFFI=1"
++          LIBFFI_LIBS="-lffi"
++        ], [have_libffi=no])
++      ])
++    ])
++  ])
++])
++
++AS_VAR_IF([have_libffi], [yes], [
++  ctypes_malloc_closure=no
++  AS_CASE([$ac_sys_system],
++    [Darwin], [
++      dnl when do we need USING_APPLE_OS_LIBFFI?
++      AS_VAR_APPEND([LIBFFI_CFLAGS], [" -I\$(srcdir)/Modules/_ctypes/darwin -DMACOSX"])
++      ctypes_malloc_closure=yes
++    ],
++    [sunos5], [AS_VAR_APPEND([LIBFFI_LIBS], [" -mimpure-text"])]
++  )
++  AS_VAR_IF([ctypes_malloc_closure], [yes], [
++    MODULE__CTYPES_MALLOC_CLOSURE=_ctypes/malloc_closure.c
++    AS_VAR_APPEND([LIBFFI_CFLAGS], [" -DUSING_MALLOC_CLOSURE_DOT_C=1"])
++  ])
++  AC_SUBST([MODULE__CTYPES_MALLOC_CLOSURE])
++
++  dnl HAVE_LIBDL: for dlopen, see gh-76828
++  AS_VAR_IF([ac_cv_lib_dl_dlopen], [yes], [AS_VAR_APPEND([LIBFFI_LIBS], [" -ldl"])])
++
++  WITH_SAVE_ENV([
++    CFLAGS="$LIBFFI_CFLAGS $CFLAGS"
++    LDFLAGS="$LIBFFI_LIBS $LDFLAGS"
++
++    PY_CHECK_FUNC([ffi_prep_cif_var], [#include <ffi.h>])
++    PY_CHECK_FUNC([ffi_prep_closure_loc], [#include <ffi.h>])
++    PY_CHECK_FUNC([ffi_closure_alloc], [#include <ffi.h>])
++  ])
++])
+ 
+ # Check for use of the system libmpdec library
+ AC_MSG_CHECKING(for --with-system-libmpdec)
+@@ -4526,26 +4609,6 @@ AC_CHECK_DECL(dirfd,
+       [#include <sys/types.h>
+        #include <dirent.h>])
+ 
+-dnl PY_CHECK_FUNC(FUNCTION, [INCLUDES], [AC_DEFINE-VAR])
+-AC_DEFUN([PY_CHECK_FUNC],
+-[ AS_VAR_PUSHDEF([py_var], [ac_cv_func_$1])
+-  AS_VAR_PUSHDEF([py_define], m4_ifblank([$3], [[HAVE_]m4_toupper($1)], [$3]))
+-  AC_CACHE_CHECK(
+-    [for $1],
+-    [py_var],
+-    [AC_COMPILE_IFELSE(
+-      [AC_LANG_PROGRAM([$2], [void *x=$1])],
+-      [AS_VAR_SET([py_var], [yes])],
+-      [AS_VAR_SET([py_var], [no])])]
+-  )
+-  AS_VAR_IF(
+-    [py_var],
+-    [yes],
+-    [AC_DEFINE([py_define], [1], [Define if you have the '$1' function.])])
+-  AS_VAR_POPDEF([py_var])
+-  AS_VAR_POPDEF([py_define])
+-])
+-
+ # For some functions, having a definition is not sufficient, since
+ # we want to take their address.
+ PY_CHECK_FUNC([chroot], [#include <unistd.h>])
+@@ -6868,7 +6931,9 @@ PY_STDLIB_MOD([_blake2],
+ PY_STDLIB_MOD([_crypt],
+   [], [test "$ac_cv_crypt_crypt" = yes],
+   [$LIBCRYPT_CFLAGS], [$LIBCRYPT_LIBS])
+-dnl PY_STDLIB_MOD([_ctypes], [], [], [], [])
++PY_STDLIB_MOD([_ctypes],
++  [], [test "$have_libffi" = yes],
++  [$LIBFFI_CFLAGS], [$LIBFFI_LIBS])
+ dnl PY_STDLIB_MOD([_curses], [], [], [], [])
+ dnl PY_STDLIB_MOD([_curses_panel], [], [], [], [])
+ PY_STDLIB_MOD([_decimal], [], [], [$LIBMPDEC_CFLAGS], [$LIBMPDEC_LDFLAGS])
+@@ -6914,7 +6979,9 @@ PY_STDLIB_MOD([_testbuffer], [test "$TEST_MODULES" = yes])
+ PY_STDLIB_MOD([_testimportmultiple], [test "$TEST_MODULES" = yes], [test "$ac_cv_func_dlopen" = yes])
+ PY_STDLIB_MOD([_testmultiphase], [test "$TEST_MODULES" = yes], [test "$ac_cv_func_dlopen" = yes])
+ PY_STDLIB_MOD([_xxtestfuzz], [test "$TEST_MODULES" = yes])
+-PY_STDLIB_MOD([_ctypes_test], [test "$TEST_MODULES" = yes], [test "$ac_cv_func_dlopen" = yes], [], [-lm])
++PY_STDLIB_MOD([_ctypes_test],
++  [test "$TEST_MODULES" = yes], [test "$have_libffi" = yes -a "$ac_cv_func_dlopen" = yes],
++  [], [$LIBM])
+ 
+ dnl Limited API template modules.
+ dnl The limited C API is not compatible with the Py_TRACE_REFS macro.
+diff --git a/pyconfig.h.in b/pyconfig.h.in
+index a09652ec15e53..15933e75b1b07 100644
+--- a/pyconfig.h.in
++++ b/pyconfig.h.in
+@@ -356,6 +356,15 @@
+ /* Define to 1 if you have the `fexecve' function. */
+ #undef HAVE_FEXECVE
+ 
++/* Define if you have the 'ffi_closure_alloc' function. */
++#undef HAVE_FFI_CLOSURE_ALLOC
++
++/* Define if you have the 'ffi_prep_cif_var' function. */
++#undef HAVE_FFI_PREP_CIF_VAR
++
++/* Define if you have the 'ffi_prep_closure_loc' function. */
++#undef HAVE_FFI_PREP_CLOSURE_LOC
++
+ /* Define to 1 if you have the `flock' function. */
+ #undef HAVE_FLOCK
+ 
+diff --git a/setup.py b/setup.py
+index bba344c3af07c..af2800744091c 100644
+--- a/setup.py
++++ b/setup.py
+@@ -395,11 +395,6 @@ def remove_disabled(self):
+         # Remove modules that are present on the disabled list
+         extensions = [ext for ext in self.extensions
+                       if ext.name not in DISABLED_MODULE_LIST]
+-        # move ctypes to the end, it depends on other modules
+-        ext_map = dict((ext.name, i) for i, ext in enumerate(extensions))
+-        if "_ctypes" in ext_map:
+-            ctypes = extensions.pop(ext_map["_ctypes"])
+-            extensions.append(ctypes)
+         self.extensions = extensions
+ 
+     def update_sources_depends(self):
+@@ -600,12 +595,6 @@ def print_three_column(lst):
+             raise RuntimeError("Failed to build some stdlib modules")
+ 
+     def build_extension(self, ext):
+-
+-        if ext.name == '_ctypes':
+-            if not self.configure_ctypes(ext):
+-                self.failed.append(ext.name)
+-                return
+-
+         try:
+             build_ext.build_extension(self, ext)
+         except (CCompilerError, DistutilsError) as why:
+@@ -1370,102 +1359,24 @@ def detect_modules(self):
+     def detect_tkinter(self):
+         self.addext(Extension('_tkinter', ['_tkinter.c', 'tkappinit.c']))
+ 
+-    def configure_ctypes(self, ext):
+-        return True
+-
+     def detect_ctypes(self):
+         # Thomas Heller's _ctypes module
++        src = [
++            '_ctypes/_ctypes.c',
++            '_ctypes/callbacks.c',
++            '_ctypes/callproc.c',
++            '_ctypes/stgdict.c',
++            '_ctypes/cfield.c',
++        ]
++        malloc_closure = sysconfig.get_config_var(
++            "MODULE__CTYPES_MALLOC_CLOSURE"
++        )
++        if malloc_closure:
++            src.append(malloc_closure)
+ 
+-        if (not sysconfig.get_config_var("LIBFFI_INCLUDEDIR") and MACOS):
+-            self.use_system_libffi = True
+-        else:
+-            self.use_system_libffi = '--with-system-ffi' in sysconfig.get_config_var("CONFIG_ARGS")
+-
+-        include_dirs = []
+-        extra_compile_args = []
+-        extra_link_args = []
+-        sources = ['_ctypes/_ctypes.c',
+-                   '_ctypes/callbacks.c',
+-                   '_ctypes/callproc.c',
+-                   '_ctypes/stgdict.c',
+-                   '_ctypes/cfield.c']
+-
+-        if MACOS:
+-            sources.append('_ctypes/malloc_closure.c')
+-            extra_compile_args.append('-DUSING_MALLOC_CLOSURE_DOT_C=1')
+-            extra_compile_args.append('-DMACOSX')
+-            include_dirs.append('_ctypes/darwin')
+-
+-        elif HOST_PLATFORM == 'sunos5':
+-            # XXX This shouldn't be necessary; it appears that some
+-            # of the assembler code is non-PIC (i.e. it has relocations
+-            # when it shouldn't. The proper fix would be to rewrite
+-            # the assembler code to be PIC.
+-            # This only works with GCC; the Sun compiler likely refuses
+-            # this option. If you want to compile ctypes with the Sun
+-            # compiler, please research a proper solution, instead of
+-            # finding some -z option for the Sun compiler.
+-            extra_link_args.append('-mimpure-text')
+-
+-        ext = Extension('_ctypes',
+-                        include_dirs=include_dirs,
+-                        extra_compile_args=extra_compile_args,
+-                        extra_link_args=extra_link_args,
+-                        libraries=[],
+-                        sources=sources)
+-        self.add(ext)
+-        # function my_sqrt() needs libm for sqrt()
++        self.addext(Extension('_ctypes', src))
+         self.addext(Extension('_ctypes_test', ['_ctypes/_ctypes_test.c']))
+ 
+-        ffi_inc = sysconfig.get_config_var("LIBFFI_INCLUDEDIR")
+-        ffi_lib = None
+-
+-        ffi_inc_dirs = self.inc_dirs.copy()
+-        if MACOS:
+-            ffi_in_sdk = os.path.join(macosx_sdk_root(), "usr/include/ffi")
+-
+-            if not ffi_inc:
+-                if os.path.exists(ffi_in_sdk):
+-                    ext.extra_compile_args.append("-DUSING_APPLE_OS_LIBFFI=1")
+-                    ffi_inc = ffi_in_sdk
+-                    ffi_lib = 'ffi'
+-                else:
+-                    # OS X 10.5 comes with libffi.dylib; the include files are
+-                    # in /usr/include/ffi
+-                    ffi_inc_dirs.append('/usr/include/ffi')
+-
+-        if not ffi_inc:
+-            found = find_file('ffi.h', [], ffi_inc_dirs)
+-            if found:
+-                ffi_inc = found[0]
+-        if ffi_inc:
+-            ffi_h = ffi_inc + '/ffi.h'
+-            if not os.path.exists(ffi_h):
+-                ffi_inc = None
+-                print('Header file {} does not exist'.format(ffi_h))
+-        if ffi_lib is None and ffi_inc:
+-            for lib_name in ('ffi', 'ffi_pic'):
+-                if (self.compiler.find_library_file(self.lib_dirs, lib_name)):
+-                    ffi_lib = lib_name
+-                    break
+-
+-        if ffi_inc and ffi_lib:
+-            ffi_headers = glob(os.path.join(ffi_inc, '*.h'))
+-            if grep_headers_for('ffi_prep_cif_var', ffi_headers):
+-                ext.extra_compile_args.append("-DHAVE_FFI_PREP_CIF_VAR=1")
+-            if grep_headers_for('ffi_prep_closure_loc', ffi_headers):
+-                ext.extra_compile_args.append("-DHAVE_FFI_PREP_CLOSURE_LOC=1")
+-            if grep_headers_for('ffi_closure_alloc', ffi_headers):
+-                ext.extra_compile_args.append("-DHAVE_FFI_CLOSURE_ALLOC=1")
+-
+-            ext.include_dirs.append(ffi_inc)
+-            ext.libraries.append(ffi_lib)
+-            self.use_system_libffi = True
+-
+-        if sysconfig.get_config_var('HAVE_LIBDL'):
+-            # for dlopen, see bpo-32647
+-            ext.libraries.append('dl')
+-
+     def detect_decimal(self):
+         # Stefan Krah's _decimal module
+         self.addext(
+--- Makefile.pre.in.orig	2023-08-07 20:50:54.600398448 +0200
++++ Makefile.pre.in	2023-08-07 20:53:37.130317846 +0200
diff --git a/package/python3/0020-Port-readline-and-curses-to-PY_STDLIB_MOD.patch b/package/python3/0020-Port-readline-and-curses-to-PY_STDLIB_MOD.patch
new file mode 100644
index 0000000000..43a5eb07ef
--- /dev/null
+++ b/package/python3/0020-Port-readline-and-curses-to-PY_STDLIB_MOD.patch
@@ -0,0 +1,718 @@
+From e925241d95d8095adf67f492042f97254ff82ec1 Mon Sep 17 00:00:00 2001
+From: Christian Heimes <christian@python.org>
+Date: Wed, 6 Jul 2022 11:56:25 +0200
+Subject: [PATCH] gh-90005: Port readline and curses to PY_STDLIB_MOD
+ (GH-94452)
+
+Co-authored-by: Erlend Egeberg Aasland <erlend.aasland@protonmail.com>
+
+Upstream: https://github.com/python/cpython/commit/e925241d95d8095adf67f492042f97254ff82ec1
+
+[Bernd: backported to 3.11.4]
+Signed-off-by: Bernd Kuhls <bernd@kuhls.net>
+---
+ ...2-06-30-17-18-23.gh-issue-90005.EIOOla.rst |    5 +
+ Modules/Setup.stdlib.in                       |   12 +-
+ configure                                     | 1977 ++++++++++++++---
+ configure.ac                                  |  388 +++-
+ pyconfig.h.in                                 |   17 +-
+ setup.py                                      |  146 +-
+ 6 files changed, 2015 insertions(+), 530 deletions(-)
+ create mode 100644 Misc/NEWS.d/next/Build/2022-06-30-17-18-23.gh-issue-90005.EIOOla.rst
+
+diff --git a/Misc/NEWS.d/next/Build/2022-06-30-17-18-23.gh-issue-90005.EIOOla.rst b/Misc/NEWS.d/next/Build/2022-06-30-17-18-23.gh-issue-90005.EIOOla.rst
+new file mode 100644
+index 0000000000000..90a2dd486c195
+--- /dev/null
++++ b/Misc/NEWS.d/next/Build/2022-06-30-17-18-23.gh-issue-90005.EIOOla.rst
+@@ -0,0 +1,5 @@
++Dependencies of :mod:`readline` and :mod:`curses` module are now detected in
++``configure`` script with ``pkg-config``. Only ``ncurses`` / ``ncursesw``
++are detected automatically. The old ``curses`` library is not configured
++automatically. Workaround for missing ``termcap`` or ``tinfo`` library
++has been removed.
+diff --git a/Modules/Setup.stdlib.in b/Modules/Setup.stdlib.in
+index ad34f85e25451..7357aacd7267a 100644
+--- a/Modules/Setup.stdlib.in
++++ b/Modules/Setup.stdlib.in
+@@ -72,8 +72,8 @@
+ # gdbm module needs -lgdbm
+ @MODULE__GDBM_TRUE@_gdbm _gdbmmodule.c
+ 
+-# needs -lreadline or -leditline, sometimes termcap, termlib, or tinfo
+-#@MODULE_READLINE_TRUE@readline readline.c
++# needs -lreadline or -ledit, sometimes termcap, termlib, or tinfo
++@MODULE_READLINE_TRUE@readline readline.c
+ 
+ # hashing builtins, can be disabled with --without-builtin-hashlib-hashes
+ @MODULE__MD5_TRUE@_md5 md5module.c
+@@ -138,10 +138,10 @@
+ # needs -lffi and -ldl
+ @MODULE__CTYPES_TRUE@_ctypes _ctypes/_ctypes.c _ctypes/callbacks.c _ctypes/callproc.c _ctypes/stgdict.c _ctypes/cfield.c @MODULE__CTYPES_MALLOC_CLOSURE@
+ 
+-# needs -lncurses, -lncursesw or -lcurses, sometimes -ltermcap
+-#@MODULE__CURSES_TRUE@_curses _cursesmodule.c
+-# needs -lncurses and -lpanel
+-#@MODULE__CURSES_PANEL_TRUE@_curses_panel _curses_panel.c
++# needs -lncurses[w], sometimes -ltermcap/tinfo
++@MODULE__CURSES_TRUE@_curses _cursesmodule.c
++# needs -lncurses[w] and -lpanel[w]
++@MODULE__CURSES_PANEL_TRUE@_curses_panel _curses_panel.c
+ 
+ @MODULE__SQLITE3_TRUE@_sqlite3 _sqlite/blob.c _sqlite/connection.c _sqlite/cursor.c _sqlite/microprotocols.c _sqlite/module.c _sqlite/prepare_protocol.c _sqlite/row.c _sqlite/statement.c _sqlite/util.c
+ 
+diff --git a/configure.ac b/configure.ac
+index b03ead3bdefa0..42e181bca9dac 100644
+--- a/configure.ac
++++ b/configure.ac
+@@ -5780,127 +5780,169 @@ then
+   [Define this if you have flockfile(), getc_unlocked(), and funlockfile()])
+ fi
+ 
+-AC_ARG_WITH([readline],
+-  [AS_HELP_STRING([--with(out)-readline@<:@=editline@:>@],
+-    [use Editline for backend or disable readline module])],
+-    [],
+-    [with_readline=yes])
++dnl Check for libreadline and libedit
++dnl - libreadline provides "readline/readline.h" header and "libreadline"
++dnl   shared library. pkg-config file is readline.pc
++dnl - libedit provides "editline/readline.h" header and "libedit" shared
++dnl   library. pkg-config file ins libedit.pc
++dnl - editline is not supported ("readline.h" and "libeditline" shared library)
++dnl
++dnl NOTE: In the past we checked if readline needs an additional termcap
++dnl library (tinfo ncursesw ncurses termcap). We now assume that libreadline
++dnl or readline.pc provide correct linker information.
+ 
+-# check where readline lives
+-py_cv_lib_readline=no
+-# save the value of LIBS so we don't actually link Python with readline
+-LIBS_no_readline=$LIBS
++AH_TEMPLATE([WITH_EDITLINE], [Define to build the readline module against libedit.])
+ 
+-if test "$with_readline" != no; then
+-  case "$with_readline" in
+-  editline|edit)
+-    LIBREADLINE=edit
+-    AC_DEFINE(WITH_EDITLINE, 1,
+-      [Define to build the readline module against Editline.])
+-    ;;
+-  yes|readline)
++AC_ARG_WITH(
++  [readline],
++  [AS_HELP_STRING([--with(out)-readline@<:@=editline|readline|no@:>@],
++                  [use libedit for backend or disable readline module])],
++  [
++    AS_CASE([$with_readline],
++      [editline|edit], [with_readline=edit],
++      [yes|readline], [with_readline=readline],
++      [no], [],
++      [AC_MSG_ERROR([proper usage is --with(out)-readline@<:@=editline|readline|no@:>@])]
++    )
++  ],
++  [with_readline=readline]
++)
++
++AS_VAR_IF([with_readline], [readline], [
++  PKG_CHECK_MODULES([LIBREADLINE], [readline], [
+     LIBREADLINE=readline
+-    ;;
+-  *)
+-    AC_MSG_ERROR([proper usage is --with(out)-readline@<:@=editline@:>@])
+-    ;;
+-  esac
++    READLINE_CFLAGS=$LIBREADLINE_CFLAGS
++    READLINE_LIBS=$LIBREADLINE_LIBS
++  ], [
++    AC_CHECK_HEADERS([readline/readline.h], [
++      WITH_SAVE_ENV([
++        AC_CHECK_LIB([readline], [readline], [
++          LIBREADLINE=readline
++          READLINE_CFLAGS=${LIBREADLINE_CFLAGS-""}
++          READLINE_LIBS=${LIBREADLINE_LIBS-"-lreadline"}
++        ], [
++          with_readline=no
++        ])
++      ])
++    ], [with_readline=no])
++  ])
++])
+ 
+-  # On some systems we need to link readline to a termcap compatible
+-  # library.  NOTE: Keep the precedence of listed libraries synchronised
+-  # with setup.py.
+-  AC_MSG_CHECKING([how to link readline libs])
+-  for py_libtermcap in "" tinfo ncursesw ncurses curses termcap; do
+-    if test -z "$py_libtermcap"; then
+-      READLINE_LIBS="-l$LIBREADLINE"
+-    else
+-      READLINE_LIBS="-l$LIBREADLINE -l$py_libtermcap"
+-    fi
+-    LIBS="$READLINE_LIBS $LIBS_no_readline"
+-    AC_LINK_IFELSE(
+-      [AC_LANG_CALL([],[readline])],
+-      [py_cv_lib_readline=yes])
+-    if test $py_cv_lib_readline = yes; then
+-      break
+-    fi
+-  done
++AS_VAR_IF([with_readline], [edit], [
++  PKG_CHECK_MODULES([LIBEDIT], [libedit], [
++    AC_DEFINE([WITH_EDITLINE], [1])
++    LIBREADLINE=edit
++    READLINE_CFLAGS=$LIBEDIT_CFLAGS
++    READLINE_LIBS=$LIBEDIT_LIBS
++  ], [
++    AC_CHECK_HEADERS([editline/readline.h], [
++      WITH_SAVE_ENV([
++        AC_CHECK_LIB([edit], [readline], [
++          LIBREADLINE=edit
++          AC_DEFINE([WITH_EDITLINE], [1])
++          READLINE_CFLAGS=${LIBEDIT_CFLAGS-""}
++          READLINE_LIBS=${LIBEDIT_LIBS-"-ledit"}
++        ], [
++          with_readline=no
++        ])
++      ])
++    ], [with_readline=no])
++  ])
++])
+ 
+-  # Uncomment this line if you want to use READLINE_LIBS in Makefile or scripts
+-  #AC_SUBST([READLINE_LIBS])
+-  if test $py_cv_lib_readline = no; then
+-    AC_MSG_RESULT([none])
+-  else
+-    AC_MSG_RESULT([$READLINE_LIBS])
+-    AC_DEFINE(HAVE_LIBREADLINE, 1,
+-      [Define to build the readline module.])
+-  fi
+-fi
+ 
+-if test "$py_cv_lib_readline" = yes; then
+-  # check for readline 2.2
+-  AC_CHECK_DECL(rl_completion_append_character,
+-    AC_DEFINE(HAVE_RL_COMPLETION_APPEND_CHARACTER, 1,
+-      [Define if you have readline 2.2]),,
+-    [
+-#include <stdio.h> /* Must be first for Gnu Readline */
+-#ifdef WITH_EDITLINE
+-# include <editline/readline.h>
+-#else
+-# include <readline/readline.h>
+-#endif
++AC_MSG_CHECKING([how to link readline])
++AS_VAR_IF([with_readline], [no], [
++  AC_MSG_RESULT([no])
++], [
++  AC_MSG_RESULT([$with_readline (CFLAGS: $READLINE_CFLAGS, LIBS: $READLINE_LIBS)])
++
++  WITH_SAVE_ENV([
++    CPPFLAGS="$READLINE_CFLAGS $CFLAGS"
++    LIBS="$READLINE_LIBS $LIBS"
++    LIBS_SAVE=$LIBS
++
++    m4_define([readline_includes], [
++      #include <stdio.h> /* Must be first for Gnu Readline */
++      #ifdef WITH_EDITLINE
++      # include <editline/readline.h>
++      #else
++      # include <readline/readline.h>
++      # include <readline/history.h>
++      #endif
+     ])
+-  AC_CHECK_DECL(rl_completion_suppress_append,
+-    AC_DEFINE(HAVE_RL_COMPLETION_SUPPRESS_APPEND, 1,
+-      [Define if you have rl_completion_suppress_append]),,
+-    [
+-#include <stdio.h> /* Must be first for Gnu Readline */
+-#ifdef WITH_EDITLINE
+-# include <editline/readline.h>
+-#else
+-# include <readline/readline.h>
+-#endif
++
++    # check for readline 2.2
++    AC_CHECK_DECL([rl_completion_append_character], [
++      AC_DEFINE([HAVE_RL_COMPLETION_APPEND_CHARACTER], [1], [Define if you have readline 2.2])
++    ], [], [readline_includes])
++
++    AC_CHECK_DECL([rl_completion_suppress_append], [
++      AC_DEFINE([HAVE_RL_COMPLETION_SUPPRESS_APPEND], [1], [Define if you have rl_completion_suppress_append])
++    ], [], [readline_includes])
++
++    # check for readline 4.0
++    AC_CACHE_CHECK([for rl_pre_input_hook in -l$LIBREADLINE], [ac_cv_readline_rl_pre_input_hook], [
++      AC_LINK_IFELSE(
++        [AC_LANG_PROGRAM([readline_includes], [void *x = rl_pre_input_hook])],
++        [ac_cv_readline_rl_pre_input_hook=yes], [ac_cv_readline_rl_pre_input_hook=no]
++      )
++    ])
++    AS_VAR_IF([ac_cv_readline_rl_pre_input_hook], [yes], [
++      AC_DEFINE([HAVE_RL_PRE_INPUT_HOOK], [1], [Define if you have readline 4.0])
+     ])
+ 
+-  # check for readline 4.0
+-  AC_CHECK_LIB($LIBREADLINE, rl_pre_input_hook,
+-    AC_DEFINE(HAVE_RL_PRE_INPUT_HOOK, 1,
+-      [Define if you have readline 4.0]),,$READLINE_LIBS)
+-
+-  # also in 4.0
+-  AC_CHECK_LIB($LIBREADLINE, rl_completion_display_matches_hook,
+-    AC_DEFINE(HAVE_RL_COMPLETION_DISPLAY_MATCHES_HOOK, 1,
+-      [Define if you have readline 4.0]),,$READLINE_LIBS)
+-
+-  # also in 4.0, but not in editline
+-  AC_CHECK_LIB($LIBREADLINE, rl_resize_terminal,
+-    AC_DEFINE(HAVE_RL_RESIZE_TERMINAL, 1,
+-      [Define if you have readline 4.0]),,$READLINE_LIBS)
+-
+-  # check for readline 4.2
+-  AC_CHECK_LIB($LIBREADLINE, rl_completion_matches,
+-    AC_DEFINE(HAVE_RL_COMPLETION_MATCHES, 1,
+-      [Define if you have readline 4.2]),,$READLINE_LIBS)
+-
+-  # also in readline 4.2
+-  AC_CHECK_DECL(rl_catch_signals,
+-    AC_DEFINE(HAVE_RL_CATCH_SIGNAL, 1,
+-      [Define if you can turn off readline's signal handling.]),,
+-    [
+-#include <stdio.h> /* Must be first for Gnu Readline */
+-#ifdef WITH_EDITLINE
+-# include <editline/readline.h>
+-#else
+-# include <readline/readline.h>
+-#endif
++    # also in 4.0
++    AC_CACHE_CHECK([for rl_completion_display_matches_hook in -l$LIBREADLINE], [ac_cv_readline_rl_completion_display_matches_hook], [
++      AC_LINK_IFELSE(
++        [AC_LANG_PROGRAM([readline_includes], [void *x = rl_completion_display_matches_hook])],
++        [ac_cv_readline_rl_completion_display_matches_hook=yes], [ac_cv_readline_rl_completion_display_matches_hook=no]
++      )
++    ])
++    AS_VAR_IF([ac_cv_readline_rl_completion_display_matches_hook], [yes], [
++      AC_DEFINE([HAVE_RL_COMPLETION_DISPLAY_MATCHES_HOOK], [1], [Define if you have readline 4.0])
+     ])
+ 
+-  AC_CHECK_LIB($LIBREADLINE, append_history,
+-    AC_DEFINE(HAVE_RL_APPEND_HISTORY, 1,
+-      [Define if readline supports append_history]),,$READLINE_LIBS)
+-fi
++    # also in 4.0, but not in editline
++      AC_CACHE_CHECK([for rl_resize_terminal in -l$LIBREADLINE], [ac_cv_readline_rl_resize_terminal], [
++      AC_LINK_IFELSE(
++        [AC_LANG_PROGRAM([readline_includes], [void *x = rl_resize_terminal])],
++        [ac_cv_readline_rl_resize_terminal=yes], [ac_cv_readline_rl_resize_terminal=no]
++      )
++    ])
++    AS_VAR_IF([ac_cv_readline_rl_resize_terminal], [yes], [
++      AC_DEFINE([HAVE_RL_RESIZE_TERMINAL], [1], [Define if you have readline 4.0])
++    ])
+ 
+-# End of readline checks: restore LIBS
+-LIBS=$LIBS_no_readline
++    # check for readline 4.2
++    AC_CACHE_CHECK([for rl_completion_matches in -l$LIBREADLINE], [ac_cv_readline_rl_completion_matches], [
++      AC_LINK_IFELSE(
++        [AC_LANG_PROGRAM([readline_includes], [void *x = rl_completion_matches])],
++        [ac_cv_readline_rl_completion_matches=yes], [ac_cv_readline_rl_completion_matches=no]
++      )
++    ])
++    AS_VAR_IF([ac_cv_readline_rl_completion_matches], [yes], [
++      AC_DEFINE([HAVE_RL_COMPLETION_MATCHES], [1], [Define if you have readline 4.2])
++    ])
++
++    # also in readline 4.2
++    AC_CHECK_DECL([rl_catch_signals], [
++      AC_DEFINE([HAVE_RL_CATCH_SIGNAL], [1], [Define if you can turn off readline's signal handling.])
++    ], [], [readline_includes])
++
++    AC_CACHE_CHECK([for append_history in -l$LIBREADLINE], [ac_cv_readline_append_history], [
++      AC_LINK_IFELSE(
++        [AC_LANG_PROGRAM([readline_includes], [void *x = append_history])],
++        [ac_cv_readline_append_history=yes], [ac_cv_readline_append_history=no]
++      )
++    ])
++    AS_VAR_IF([ac_cv_readline_append_history], [yes], [
++      AC_DEFINE([HAVE_RL_APPEND_HISTORY], [1], [Define if readline supports append_history])
++    ])
++
++    m4_undefine([readline_includes])
++  ])dnl WITH_SAVE_ENV()
++])
+ 
+ AC_CACHE_CHECK([for broken nice()], [ac_cv_broken_nice], [
+ AC_RUN_IFELSE([AC_LANG_SOURCE([[
+@@ -6056,14 +6098,124 @@ then
+   [Define if you have struct stat.st_mtimensec])
+ fi
+ 
++dnl check for ncurses/ncursesw and panel/panelw
++dnl NOTE: old curses is not detected.
++dnl have_curses=[no, ncursesw, ncurses]
++dnl have_panel=[no, panelw, panel]
++have_curses=no
++have_panel=no
++
++AH_TEMPLATE([HAVE_NCURSESW], [Define to 1 if you have the `ncursesw' library.])
++AC_CHECK_HEADERS([curses.h ncurses.h])
++
++AS_VAR_IF([ac_cv_header_ncurses_h], [yes], [
++  if test "$ac_sys_system" != "Darwin"; then
++    dnl On macOS, there is no separate /usr/lib/libncursesw nor libpanelw.
++    PKG_CHECK_MODULES([CURSES], [ncursesw], [
++      have_curses=ncursesw
++    ], [
++      WITH_SAVE_ENV([
++        AC_CHECK_LIB([ncursesw], [initscr], [
++          AC_DEFINE([HAVE_NCURSESW], [1])
++          have_curses=ncursesw
++          CURSES_CFLAGS=${CURSES_CFLAGS-""}
++          CURSES_LIBS=${CURSES_LIBS-"-lncursesw"}
++        ])
++      ])
++    ])
++  fi
++
++  AS_VAR_IF([have_curses], [no], [
++    PKG_CHECK_MODULES([CURSES], [ncurses], [
++      have_curses=ncurses
++    ], [
++      WITH_SAVE_ENV([
++        AC_CHECK_LIB([ncurses], [initscr], [
++          have_curses=ncurses
++          CURSES_CFLAGS=${CURSES_CFLAGS-""}
++          CURSES_LIBS=${CURSES_LIBS-"-lncurses"}
++        ])
++      ])
++    ])
++  ])
++
++])dnl ac_cv_header_ncurses_h = yes
++
++dnl remove _XOPEN_SOURCE macro from curses cflags. pyconfig.h sets
++dnl the macro to 700.
++CURSES_CFLAGS=$(echo $CURSES_CFLAGS | sed 's/-D_XOPEN_SOURCE=600//')
++
++if test "$have_curses" = no -a "$ac_sys_system" = "Darwin"; then
++  dnl On macOS, there is no separate /usr/lib/libncursesw nor libpanelw.
++  dnl If we are here, we found a locally-supplied version of libncursesw.
++  dnl There should also be a libpanelw.
++  dnl _XOPEN_SOURCE defines are usually excluded for macOS, but we need
++  dnl _XOPEN_SOURCE_EXTENDED here for ncurses wide char support.
++
++  AS_VAR_APPEND([CURSES_CFLAGS], [" -D_XOPEN_SOURCE_EXTENDED=1"])
++  AC_DEFINE([HAVE_NCURSESW], [1])
++fi
++
++dnl TODO: detect "curses" and special cases tinfo, terminfo, or termcap
++
++AC_MSG_CHECKING([curses module flags])
++AS_VAR_IF([have_curses], [no], [
++  AC_MSG_RESULT([no])  
++], [
++  AC_MSG_RESULT([$have_curses (CFLAGS: $CURSES_CFLAGS, LIBS: $CURSES_LIBS)])
++])
++
++dnl check for ncurses' panel/panelw library
++AC_CHECK_HEADERS([panel.h])
++
++AS_VAR_IF([ac_cv_header_panel_h], [yes], [
++
++  if test "$ac_sys_system" != "Darwin"; then
++    dnl On macOS, there is no separate /usr/lib/libncursesw nor libpanelw.
++    AS_VAR_IF([have_curses], [ncursesw], [
++      PKG_CHECK_MODULES([PANEL], [panelw], [
++        have_panel=panelw
++      ], [
++        WITH_SAVE_ENV([
++          AC_CHECK_LIB([panelw], [update_panels], [
++            have_panel=panelw
++            PANEL_CFLAGS=${PANEL_CFLAGS-""}
++            PANEL_LIBS=${PANEL_LIBS-"-lpanelw"}
++          ])
++        ])
++      ])
++    ])
++  fi
++
++  AS_VAR_IF([have_curses], [ncurses], [
++    PKG_CHECK_MODULES([PANEL], [panel], [
++      have_panel=panel
++    ], [
++      WITH_SAVE_ENV([
++        AC_CHECK_LIB([panel], [update_panels], [
++          have_panel=panel
++          PANEL_CFLAGS=${PANEL_CFLAGS-""}
++          PANEL_LIBS=${PANEL_LIBS-"-lpanel"}
++        ])
++      ])
++    ])
++  ])
++
++])dnl ac_cv_header_panel_h = yes
++
++AC_MSG_CHECKING([panel flags])
++AS_VAR_IF([have_panel], [no], [
++  AC_MSG_RESULT([no])  
++], [
++  AC_MSG_RESULT([$have_panel (CFLAGS: $PANEL_CFLAGS, LIBS: $PANEL_LIBS)])
++])
++
+ # first curses header check
+ ac_save_cppflags="$CPPFLAGS"
+ if test "$cross_compiling" = no; then
+   CPPFLAGS="$CPPFLAGS -I/usr/include/ncursesw"
+ fi
+ 
+-AC_CHECK_HEADERS(curses.h ncurses.h)
+-
+ # On Solaris, term.h requires curses.h
+ AC_CHECK_HEADERS(term.h,,,[
+ #ifdef HAVE_CURSES_H
+@@ -6985,8 +7137,14 @@ PY_STDLIB_MOD([_crypt],
+ PY_STDLIB_MOD([_ctypes],
+   [], [test "$have_libffi" = yes],
+   [$LIBFFI_CFLAGS], [$LIBFFI_LIBS])
+-dnl PY_STDLIB_MOD([_curses], [], [], [], [])
+-dnl PY_STDLIB_MOD([_curses_panel], [], [], [], [])
++PY_STDLIB_MOD([_curses],
++  [], [test "$have_curses" != "no"],
++  [$CURSES_CFLAGS], [$CURSES_LIBS]
++)
++PY_STDLIB_MOD([_curses_panel],
++  [], [test "$have_panel" != "no"],
++  [$PANEL_CFLAGS $CURSES_CFLAGS], [$PANEL_LIBS $CURSES_LIBS]
++)
+ PY_STDLIB_MOD([_decimal], [], [], [$LIBMPDEC_CFLAGS], [$LIBMPDEC_LDFLAGS])
+ PY_STDLIB_MOD([_dbm],
+   [test -n "$with_dbmliborder"], [test "$have_dbm" != "no"],
+@@ -6997,7 +7155,9 @@ PY_STDLIB_MOD([_gdbm],
+ PY_STDLIB_MOD([nis],
+   [], [test "$have_nis" = yes -a "$ac_cv_header_rpc_rpc_h" = yes],
+   [$LIBNSL_CFLAGS], [$LIBNSL_LIBS])
+-dnl PY_STDLIB_MOD([readline], [], [], [], [])
++ PY_STDLIB_MOD([readline],
++  [], [test "$with_readline" != "no"],
++  [$READLINE_CFLAGS], [$READLINE_LIBS])
+ PY_STDLIB_MOD([_sqlite3],
+   [test "$have_sqlite3" = "yes"],
+   [test "$have_supported_sqlite3" = "yes"],
+diff --git a/pyconfig.h.in b/pyconfig.h.in
+index b05ddd41c2bba..aa9fc559fa251 100644
+--- a/pyconfig.h.in
++++ b/pyconfig.h.in
+@@ -290,6 +290,9 @@
+ /* Defined when any dynamic module loading is enabled. */
+ #undef HAVE_DYNAMIC_LOADING
+ 
++/* Define to 1 if you have the <editline/readline.h> header file. */
++#undef HAVE_EDITLINE_READLINE_H
++
+ /* Define to 1 if you have the <endian.h> header file. */
+ #undef HAVE_ENDIAN_H
+ 
+@@ -646,9 +649,6 @@
+ /* Define to 1 if you have the <libintl.h> header file. */
+ #undef HAVE_LIBINTL_H
+ 
+-/* Define to build the readline module. */
+-#undef HAVE_LIBREADLINE
+-
+ /* Define to 1 if you have the `resolv' library (-lresolv). */
+ #undef HAVE_LIBRESOLV
+ 
+@@ -784,6 +784,9 @@
+ /* Define to 1 if you have the `nanosleep' function. */
+ #undef HAVE_NANOSLEEP
+ 
++/* Define to 1 if you have the `ncursesw' library. */
++#undef HAVE_NCURSESW
++
+ /* Define to 1 if you have the <ncurses.h> header file. */
+ #undef HAVE_NCURSES_H
+ 
+@@ -821,6 +824,9 @@
+ /* Define to 1 if you have the `openpty' function. */
+ #undef HAVE_OPENPTY
+ 
++/* Define to 1 if you have the <panel.h> header file. */
++#undef HAVE_PANEL_H
++
+ /* Define to 1 if you have the `pathconf' function. */
+ #undef HAVE_PATHCONF
+ 
+@@ -905,6 +911,9 @@
+ /* Define to 1 if you have the `pwritev2' function. */
+ #undef HAVE_PWRITEV2
+ 
++/* Define to 1 if you have the <readline/readline.h> header file. */
++#undef HAVE_READLINE_READLINE_H
++
+ /* Define to 1 if you have the `readlink' function. */
+ #undef HAVE_READLINK
+ 
+@@ -1662,7 +1671,7 @@
+    Dyld is necessary to support frameworks. */
+ #undef WITH_DYLD
+ 
+-/* Define to build the readline module against Editline. */
++/* Define to build the readline module against libedit. */
+ #undef WITH_EDITLINE
+ 
+ /* Define if you want to compile in object freelists optimization */
+diff --git a/setup.py b/setup.py
+index cc11dedee1b2e..2edcb08b4fd7f 100644
+--- a/setup.py
++++ b/setup.py
+@@ -1027,146 +1027,9 @@
+         ))
+ 
+     def detect_readline_curses(self):
+-        # readline
+-        readline_termcap_library = ""
+-        curses_library = ""
+-        # Cannot use os.popen here in py3k.
+-        tmpfile = os.path.join(self.build_temp, 'readline_termcap_lib')
+-        if not os.path.exists(self.build_temp):
+-            os.makedirs(self.build_temp)
+-        # Determine if readline is already linked against curses or tinfo.
+-        if sysconfig.get_config_var('HAVE_LIBREADLINE'):
+-            if sysconfig.get_config_var('WITH_EDITLINE'):
+-                readline_lib = 'edit'
+-            else:
+-                readline_lib = 'readline'
+-            do_readline = self.compiler.find_library_file(self.lib_dirs,
+-                readline_lib)
+-            if CROSS_COMPILING:
+-                ret = run_command("%s -d %s | grep '(NEEDED)' > %s"
+-                                % (sysconfig.get_config_var('READELF'),
+-                                   do_readline, tmpfile))
+-            elif find_executable('ldd'):
+-                ret = run_command("ldd %s > %s" % (do_readline, tmpfile))
+-            else:
+-                ret = 1
+-            if ret == 0:
+-                with open(tmpfile) as fp:
+-                    for ln in fp:
+-                        if 'curses' in ln:
+-                            readline_termcap_library = re.sub(
+-                                r'.*lib(n?cursesw?)\.so.*', r'\1', ln
+-                            ).rstrip()
+-                            break
+-                        # termcap interface split out from ncurses
+-                        if 'tinfo' in ln:
+-                            readline_termcap_library = 'tinfo'
+-                            break
+-            if os.path.exists(tmpfile):
+-                os.unlink(tmpfile)
+-        else:
+-            do_readline = False
+-        # Issue 7384: If readline is already linked against curses,
+-        # use the same library for the readline and curses modules.
+-        if 'curses' in readline_termcap_library:
+-            curses_library = readline_termcap_library
+-        elif self.compiler.find_library_file(self.lib_dirs, 'ncursesw'):
+-            curses_library = 'ncursesw'
+-        # Issue 36210: OSS provided ncurses does not link on AIX
+-        # Use IBM supplied 'curses' for successful build of _curses
+-        elif AIX and self.compiler.find_library_file(self.lib_dirs, 'curses'):
+-            curses_library = 'curses'
+-        elif self.compiler.find_library_file(self.lib_dirs, 'ncurses'):
+-            curses_library = 'ncurses'
+-        elif self.compiler.find_library_file(self.lib_dirs, 'curses'):
+-            curses_library = 'curses'
+-
+-        if MACOS:
+-            os_release = int(os.uname()[2].split('.')[0])
+-            dep_target = sysconfig.get_config_var('MACOSX_DEPLOYMENT_TARGET')
+-            if (dep_target and
+-                    (tuple(int(n) for n in dep_target.split('.')[0:2])
+-                        < (10, 5) ) ):
+-                os_release = 8
+-            if os_release < 9:
+-                # MacOSX 10.4 has a broken readline. Don't try to build
+-                # the readline module unless the user has installed a fixed
+-                # readline package
+-                if find_file('readline/rlconf.h', self.inc_dirs, []) is None:
+-                    do_readline = False
+-        if do_readline:
+-            readline_libs = [readline_lib]
+-            if readline_termcap_library:
+-                pass # Issue 7384: Already linked against curses or tinfo.
+-            elif curses_library:
+-                readline_libs.append(curses_library)
+-            elif self.compiler.find_library_file(self.lib_dirs, 'termcap'):
+-                readline_libs.append('termcap')
+-            self.add(Extension('readline', ['readline.c'],
+-                               libraries=readline_libs))
+-        else:
+-            self.missing.append('readline')
+-
+-        # Curses support, requiring the System V version of curses, often
+-        # provided by the ncurses library.
+-        curses_defines = []
+-        curses_includes = []
+-        panel_library = 'panel'
+-        if curses_library == 'ncursesw':
+-            curses_defines.append(('HAVE_NCURSESW', '1'))
+-            if not CROSS_COMPILING:
+-                curses_includes.append('/usr/include/ncursesw')
+-            # Bug 1464056: If _curses.so links with ncursesw,
+-            # _curses_panel.so must link with panelw.
+-            panel_library = 'panelw'
+-            if MACOS:
+-                # On OS X, there is no separate /usr/lib/libncursesw nor
+-                # libpanelw.  If we are here, we found a locally-supplied
+-                # version of libncursesw.  There should also be a
+-                # libpanelw.  _XOPEN_SOURCE defines are usually excluded
+-                # for OS X but we need _XOPEN_SOURCE_EXTENDED here for
+-                # ncurses wide char support
+-                curses_defines.append(('_XOPEN_SOURCE_EXTENDED', '1'))
+-        elif MACOS and curses_library == 'ncurses':
+-            # Building with the system-suppied combined libncurses/libpanel
+-            curses_defines.append(('HAVE_NCURSESW', '1'))
+-            curses_defines.append(('_XOPEN_SOURCE_EXTENDED', '1'))
+-
+-        curses_enabled = True
+-        if curses_library.startswith('ncurses'):
+-            curses_libs = [curses_library]
+-            self.add(Extension('_curses', ['_cursesmodule.c'],
+-                               include_dirs=curses_includes,
+-                               define_macros=curses_defines,
+-                               libraries=curses_libs))
+-        elif curses_library == 'curses' and not MACOS:
+-                # OSX has an old Berkeley curses, not good enough for
+-                # the _curses module.
+-            if (self.compiler.find_library_file(self.lib_dirs, 'terminfo')):
+-                curses_libs = ['curses', 'terminfo']
+-            elif (self.compiler.find_library_file(self.lib_dirs, 'termcap')):
+-                curses_libs = ['curses', 'termcap']
+-            else:
+-                curses_libs = ['curses']
+-
+-            self.add(Extension('_curses', ['_cursesmodule.c'],
+-                               define_macros=curses_defines,
+-                               libraries=curses_libs))
+-        else:
+-            curses_enabled = False
+-            self.missing.append('_curses')
+-
+-        # If the curses module is enabled, check for the panel module
+-        # _curses_panel needs some form of ncurses
+-        skip_curses_panel = True if AIX else False
+-        if (curses_enabled and not skip_curses_panel and
+-                self.compiler.find_library_file(self.lib_dirs, panel_library)):
+-            self.add(Extension('_curses_panel', ['_curses_panel.c'],
+-                           include_dirs=curses_includes,
+-                           define_macros=curses_defines,
+-                           libraries=[panel_library, *curses_libs]))
+-        elif not skip_curses_panel:
+-            self.missing.append('_curses_panel')
++        self.addext(Extension('readline', ['readline.c']))
++        self.addext(Extension('_curses', ['_cursesmodule.c']))
++        self.addext(Extension('_curses_panel', ['_curses_panel.c']))
+ 
+     def detect_crypt(self):
+         self.addext(Extension('_crypt', ['_cryptmodule.c']))
diff --git a/package/python3/0024-Add-an-option-to-disable-the-readline-module.patch b/package/python3/0024-Add-an-option-to-disable-the-readline-module.patch
deleted file mode 100644
index 9250007dae..0000000000
--- a/package/python3/0024-Add-an-option-to-disable-the-readline-module.patch
+++ /dev/null
@@ -1,30 +0,0 @@
-From 9082468ca620db77b670ccf568a96bbabb865f80 Mon Sep 17 00:00:00 2001
-From: Thomas Petazzoni <thomas.petazzoni@free-electrons.com>
-Date: Tue, 7 Mar 2017 23:29:05 +0100
-Subject: [PATCH] Add an option to disable the readline module
-
-Signed-off-by: Thomas Petazzoni <thomas.petazzoni@free-electrons.com>
----
- configure.ac | 6 ++++++
- 1 file changed, 6 insertions(+)
-
-diff --git a/configure.ac b/configure.ac
-index 42fe6c8f5a..19875d7d30 100644
---- a/configure.ac
-+++ b/configure.ac
-@@ -4225,6 +4225,12 @@ AC_ARG_ENABLE(openssl,
- 	     DISABLED_EXTENSIONS="${DISABLED_EXTENSIONS} ssl _ssl _hashlib"
- 	  fi])
- 
-+AC_ARG_ENABLE(readline,
-+	AS_HELP_STRING([--disable-readline], [disable readline]),
-+	[ if test "$enableval" = "no"; then
-+	     DISABLED_EXTENSIONS="${DISABLED_EXTENSIONS} readline"
-+	  fi])
-+
- AC_SUBST(TK)
- AC_ARG_ENABLE(tk,
- 	AS_HELP_STRING([--disable-tk], [disable tk]),
--- 
-2.34.1
-
diff --git a/package/python3/python3.mk b/package/python3/python3.mk
index 555afdab4f..3843131662 100644
--- a/package/python3/python3.mk
+++ b/package/python3/python3.mk
@@ -22,7 +22,6 @@ HOST_PYTHON3_CONF_OPTS += \
 	--disable-sqlite3 \
 	--disable-tk \
 	--with-expat=system \
-	--disable-curses \
 	--disable-codecs-cjk \
 	--disable-nis \
 	--enable-unicodedata \
@@ -37,6 +36,8 @@ HOST_PYTHON3_CONF_OPTS += \
 # communicate over the network during the build.
 HOST_PYTHON3_CONF_ENV += \
 	LDFLAGS="$(HOST_LDFLAGS) -Wl,--enable-new-dtags" \
+	py_cv_module__curses=n/a \
+	py_cv_module__curses_panel=n/a \
 	py_cv_module__uuid=n/a \
 	ac_cv_prog_HAS_HG=/bin/false
 
@@ -76,15 +77,18 @@ PYTHON3_CONF_ENV += py_cv_module__dbm=n/a
 endif
 
 ifeq ($(BR2_PACKAGE_PYTHON3_READLINE),y)
+PYTHON3_CONF_OPTS += --with-readline
 PYTHON3_DEPENDENCIES += readline
 else
-PYTHON3_CONF_OPTS += --disable-readline
+PYTHON3_CONF_OPTS += --without-readline
 endif
 
 ifeq ($(BR2_PACKAGE_PYTHON3_CURSES),y)
 PYTHON3_DEPENDENCIES += ncurses
 else
-PYTHON3_CONF_OPTS += --disable-curses
+PYTHON3_CONF_ENV += \
+	py_cv_module__curses=n/a \
+	py_cv_module__curses_panel=n/a
 endif
 
 ifeq ($(BR2_PACKAGE_PYTHON3_DECIMAL),y)
-- 
2.41.0

_______________________________________________
buildroot mailing list
buildroot@buildroot.org
https://lists.buildroot.org/mailman/listinfo/buildroot

^ permalink raw reply related	[flat|nested] 54+ messages in thread

* [Buildroot] [PATCH 05/30] package/python3: use upstream build system to disable ssl module
  2023-10-26  9:26 [Buildroot] [PATCH 00/30] package/python3: bump version to 3.12.0 Adam Duskett
                   ` (3 preceding siblings ...)
  2023-10-26  9:26 ` [Buildroot] [PATCH 04/30] package/python3: use upstream build system to disable curses/readline modules Adam Duskett
@ 2023-10-26  9:26 ` Adam Duskett
  2023-10-26  9:26 ` [Buildroot] [PATCH 06/30] package/python3: use upstream build system to disable ossaudiodev module Adam Duskett
                   ` (25 subsequent siblings)
  30 siblings, 0 replies; 54+ messages in thread
From: Adam Duskett @ 2023-10-26  9:26 UTC (permalink / raw)
  To: buildroot
  Cc: Adam Duskett, Andrey Smirnov, Bernd Kuhls, Julien Olivain,
	Asaf Kahlon, James Hilliard, Thomas Petazzoni, Mauro Condarelli

From: Bernd Kuhls <bernd@kuhls.net>

Signed-off-by: Bernd Kuhls <bernd@kuhls.net>
Signed-off-by: Adam Duskett <adam.duskett@amarulasolutions.com>
---
 .checkpackageignore                           |  1 -
 ...an-option-to-disable-openssl-support.patch | 30 -------------------
 package/python3/python3.mk                    |  8 +++--
 3 files changed, 6 insertions(+), 33 deletions(-)
 delete mode 100644 package/python3/0023-Add-an-option-to-disable-openssl-support.patch

diff --git a/.checkpackageignore b/.checkpackageignore
index ee67e9a3bf..3bf861b3f1 100644
--- a/.checkpackageignore
+++ b/.checkpackageignore
@@ -1172,7 +1172,6 @@ package/python3/0019-Add-an-option-to-disable-unicodedata.patch Upstream
 package/python3/0020-Add-an-option-to-disable-IDLE.patch Upstream
 package/python3/0021-Add-an-option-to-disable-decimal.patch Upstream
 package/python3/0022-Add-an-option-to-disable-the-ossaudiodev-module.patch Upstream
-package/python3/0023-Add-an-option-to-disable-openssl-support.patch Upstream
 package/python3/0026-python-config.sh-don-t-reassign-prefix.patch Upstream
 package/python3/0028-fix-building-on-older-distributions.patch Upstream
 package/python3/0029-configure.ac-fixup-CC-print-multiarch-output-for-mus.patch Upstream
diff --git a/package/python3/0023-Add-an-option-to-disable-openssl-support.patch b/package/python3/0023-Add-an-option-to-disable-openssl-support.patch
deleted file mode 100644
index d4c9fd43be..0000000000
--- a/package/python3/0023-Add-an-option-to-disable-openssl-support.patch
+++ /dev/null
@@ -1,30 +0,0 @@
-From 25c900e81a2fc0bbe35e7c94e2e5028cfbf6582a Mon Sep 17 00:00:00 2001
-From: Nicolas Cavallari <nicolas.cavallari@green-communications.fr>
-Date: Wed, 22 Feb 2017 17:55:59 -0800
-Subject: [PATCH] Add an option to disable openssl support.
-
-Signed-off-by: Nicolas Cavallari <nicolas.cavallari@green-communications.fr>
----
- configure.ac | 6 ++++++
- 1 file changed, 6 insertions(+)
-
-diff --git a/configure.ac b/configure.ac
-index db33d567ad..42fe6c8f5a 100644
---- a/configure.ac
-+++ b/configure.ac
-@@ -4219,6 +4219,12 @@ AC_ARG_ENABLE(unicodedata,
-     	     DISABLED_EXTENSIONS="${DISABLED_EXTENSIONS} unicodedata"
-   	  fi])
- 
-+AC_ARG_ENABLE(openssl,
-+	AS_HELP_STRING([--disable-openssl], [disable openssl support]),
-+	[ if test "$enableval" = "no"; then
-+	     DISABLED_EXTENSIONS="${DISABLED_EXTENSIONS} ssl _ssl _hashlib"
-+	  fi])
-+
- AC_SUBST(TK)
- AC_ARG_ENABLE(tk,
- 	AS_HELP_STRING([--disable-tk], [disable tk]),
--- 
-2.34.1
-
diff --git a/package/python3/python3.mk b/package/python3/python3.mk
index 3843131662..79a89ca225 100644
--- a/package/python3/python3.mk
+++ b/package/python3/python3.mk
@@ -59,7 +59,9 @@ endif
 ifeq ($(BR2_PACKAGE_HOST_PYTHON3_SSL),y)
 HOST_PYTHON3_DEPENDENCIES += host-openssl
 else
-HOST_PYTHON3_CONF_OPTS += --disable-openssl
+HOST_PYTHON3_CONF_ENV += \
+	py_cv_module__hashlib=n/a \
+	py_cv_module__ssl=n/a
 endif
 
 PYTHON3_INSTALL_STAGING = YES
@@ -115,7 +117,9 @@ ifeq ($(BR2_PACKAGE_PYTHON3_SSL),y)
 PYTHON3_DEPENDENCIES += openssl
 PYTHON3_CONF_OPTS += --with-openssl=$(STAGING_DIR)/usr
 else
-PYTHON3_CONF_OPTS += --disable-openssl
+PYTHON3_CONF_ENV += \
+	py_cv_module__hashlib=n/a \
+	py_cv_module__ssl=n/a
 endif
 
 ifneq ($(BR2_PACKAGE_PYTHON3_CODECSCJK),y)
-- 
2.41.0

_______________________________________________
buildroot mailing list
buildroot@buildroot.org
https://lists.buildroot.org/mailman/listinfo/buildroot

^ permalink raw reply related	[flat|nested] 54+ messages in thread

* [Buildroot] [PATCH 06/30] package/python3: use upstream build system to disable ossaudiodev module
  2023-10-26  9:26 [Buildroot] [PATCH 00/30] package/python3: bump version to 3.12.0 Adam Duskett
                   ` (4 preceding siblings ...)
  2023-10-26  9:26 ` [Buildroot] [PATCH 05/30] package/python3: use upstream build system to disable ssl module Adam Duskett
@ 2023-10-26  9:26 ` Adam Duskett
  2023-10-26  9:26 ` [Buildroot] [PATCH 07/30] package/python3: use upstream build system to disable unicodedata module Adam Duskett
                   ` (24 subsequent siblings)
  30 siblings, 0 replies; 54+ messages in thread
From: Adam Duskett @ 2023-10-26  9:26 UTC (permalink / raw)
  To: buildroot
  Cc: Adam Duskett, Andrey Smirnov, Bernd Kuhls, Julien Olivain,
	Asaf Kahlon, James Hilliard, Thomas Petazzoni, Mauro Condarelli

From: Bernd Kuhls <bernd@kuhls.net>

Signed-off-by: Bernd Kuhls <bernd@kuhls.net>
Signed-off-by: Adam Duskett <adam.duskett@amarulasolutions.com>
---
 .checkpackageignore                           |  1 -
 ...on-to-disable-the-ossaudiodev-module.patch | 30 -------------------
 package/python3/python3.mk                    |  6 ++--
 3 files changed, 2 insertions(+), 35 deletions(-)
 delete mode 100644 package/python3/0022-Add-an-option-to-disable-the-ossaudiodev-module.patch

diff --git a/.checkpackageignore b/.checkpackageignore
index 3bf861b3f1..9fe8a93a5a 100644
--- a/.checkpackageignore
+++ b/.checkpackageignore
@@ -1171,7 +1171,6 @@ package/python3/0018-Add-an-option-to-disable-NIS.patch Upstream
 package/python3/0019-Add-an-option-to-disable-unicodedata.patch Upstream
 package/python3/0020-Add-an-option-to-disable-IDLE.patch Upstream
 package/python3/0021-Add-an-option-to-disable-decimal.patch Upstream
-package/python3/0022-Add-an-option-to-disable-the-ossaudiodev-module.patch Upstream
 package/python3/0026-python-config.sh-don-t-reassign-prefix.patch Upstream
 package/python3/0028-fix-building-on-older-distributions.patch Upstream
 package/python3/0029-configure.ac-fixup-CC-print-multiarch-output-for-mus.patch Upstream
diff --git a/package/python3/0022-Add-an-option-to-disable-the-ossaudiodev-module.patch b/package/python3/0022-Add-an-option-to-disable-the-ossaudiodev-module.patch
deleted file mode 100644
index 56713a7f8f..0000000000
--- a/package/python3/0022-Add-an-option-to-disable-the-ossaudiodev-module.patch
+++ /dev/null
@@ -1,30 +0,0 @@
-From d48d9da534cec7891ae444b4ab94a76ac67f5daa Mon Sep 17 00:00:00 2001
-From: Thomas Petazzoni <thomas.petazzoni@free-electrons.com>
-Date: Wed, 23 Dec 2015 11:51:58 +0100
-Subject: [PATCH] Add an option to disable the ossaudiodev module
-
-Signed-off-by: Thomas Petazzoni <thomas.petazzoni@free-electrons.com>
----
- configure.ac | 6 ++++++
- 1 file changed, 6 insertions(+)
-
-diff --git a/configure.ac b/configure.ac
-index e6b1f1e9de..db33d567ad 100644
---- a/configure.ac
-+++ b/configure.ac
-@@ -3678,6 +3678,12 @@ fi
- 
- AC_MSG_RESULT($with_decimal_contextvar)
- 
-+AC_ARG_ENABLE(ossaudiodev,
-+	AS_HELP_STRING([--disable-ossaudiodev], [disable OSSAUDIODEV]),
-+	[ if test "$enableval" = "no"; then
-+	  DISABLED_EXTENSIONS="${DISABLED_EXTENSIONS} ossaudiodev"
-+	  fi])
-+
- # Check for libmpdec machine flavor
- AC_MSG_CHECKING(for decimal libmpdec machine)
- AS_CASE([$ac_sys_system],
--- 
-2.34.1
-
diff --git a/package/python3/python3.mk b/package/python3/python3.mk
index 79a89ca225..0033bec2bc 100644
--- a/package/python3/python3.mk
+++ b/package/python3/python3.mk
@@ -153,10 +153,8 @@ else
 PYTHON3_CONF_ENV += py_cv_module_zlib=n/a
 endif
 
-ifeq ($(BR2_PACKAGE_PYTHON3_OSSAUDIODEV),y)
-PYTHON3_CONF_OPTS += --enable-ossaudiodev
-else
-PYTHON3_CONF_OPTS += --disable-ossaudiodev
+ifneq ($(BR2_PACKAGE_PYTHON3_OSSAUDIODEV),y)
+PYTHON3_CONF_ENV += py_cv_module_ossaudiodev=n/a
 endif
 
 # Make python believe we don't have 'hg', so that it doesn't try to
-- 
2.41.0

_______________________________________________
buildroot mailing list
buildroot@buildroot.org
https://lists.buildroot.org/mailman/listinfo/buildroot

^ permalink raw reply related	[flat|nested] 54+ messages in thread

* [Buildroot] [PATCH 07/30] package/python3: use upstream build system to disable unicodedata module
  2023-10-26  9:26 [Buildroot] [PATCH 00/30] package/python3: bump version to 3.12.0 Adam Duskett
                   ` (5 preceding siblings ...)
  2023-10-26  9:26 ` [Buildroot] [PATCH 06/30] package/python3: use upstream build system to disable ossaudiodev module Adam Duskett
@ 2023-10-26  9:26 ` Adam Duskett
  2023-10-26  9:26 ` [Buildroot] [PATCH 08/30] package/python3: use upstream build system to disable nis module Adam Duskett
                   ` (23 subsequent siblings)
  30 siblings, 0 replies; 54+ messages in thread
From: Adam Duskett @ 2023-10-26  9:26 UTC (permalink / raw)
  To: buildroot
  Cc: Adam Duskett, Andrey Smirnov, Bernd Kuhls, Julien Olivain,
	Asaf Kahlon, James Hilliard, Thomas Petazzoni, Mauro Condarelli

From: Bernd Kuhls <bernd@kuhls.net>

Signed-off-by: Bernd Kuhls <bernd@kuhls.net>
Signed-off-by: Adam Duskett <adam.duskett@amarulasolutions.com>
---
 .checkpackageignore                           |  1 -
 ...Add-an-option-to-disable-unicodedata.patch | 30 -------------------
 package/python3/python3.mk                    |  4 +--
 3 files changed, 2 insertions(+), 33 deletions(-)
 delete mode 100644 package/python3/0019-Add-an-option-to-disable-unicodedata.patch

diff --git a/.checkpackageignore b/.checkpackageignore
index 9fe8a93a5a..448357efbd 100644
--- a/.checkpackageignore
+++ b/.checkpackageignore
@@ -1168,7 +1168,6 @@ package/python3/0014-Add-an-option-to-disable-the-tk-module.patch Upstream
 package/python3/0016-Add-an-option-to-disable-expat.patch Upstream
 package/python3/0017-Add-an-option-to-disable-CJK-codecs.patch Upstream
 package/python3/0018-Add-an-option-to-disable-NIS.patch Upstream
-package/python3/0019-Add-an-option-to-disable-unicodedata.patch Upstream
 package/python3/0020-Add-an-option-to-disable-IDLE.patch Upstream
 package/python3/0021-Add-an-option-to-disable-decimal.patch Upstream
 package/python3/0026-python-config.sh-don-t-reassign-prefix.patch Upstream
diff --git a/package/python3/0019-Add-an-option-to-disable-unicodedata.patch b/package/python3/0019-Add-an-option-to-disable-unicodedata.patch
deleted file mode 100644
index 74702b67a5..0000000000
--- a/package/python3/0019-Add-an-option-to-disable-unicodedata.patch
+++ /dev/null
@@ -1,30 +0,0 @@
-From 4fe7f375a3d171d294caebdd7b7ce49bbc9ad9f3 Mon Sep 17 00:00:00 2001
-From: Thomas Petazzoni <thomas.petazzoni@free-electrons.com>
-Date: Wed, 23 Dec 2015 11:50:27 +0100
-Subject: [PATCH] Add an option to disable unicodedata
-
-Signed-off-by: Thomas Petazzoni <thomas.petazzoni@free-electrons.com>
----
- configure.ac | 6 ++++++
- 1 file changed, 6 insertions(+)
-
-diff --git a/configure.ac b/configure.ac
-index 1bdde7f69d..ba4b0e0c1c 100644
---- a/configure.ac
-+++ b/configure.ac
-@@ -4200,6 +4200,12 @@ AC_ARG_ENABLE(nis,
-     	     DISABLED_EXTENSIONS="${DISABLED_EXTENSIONS} nis"
-   	  fi])
- 
-+AC_ARG_ENABLE(unicodedata,
-+	AS_HELP_STRING([--disable-unicodedata], [disable unicodedata]),
-+	[ if test "$enableval" = "no"; then
-+    	     DISABLED_EXTENSIONS="${DISABLED_EXTENSIONS} unicodedata"
-+  	  fi])
-+
- AC_SUBST(TK)
- AC_ARG_ENABLE(tk,
- 	AS_HELP_STRING([--disable-tk], [disable tk]),
--- 
-2.34.1
-
diff --git a/package/python3/python3.mk b/package/python3/python3.mk
index 0033bec2bc..7c0473c1b0 100644
--- a/package/python3/python3.mk
+++ b/package/python3/python3.mk
@@ -24,7 +24,6 @@ HOST_PYTHON3_CONF_OPTS += \
 	--with-expat=system \
 	--disable-codecs-cjk \
 	--disable-nis \
-	--enable-unicodedata \
 	--disable-test-modules \
 	--disable-idle3 \
 	--disable-ossaudiodev
@@ -38,6 +37,7 @@ HOST_PYTHON3_CONF_ENV += \
 	LDFLAGS="$(HOST_LDFLAGS) -Wl,--enable-new-dtags" \
 	py_cv_module__curses=n/a \
 	py_cv_module__curses_panel=n/a \
+	py_cv_module_unicodedata=yes \
 	py_cv_module__uuid=n/a \
 	ac_cv_prog_HAS_HG=/bin/false
 
@@ -127,7 +127,7 @@ PYTHON3_CONF_OPTS += --disable-codecs-cjk
 endif
 
 ifneq ($(BR2_PACKAGE_PYTHON3_UNICODEDATA),y)
-PYTHON3_CONF_OPTS += --disable-unicodedata
+PYTHON3_CONF_ENV += py_cv_module_unicodedata=n/a
 endif
 
 # Disable auto-detection of uuid.h (util-linux)
-- 
2.41.0

_______________________________________________
buildroot mailing list
buildroot@buildroot.org
https://lists.buildroot.org/mailman/listinfo/buildroot

^ permalink raw reply related	[flat|nested] 54+ messages in thread

* [Buildroot] [PATCH 08/30] package/python3: use upstream build system to disable nis module
  2023-10-26  9:26 [Buildroot] [PATCH 00/30] package/python3: bump version to 3.12.0 Adam Duskett
                   ` (6 preceding siblings ...)
  2023-10-26  9:26 ` [Buildroot] [PATCH 07/30] package/python3: use upstream build system to disable unicodedata module Adam Duskett
@ 2023-10-26  9:26 ` Adam Duskett
  2023-10-26  9:26 ` [Buildroot] [PATCH 09/30] package/python3: use upstream build system to disable decimal module Adam Duskett
                   ` (22 subsequent siblings)
  30 siblings, 0 replies; 54+ messages in thread
From: Adam Duskett @ 2023-10-26  9:26 UTC (permalink / raw)
  To: buildroot
  Cc: Adam Duskett, Andrey Smirnov, Bernd Kuhls, Julien Olivain,
	Asaf Kahlon, James Hilliard, Thomas Petazzoni, Mauro Condarelli

From: Bernd Kuhls <bernd@kuhls.net>

Signed-off-by: Bernd Kuhls <bernd@kuhls.net>
Signed-off-by: Adam Duskett <adam.duskett@amarulasolutions.com>
---
 .checkpackageignore                           |  1 -
 .../0018-Add-an-option-to-disable-NIS.patch   | 33 -------------------
 package/python3/python3.mk                    |  4 +--
 3 files changed, 2 insertions(+), 36 deletions(-)
 delete mode 100644 package/python3/0018-Add-an-option-to-disable-NIS.patch

diff --git a/.checkpackageignore b/.checkpackageignore
index 448357efbd..1be6d393c6 100644
--- a/.checkpackageignore
+++ b/.checkpackageignore
@@ -1167,7 +1167,6 @@ package/python3/0013-Add-option-to-disable-the-sqlite3-module.patch Upstream
 package/python3/0014-Add-an-option-to-disable-the-tk-module.patch Upstream
 package/python3/0016-Add-an-option-to-disable-expat.patch Upstream
 package/python3/0017-Add-an-option-to-disable-CJK-codecs.patch Upstream
-package/python3/0018-Add-an-option-to-disable-NIS.patch Upstream
 package/python3/0020-Add-an-option-to-disable-IDLE.patch Upstream
 package/python3/0021-Add-an-option-to-disable-decimal.patch Upstream
 package/python3/0026-python-config.sh-don-t-reassign-prefix.patch Upstream
diff --git a/package/python3/0018-Add-an-option-to-disable-NIS.patch b/package/python3/0018-Add-an-option-to-disable-NIS.patch
deleted file mode 100644
index 977955d1cb..0000000000
--- a/package/python3/0018-Add-an-option-to-disable-NIS.patch
+++ /dev/null
@@ -1,33 +0,0 @@
-From 09fc9f72ebe60bb65e80732a6bd4f12a84159f6d Mon Sep 17 00:00:00 2001
-From: Thomas Petazzoni <thomas.petazzoni@free-electrons.com>
-Date: Wed, 23 Dec 2015 11:50:11 +0100
-Subject: [PATCH] Add an option to disable NIS
-
-NIS is not necessarily available in uClibc, so we need an option to
-not compile support for it.
-
-Signed-off-by: Thomas Petazzoni <thomas.petazzoni@free-electrons.com>
----
- configure.ac | 6 ++++++
- 1 file changed, 6 insertions(+)
-
-diff --git a/configure.ac b/configure.ac
-index ecdd7dbc07..1bdde7f69d 100644
---- a/configure.ac
-+++ b/configure.ac
-@@ -4194,6 +4194,12 @@ AC_ARG_ENABLE(codecs-cjk,
- 		DISABLED_EXTENSIONS="${DISABLED_EXTENSIONS} _codecs_kr _codecs_jp _codecs_cn _codecs_tw _codecs_hk _codecs_iso2022"
- 	fi])
- 
-+AC_ARG_ENABLE(nis,
-+	AS_HELP_STRING([--disable-nis], [disable NIS]),
-+	[ if test "$enableval" = "no"; then
-+    	     DISABLED_EXTENSIONS="${DISABLED_EXTENSIONS} nis"
-+  	  fi])
-+
- AC_SUBST(TK)
- AC_ARG_ENABLE(tk,
- 	AS_HELP_STRING([--disable-tk], [disable tk]),
--- 
-2.34.1
-
diff --git a/package/python3/python3.mk b/package/python3/python3.mk
index 7c0473c1b0..7518edf104 100644
--- a/package/python3/python3.mk
+++ b/package/python3/python3.mk
@@ -23,7 +23,6 @@ HOST_PYTHON3_CONF_OPTS += \
 	--disable-tk \
 	--with-expat=system \
 	--disable-codecs-cjk \
-	--disable-nis \
 	--disable-test-modules \
 	--disable-idle3 \
 	--disable-ossaudiodev
@@ -37,6 +36,7 @@ HOST_PYTHON3_CONF_ENV += \
 	LDFLAGS="$(HOST_LDFLAGS) -Wl,--enable-new-dtags" \
 	py_cv_module__curses=n/a \
 	py_cv_module__curses_panel=n/a \
+	py_cv_module_nis=n/a \
 	py_cv_module_unicodedata=yes \
 	py_cv_module__uuid=n/a \
 	ac_cv_prog_HAS_HG=/bin/false
@@ -164,6 +164,7 @@ PYTHON3_CONF_ENV += \
 	ac_cv_file__dev_ptmx=yes \
 	ac_cv_file__dev_ptc=yes \
 	ac_cv_working_tzset=yes \
+	py_cv_module_nis=n/a \
 	ac_cv_prog_HAS_HG=/bin/false
 
 # GCC is always compliant with IEEE754
@@ -191,7 +192,6 @@ PYTHON3_CONF_OPTS += \
 	--disable-pydoc \
 	--disable-test-modules \
 	--disable-tk \
-	--disable-nis \
 	--disable-idle3 \
 	--disable-pyc-build
 
-- 
2.41.0

_______________________________________________
buildroot mailing list
buildroot@buildroot.org
https://lists.buildroot.org/mailman/listinfo/buildroot

^ permalink raw reply related	[flat|nested] 54+ messages in thread

* [Buildroot] [PATCH 09/30] package/python3: use upstream build system to disable decimal module
  2023-10-26  9:26 [Buildroot] [PATCH 00/30] package/python3: bump version to 3.12.0 Adam Duskett
                   ` (7 preceding siblings ...)
  2023-10-26  9:26 ` [Buildroot] [PATCH 08/30] package/python3: use upstream build system to disable nis module Adam Duskett
@ 2023-10-26  9:26 ` Adam Duskett
  2023-10-26  9:26 ` [Buildroot] [PATCH 10/30] package/python3: use upstream build system to disable CJK codecs Adam Duskett
                   ` (21 subsequent siblings)
  30 siblings, 0 replies; 54+ messages in thread
From: Adam Duskett @ 2023-10-26  9:26 UTC (permalink / raw)
  To: buildroot
  Cc: Adam Duskett, Andrey Smirnov, Bernd Kuhls, Julien Olivain,
	Asaf Kahlon, James Hilliard, Thomas Petazzoni, Mauro Condarelli

From: Bernd Kuhls <bernd@kuhls.net>

Signed-off-by: Bernd Kuhls <bernd@kuhls.net>
Signed-off-by: Adam Duskett <adam.duskett@amarulasolutions.com>
---
 .checkpackageignore                           |  1 -
 ...021-Add-an-option-to-disable-decimal.patch | 54 -------------------
 package/python3/python3.mk                    |  4 +-
 3 files changed, 2 insertions(+), 57 deletions(-)
 delete mode 100644 package/python3/0021-Add-an-option-to-disable-decimal.patch

diff --git a/.checkpackageignore b/.checkpackageignore
index 1be6d393c6..8778c52ae2 100644
--- a/.checkpackageignore
+++ b/.checkpackageignore
@@ -1168,7 +1168,6 @@ package/python3/0014-Add-an-option-to-disable-the-tk-module.patch Upstream
 package/python3/0016-Add-an-option-to-disable-expat.patch Upstream
 package/python3/0017-Add-an-option-to-disable-CJK-codecs.patch Upstream
 package/python3/0020-Add-an-option-to-disable-IDLE.patch Upstream
-package/python3/0021-Add-an-option-to-disable-decimal.patch Upstream
 package/python3/0026-python-config.sh-don-t-reassign-prefix.patch Upstream
 package/python3/0028-fix-building-on-older-distributions.patch Upstream
 package/python3/0029-configure.ac-fixup-CC-print-multiarch-output-for-mus.patch Upstream
diff --git a/package/python3/0021-Add-an-option-to-disable-decimal.patch b/package/python3/0021-Add-an-option-to-disable-decimal.patch
deleted file mode 100644
index d683565f94..0000000000
--- a/package/python3/0021-Add-an-option-to-disable-decimal.patch
+++ /dev/null
@@ -1,54 +0,0 @@
-From 7091fdf77f612425c178a75148560f9c3514e8b8 Mon Sep 17 00:00:00 2001
-From: Thomas Petazzoni <thomas.petazzoni@free-electrons.com>
-Date: Wed, 23 Dec 2015 11:51:31 +0100
-Subject: [PATCH] Add an option to disable decimal
-
-This patch replaces the existing --with-system-libmpdec option with a
---with-libmpdec={system,builtin,none} option, which allows to tell
-Python whether we want to use the system libmpdec (already installed),
-the libmpdec builtin the Python sources, or no libmpdec at all.
-
-Signed-off-by: Thomas Petazzoni <thomas.petazzoni@free-electrons.com>
-[aduskett@gmail.com: Update for python 3.7.0]
-Signed-off-by: Adam Duskett <aduskett@gmail.com>
-[james.hilliard1@gmail.com: adapt to python 3.9]
-Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
----
- configure.ac | 19 +++++++++++++------
- 1 file changed, 13 insertions(+), 6 deletions(-)
-
-diff --git a/configure.ac b/configure.ac
-index 5e6d72f7db..e6b1f1e9de 100644
---- a/configure.ac
-+++ b/configure.ac
-@@ -3630,14 +3630,21 @@ fi
- AC_SUBST(LIBFFI_INCLUDEDIR)
- 
- # Check for use of the system libmpdec library
--AC_MSG_CHECKING(for --with-system-libmpdec)
--AC_ARG_WITH(system_libmpdec,
--            AS_HELP_STRING([--with-system-libmpdec], [build _decimal module using an installed libmpdec library, see Doc/library/decimal.rst (default is no)]),
-+AC_MSG_CHECKING(for --with-libmpdec)
-+AC_ARG_WITH(libmpdec,
-+            AS_HELP_STRING([--with-libmpdec], [select which libmpdec version to use: system, builtin, none]),
-             [],
--            [with_system_libmpdec="no"])
--AC_MSG_RESULT($with_system_libmpdec)
-+            [with_libmpdec="builtin"])
-+AC_MSG_RESULT($with_libmpdec)
-+if test "$with_libmpdec" != "none"; then
-+   MPDEC=yes
-+else
-+   DISABLED_EXTENSIONS="${DISABLED_EXTENSIONS} _decimal"
-+   MPDEC=no
-+fi
-+AC_SUBST(MPDEC)
- 
--AS_VAR_IF([with_system_libmpdec], [yes], [
-+AS_VAR_IF([with_libmpdec], [system], [
-   LIBMPDEC_CFLAGS=${LIBMPDEC_CFLAGS-""}
-   LIBMPDEC_LDFLAGS=${LIBMPDEC_LDFLAGS-"-lmpdec"}
-   LIBMPDEC_INTERNAL=
--- 
-2.34.1
-
diff --git a/package/python3/python3.mk b/package/python3/python3.mk
index 7518edf104..284e059c63 100644
--- a/package/python3/python3.mk
+++ b/package/python3/python3.mk
@@ -95,9 +95,9 @@ endif
 
 ifeq ($(BR2_PACKAGE_PYTHON3_DECIMAL),y)
 PYTHON3_DEPENDENCIES += mpdecimal
-PYTHON3_CONF_OPTS += --with-libmpdec=system
+PYTHON3_CONF_OPTS += --with-system-libmpdec
 else
-PYTHON3_CONF_OPTS += --with-libmpdec=none
+PYTHON3_CONF_ENV += py_cv_module__decimal=n/a
 endif
 
 ifeq ($(BR2_PACKAGE_PYTHON3_PYEXPAT),y)
-- 
2.41.0

_______________________________________________
buildroot mailing list
buildroot@buildroot.org
https://lists.buildroot.org/mailman/listinfo/buildroot

^ permalink raw reply related	[flat|nested] 54+ messages in thread

* [Buildroot] [PATCH 10/30] package/python3: use upstream build system to disable CJK codecs
  2023-10-26  9:26 [Buildroot] [PATCH 00/30] package/python3: bump version to 3.12.0 Adam Duskett
                   ` (8 preceding siblings ...)
  2023-10-26  9:26 ` [Buildroot] [PATCH 09/30] package/python3: use upstream build system to disable decimal module Adam Duskett
@ 2023-10-26  9:26 ` Adam Duskett
  2023-10-26  9:26 ` [Buildroot] [PATCH 11/30] package/python3: use upstream build system to disable pyexpat module Adam Duskett
                   ` (20 subsequent siblings)
  30 siblings, 0 replies; 54+ messages in thread
From: Adam Duskett @ 2023-10-26  9:26 UTC (permalink / raw)
  To: buildroot
  Cc: Adam Duskett, Andrey Smirnov, Bernd Kuhls, Julien Olivain,
	Asaf Kahlon, James Hilliard, Thomas Petazzoni, Mauro Condarelli

From: Bernd Kuhls <bernd@kuhls.net>

Signed-off-by: Bernd Kuhls <bernd@kuhls.net>
Signed-off-by: Adam Duskett <adam.duskett@amarulasolutions.com>
---
 .checkpackageignore                           |  1 -
 ...-Add-an-option-to-disable-CJK-codecs.patch | 30 -------------------
 package/python3/python3.mk                    | 15 ++++++++--
 3 files changed, 13 insertions(+), 33 deletions(-)
 delete mode 100644 package/python3/0017-Add-an-option-to-disable-CJK-codecs.patch

diff --git a/.checkpackageignore b/.checkpackageignore
index 8778c52ae2..da17dd32f0 100644
--- a/.checkpackageignore
+++ b/.checkpackageignore
@@ -1166,7 +1166,6 @@ package/python3/0012-Add-an-option-to-disable-lib2to3.patch Upstream
 package/python3/0013-Add-option-to-disable-the-sqlite3-module.patch Upstream
 package/python3/0014-Add-an-option-to-disable-the-tk-module.patch Upstream
 package/python3/0016-Add-an-option-to-disable-expat.patch Upstream
-package/python3/0017-Add-an-option-to-disable-CJK-codecs.patch Upstream
 package/python3/0020-Add-an-option-to-disable-IDLE.patch Upstream
 package/python3/0026-python-config.sh-don-t-reassign-prefix.patch Upstream
 package/python3/0028-fix-building-on-older-distributions.patch Upstream
diff --git a/package/python3/0017-Add-an-option-to-disable-CJK-codecs.patch b/package/python3/0017-Add-an-option-to-disable-CJK-codecs.patch
deleted file mode 100644
index 63a07fb514..0000000000
--- a/package/python3/0017-Add-an-option-to-disable-CJK-codecs.patch
+++ /dev/null
@@ -1,30 +0,0 @@
-From d8ef6a7a9f2b954cf1c1e37fc3c35055b42af0f5 Mon Sep 17 00:00:00 2001
-From: Thomas Petazzoni <thomas.petazzoni@free-electrons.com>
-Date: Wed, 23 Dec 2015 11:49:55 +0100
-Subject: [PATCH] Add an option to disable CJK codecs
-
-Signed-off-by: Thomas Petazzoni <thomas.petazzoni@free-electrons.com>
----
- configure.ac | 6 ++++++
- 1 file changed, 6 insertions(+)
-
-diff --git a/configure.ac b/configure.ac
-index 201cad0bfc..ecdd7dbc07 100644
---- a/configure.ac
-+++ b/configure.ac
-@@ -4188,6 +4188,12 @@ if test "$SQLITE3" = "no" ; then
-    DISABLED_EXTENSIONS="${DISABLED_EXTENSIONS} _sqlite3"
- fi
- 
-+AC_ARG_ENABLE(codecs-cjk,
-+	AS_HELP_STRING([--disable-codecs-cjk], [disable CJK codecs]),
-+	[ if test "$enableval" = "no"; then
-+		DISABLED_EXTENSIONS="${DISABLED_EXTENSIONS} _codecs_kr _codecs_jp _codecs_cn _codecs_tw _codecs_hk _codecs_iso2022"
-+	fi])
-+
- AC_SUBST(TK)
- AC_ARG_ENABLE(tk,
- 	AS_HELP_STRING([--disable-tk], [disable tk]),
--- 
-2.34.1
-
diff --git a/package/python3/python3.mk b/package/python3/python3.mk
index 284e059c63..e4b97e7d62 100644
--- a/package/python3/python3.mk
+++ b/package/python3/python3.mk
@@ -22,7 +22,6 @@ HOST_PYTHON3_CONF_OPTS += \
 	--disable-sqlite3 \
 	--disable-tk \
 	--with-expat=system \
-	--disable-codecs-cjk \
 	--disable-test-modules \
 	--disable-idle3 \
 	--disable-ossaudiodev
@@ -38,6 +37,12 @@ HOST_PYTHON3_CONF_ENV += \
 	py_cv_module__curses_panel=n/a \
 	py_cv_module_nis=n/a \
 	py_cv_module_unicodedata=yes \
+	py_cv_module__codecs_cn=n/a \
+	py_cv_module__codecs_hk=n/a \
+	py_cv_module__codecs_iso2022=n/a \
+	py_cv_module__codecs_jp=n/a \
+	py_cv_module__codecs_kr=n/a \
+	py_cv_module__codecs_tw=n/a \
 	py_cv_module__uuid=n/a \
 	ac_cv_prog_HAS_HG=/bin/false
 
@@ -123,7 +128,13 @@ PYTHON3_CONF_ENV += \
 endif
 
 ifneq ($(BR2_PACKAGE_PYTHON3_CODECSCJK),y)
-PYTHON3_CONF_OPTS += --disable-codecs-cjk
+PYTHON3_CONF_ENV += \
+	py_cv_module__codecs_cn=n/a \
+	py_cv_module__codecs_hk=n/a \
+	py_cv_module__codecs_iso2022=n/a \
+	py_cv_module__codecs_jp=n/a \
+	py_cv_module__codecs_kr=n/a \
+	py_cv_module__codecs_tw=n/a
 endif
 
 ifneq ($(BR2_PACKAGE_PYTHON3_UNICODEDATA),y)
-- 
2.41.0

_______________________________________________
buildroot mailing list
buildroot@buildroot.org
https://lists.buildroot.org/mailman/listinfo/buildroot

^ permalink raw reply related	[flat|nested] 54+ messages in thread

* [Buildroot] [PATCH 11/30] package/python3: use upstream build system to disable pyexpat module
  2023-10-26  9:26 [Buildroot] [PATCH 00/30] package/python3: bump version to 3.12.0 Adam Duskett
                   ` (9 preceding siblings ...)
  2023-10-26  9:26 ` [Buildroot] [PATCH 10/30] package/python3: use upstream build system to disable CJK codecs Adam Duskett
@ 2023-10-26  9:26 ` Adam Duskett
  2023-10-26  9:26 ` [Buildroot] [PATCH 12/30] package/python3: use upstream build system to disable sqlite3 module Adam Duskett
                   ` (19 subsequent siblings)
  30 siblings, 0 replies; 54+ messages in thread
From: Adam Duskett @ 2023-10-26  9:26 UTC (permalink / raw)
  To: buildroot
  Cc: Adam Duskett, Andrey Smirnov, Bernd Kuhls, Julien Olivain,
	Asaf Kahlon, James Hilliard, Thomas Petazzoni, Mauro Condarelli

From: Bernd Kuhls <bernd@kuhls.net>

Signed-off-by: Bernd Kuhls <bernd@kuhls.net>
Signed-off-by: Adam Duskett <adam.duskett@amarulasolutions.com>
---
 .checkpackageignore                           |  1 -
 .../0016-Add-an-option-to-disable-expat.patch | 82 -------------------
 package/python3/python3.mk                    |  6 +-
 3 files changed, 3 insertions(+), 86 deletions(-)
 delete mode 100644 package/python3/0016-Add-an-option-to-disable-expat.patch

diff --git a/.checkpackageignore b/.checkpackageignore
index da17dd32f0..51914e5d8c 100644
--- a/.checkpackageignore
+++ b/.checkpackageignore
@@ -1165,7 +1165,6 @@ package/python3/0011-Add-an-option-to-disable-pydoc.patch Upstream
 package/python3/0012-Add-an-option-to-disable-lib2to3.patch Upstream
 package/python3/0013-Add-option-to-disable-the-sqlite3-module.patch Upstream
 package/python3/0014-Add-an-option-to-disable-the-tk-module.patch Upstream
-package/python3/0016-Add-an-option-to-disable-expat.patch Upstream
 package/python3/0020-Add-an-option-to-disable-IDLE.patch Upstream
 package/python3/0026-python-config.sh-don-t-reassign-prefix.patch Upstream
 package/python3/0028-fix-building-on-older-distributions.patch Upstream
diff --git a/package/python3/0016-Add-an-option-to-disable-expat.patch b/package/python3/0016-Add-an-option-to-disable-expat.patch
deleted file mode 100644
index c30740762a..0000000000
--- a/package/python3/0016-Add-an-option-to-disable-expat.patch
+++ /dev/null
@@ -1,82 +0,0 @@
-From c9a2ea3edacf57746517600ccc11c254a9fd6c48 Mon Sep 17 00:00:00 2001
-From: Thomas Petazzoni <thomas.petazzoni@free-electrons.com>
-Date: Wed, 22 Feb 2017 17:40:45 -0800
-Subject: [PATCH] Add an option to disable expat
-
-This patch replaces the existing --with-system-expat option with a
---with-expat={system,builtin,none} option, which allows to tell Python
-whether we want to use the system expat (already installed), the expat
-builtin the Python sources, or no expat at all (which disables the
-installation of XML modules).
-
-Signed-off-by: Thomas Petazzoni <thomas.petazzoni@free-electrons.com>
-Signed-off-by: Samuel Martin <s.martin49@gmail.com>
-[ Andrey Smirnov: ported to Python 3.6 ]
-Signed-off-by: Andrey Smirnov <andrew.smirnov@gmail.com>
-[ Adam Duskett: ported to Python 3.10.0 ]
-Signed-off-by: Adam Duskett <aduskett@gmail.com>
----
- Makefile.pre.in |  5 ++++-
- configure.ac    | 20 ++++++++++++++------
- 2 files changed, 18 insertions(+), 7 deletions(-)
-
-diff --git a/Makefile.pre.in b/Makefile.pre.in
-index 8e879b35c6..80d617cf7f 100644
---- a/Makefile.pre.in
-+++ b/Makefile.pre.in
-@@ -1925,7 +1925,6 @@ LIBSUBDIRS=	asyncio \
- 		urllib \
- 		venv venv/scripts venv/scripts/common venv/scripts/posix \
- 		wsgiref \
--		$(XMLLIBSUBDIRS) \
- 		xmlrpc \
- 		zoneinfo \
- 		__phello__
-@@ -2027,6 +2026,10 @@ ifeq (@CURSES@,yes)
- LIBSUBDIRS += curses
- endif
- 
-+ifeq (@EXPAT@,yes)
-+LIBSUBDIRS += $(XMLLIBSUBDIRS)
-+endif
-+
- TEST_MODULES=@TEST_MODULES@
- libinstall:	all $(srcdir)/Modules/xxmodule.c
- 	@for i in $(SCRIPTDIR) $(LIBDEST); \
-diff --git a/configure.ac b/configure.ac
-index 0ae9863cd6..201cad0bfc 100644
---- a/configure.ac
-+++ b/configure.ac
-@@ -3565,15 +3565,23 @@ LIBS="$withval $LIBS"
- AC_SUBST(DISABLED_EXTENSIONS)
- 
- # Check for use of the system expat library
--AC_MSG_CHECKING(for --with-system-expat)
--AC_ARG_WITH(system_expat,
--            AS_HELP_STRING([--with-system-expat], [build pyexpat module using an installed expat library, see Doc/library/pyexpat.rst (default is no)]),
-+AC_MSG_CHECKING(for --with-expat)
-+AC_ARG_WITH(expat,
-+            AS_HELP_STRING([--with-expat], [select which expat version to use: system, builtin, none]),
-             [],
--            [with_system_expat="no"])
-+            [with_expat="builtin"])
- 
--AC_MSG_RESULT($with_system_expat)
-+AC_MSG_RESULT($with_expat)
- 
--AS_VAR_IF([with_system_expat], [yes], [
-+if test "$with_expat" != "none"; then
-+   EXPAT=yes
-+else
-+   DISABLED_EXTENSIONS="${DISABLED_EXTENSIONS} pyexpat"
-+   EXPAT=no
-+fi
-+AC_SUBST(EXPAT)
-+
-+AS_VAR_IF([with_expat], [system], [
-   LIBEXPAT_CFLAGS=${LIBEXPAT_CFLAGS-""}
-   LIBEXPAT_LDFLAGS=${LIBEXPAT_LDFLAGS-"-lexpat"}
-   LIBEXPAT_INTERNAL=
--- 
-2.34.1
-
diff --git a/package/python3/python3.mk b/package/python3/python3.mk
index e4b97e7d62..956837db68 100644
--- a/package/python3/python3.mk
+++ b/package/python3/python3.mk
@@ -21,7 +21,7 @@ HOST_PYTHON3_CONF_OPTS += \
 	--without-cxx-main \
 	--disable-sqlite3 \
 	--disable-tk \
-	--with-expat=system \
+	--with-system-expat \
 	--disable-test-modules \
 	--disable-idle3 \
 	--disable-ossaudiodev
@@ -107,9 +107,9 @@ endif
 
 ifeq ($(BR2_PACKAGE_PYTHON3_PYEXPAT),y)
 PYTHON3_DEPENDENCIES += expat
-PYTHON3_CONF_OPTS += --with-expat=system
+PYTHON3_CONF_OPTS += --with-system-expat
 else
-PYTHON3_CONF_OPTS += --with-expat=none
+PYTHON3_CONF_ENV += py_cv_module_pyexpat=n/a
 endif
 
 ifeq ($(BR2_PACKAGE_PYTHON3_SQLITE),y)
-- 
2.41.0

_______________________________________________
buildroot mailing list
buildroot@buildroot.org
https://lists.buildroot.org/mailman/listinfo/buildroot

^ permalink raw reply related	[flat|nested] 54+ messages in thread

* [Buildroot] [PATCH 12/30] package/python3: use upstream build system to disable sqlite3 module
  2023-10-26  9:26 [Buildroot] [PATCH 00/30] package/python3: bump version to 3.12.0 Adam Duskett
                   ` (10 preceding siblings ...)
  2023-10-26  9:26 ` [Buildroot] [PATCH 11/30] package/python3: use upstream build system to disable pyexpat module Adam Duskett
@ 2023-10-26  9:26 ` Adam Duskett
  2023-10-26  9:26 ` [Buildroot] [PATCH 13/30] package/python3: update patch and partly use upstream build system to disable tk module Adam Duskett
                   ` (18 subsequent siblings)
  30 siblings, 0 replies; 54+ messages in thread
From: Adam Duskett @ 2023-10-26  9:26 UTC (permalink / raw)
  To: buildroot
  Cc: Adam Duskett, Andrey Smirnov, Bernd Kuhls, Julien Olivain,
	Asaf Kahlon, James Hilliard, Thomas Petazzoni, Mauro Condarelli

From: Bernd Kuhls <bernd@kuhls.net>

Signed-off-by: Bernd Kuhls <bernd@kuhls.net>
Signed-off-by: Adam Duskett <adam.duskett@amarulasolutions.com>
---
 .checkpackageignore                           |  1 -
 ...option-to-disable-the-sqlite3-module.patch | 62 -------------------
 package/python3/python3.mk                    |  4 +-
 3 files changed, 2 insertions(+), 65 deletions(-)
 delete mode 100644 package/python3/0013-Add-option-to-disable-the-sqlite3-module.patch

diff --git a/.checkpackageignore b/.checkpackageignore
index 51914e5d8c..4c35220d42 100644
--- a/.checkpackageignore
+++ b/.checkpackageignore
@@ -1163,7 +1163,6 @@ package/python3/0009-Do-not-adjust-the-shebang-of-Python-scripts-for-cros.patch
 package/python3/0010-Misc-python-config.sh.in-ensure-sed-invocations-only.patch Upstream
 package/python3/0011-Add-an-option-to-disable-pydoc.patch Upstream
 package/python3/0012-Add-an-option-to-disable-lib2to3.patch Upstream
-package/python3/0013-Add-option-to-disable-the-sqlite3-module.patch Upstream
 package/python3/0014-Add-an-option-to-disable-the-tk-module.patch Upstream
 package/python3/0020-Add-an-option-to-disable-IDLE.patch Upstream
 package/python3/0026-python-config.sh-don-t-reassign-prefix.patch Upstream
diff --git a/package/python3/0013-Add-option-to-disable-the-sqlite3-module.patch b/package/python3/0013-Add-option-to-disable-the-sqlite3-module.patch
deleted file mode 100644
index 795ea3b898..0000000000
--- a/package/python3/0013-Add-option-to-disable-the-sqlite3-module.patch
+++ /dev/null
@@ -1,62 +0,0 @@
-From ef8c030e01b1be8be582e90c31298a5863094858 Mon Sep 17 00:00:00 2001
-From: Thomas Petazzoni <thomas.petazzoni@free-electrons.com>
-Date: Wed, 22 Feb 2017 17:20:45 -0800
-Subject: [PATCH] Add option to disable the sqlite3 module
-
-Signed-off-by: Thomas Petazzoni <thomas.petazzoni@free-electrons.com>
-Signed-off-by: Samuel Martin <s.martin49@gmail.com>
-[ Andrey Smirnov: ported to Python 3.6 ]
-Signed-off-by: Andrey Smirnov <andrew.smirnov@gmail.com>
-[ Adam Duskett: ported to Python 3.10.0 ]
-Signed-off-by: Adam Duskett <aduskett@gmail.com>
----
- Makefile.pre.in | 5 ++++-
- configure.ac    | 9 +++++++++
- 2 files changed, 13 insertions(+), 1 deletion(-)
-
-diff --git a/Makefile.pre.in b/Makefile.pre.in
-index f5d0573067..9f4cdf14cf 100644
---- a/Makefile.pre.in
-+++ b/Makefile.pre.in
-@@ -1920,7 +1920,6 @@ LIBSUBDIRS=	asyncio \
- 		multiprocessing multiprocessing/dummy \
- 		re \
- 		site-packages \
--		sqlite3 \
- 		tkinter \
- 		tomllib \
- 		turtledemo \
-@@ -2018,6 +2017,10 @@ TESTSUBDIRS += lib2to3/tests			\
- 	lib2to3/tests/data/fixers/myfixes
- endif
- 
-+ifeq (@SQLITE3@,yes)
-+LIBSUBDIRS += sqlite3
-+endif
-+
- TEST_MODULES=@TEST_MODULES@
- libinstall:	all $(srcdir)/Modules/xxmodule.c
- 	@for i in $(SCRIPTDIR) $(LIBDEST); \
-diff --git a/configure.ac b/configure.ac
-index d8e10cf2b2..4cc0951ab9 100644
---- a/configure.ac
-+++ b/configure.ac
-@@ -4171,6 +4171,15 @@ AS_VAR_IF([posix_threads], [stub], [
-   AC_DEFINE([HAVE_PTHREAD_STUBS], [1], [Define if platform requires stubbed pthreads support])
- ])
- 
-+AC_SUBST(SQLITE3)
-+AC_ARG_ENABLE(sqlite3,
-+	AS_HELP_STRING([--disable-sqlite3], [disable sqlite3]),
-+	[ SQLITE3="${enableval}" ], [ SQLITE3=yes ])
-+
-+if test "$SQLITE3" = "no" ; then
-+   DISABLED_EXTENSIONS="${DISABLED_EXTENSIONS} _sqlite3"
-+fi
-+
- AC_SUBST(PYDOC)
- 
- AC_ARG_ENABLE(pydoc,
--- 
-2.34.1
-
diff --git a/package/python3/python3.mk b/package/python3/python3.mk
index 956837db68..8b06c28fde 100644
--- a/package/python3/python3.mk
+++ b/package/python3/python3.mk
@@ -19,7 +19,6 @@ PYTHON3_CPE_ID_PRODUCT = python
 HOST_PYTHON3_CONF_OPTS += \
 	--without-ensurepip \
 	--without-cxx-main \
-	--disable-sqlite3 \
 	--disable-tk \
 	--with-system-expat \
 	--disable-test-modules \
@@ -43,6 +42,7 @@ HOST_PYTHON3_CONF_ENV += \
 	py_cv_module__codecs_jp=n/a \
 	py_cv_module__codecs_kr=n/a \
 	py_cv_module__codecs_tw=n/a \
+	py_cv_module__sqlite3=n/a \
 	py_cv_module__uuid=n/a \
 	ac_cv_prog_HAS_HG=/bin/false
 
@@ -115,7 +115,7 @@ endif
 ifeq ($(BR2_PACKAGE_PYTHON3_SQLITE),y)
 PYTHON3_DEPENDENCIES += sqlite
 else
-PYTHON3_CONF_OPTS += --disable-sqlite3
+PYTHON3_CONF_ENV += py_cv_module__sqlite3=n/a
 endif
 
 ifeq ($(BR2_PACKAGE_PYTHON3_SSL),y)
-- 
2.41.0

_______________________________________________
buildroot mailing list
buildroot@buildroot.org
https://lists.buildroot.org/mailman/listinfo/buildroot

^ permalink raw reply related	[flat|nested] 54+ messages in thread

* [Buildroot] [PATCH 13/30] package/python3: update patch and partly use upstream build system to disable tk module
  2023-10-26  9:26 [Buildroot] [PATCH 00/30] package/python3: bump version to 3.12.0 Adam Duskett
                   ` (11 preceding siblings ...)
  2023-10-26  9:26 ` [Buildroot] [PATCH 12/30] package/python3: use upstream build system to disable sqlite3 module Adam Duskett
@ 2023-10-26  9:26 ` Adam Duskett
  2023-11-04 17:47   ` Arnout Vandecappelle via buildroot
  2023-10-26  9:26 ` [Buildroot] [PATCH 14/30] package/python3: Remove infrastructure to disable the build of certain extensions Adam Duskett
                   ` (17 subsequent siblings)
  30 siblings, 1 reply; 54+ messages in thread
From: Adam Duskett @ 2023-10-26  9:26 UTC (permalink / raw)
  To: buildroot
  Cc: Adam Duskett, Andrey Smirnov, Bernd Kuhls, Julien Olivain,
	Asaf Kahlon, James Hilliard, Thomas Petazzoni, Mauro Condarelli

From: Bernd Kuhls <bernd@kuhls.net>

Signed-off-by: Bernd Kuhls <bernd@kuhls.net>
Signed-off-by: Adam Duskett <adam.duskett@amarulasolutions.com>
---
 ...d-an-option-to-disable-the-tk-module.patch | 33 +++++++------------
 package/python3/python3.mk                    |  2 ++
 2 files changed, 14 insertions(+), 21 deletions(-)

diff --git a/package/python3/0014-Add-an-option-to-disable-the-tk-module.patch b/package/python3/0014-Add-an-option-to-disable-the-tk-module.patch
index b89e1d27bc..1ed8959dba 100644
--- a/package/python3/0014-Add-an-option-to-disable-the-tk-module.patch
+++ b/package/python3/0014-Add-an-option-to-disable-the-tk-module.patch
@@ -20,15 +20,15 @@ diff --git a/Makefile.pre.in b/Makefile.pre.in
 index 9f4cdf14cf..4f83911200 100644
 --- a/Makefile.pre.in
 +++ b/Makefile.pre.in
-@@ -1920,7 +1920,6 @@ LIBSUBDIRS=	asyncio \
- 		multiprocessing multiprocessing/dummy \
+@@ -1921,7 +1921,6 @@
  		re \
  		site-packages \
+ 		sqlite3 \
 -		tkinter \
  		tomllib \
  		turtledemo \
  		unittest \
-@@ -2038,9 +2038,6 @@
+@@ -2039,12 +2038,15 @@
  		test/xmltestdata \
  		test/xmltestdata/c14n-20 \
  		test/ziptestdata \
@@ -37,10 +37,6 @@ index 9f4cdf14cf..4f83911200 100644
 -		tkinter/test/test_ttk \
  		unittest/test \
  		unittest/test/testmock
- ifeq (@PYDOC@,yes)
-@@ -2021,6 +2018,13 @@ ifeq (@SQLITE3@,yes)
- LIBSUBDIRS += sqlite3
- endif
  
 +ifeq (@TK@,yes)
 +LIBSUBDIRS += tkinter
@@ -48,30 +44,25 @@ index 9f4cdf14cf..4f83911200 100644
 +	tkinter/test/test_ttk
 +endif
 +
-+
- TEST_MODULES=@TEST_MODULES@
- libinstall:	all $(srcdir)/Modules/xxmodule.c
- 	@for i in $(SCRIPTDIR) $(LIBDEST); \
+ ifeq (@PYDOC@,yes)
+ LIBSUBDIRS += pydoc_data
+ endif
 diff --git a/configure.ac b/configure.ac
 index 4cc0951ab9..f4ce506801 100644
 --- a/configure.ac
 +++ b/configure.ac
-@@ -4180,6 +4180,15 @@ if test "$SQLITE3" = "no" ; then
-    DISABLED_EXTENSIONS="${DISABLED_EXTENSIONS} _sqlite3"
- fi
+@@ -4202,6 +4202,11 @@
+ 	AS_HELP_STRING([--disable-pydoc], [disable pydoc]),
+ 	[ PYDOC="${enableval}" ], [ PYDOC=yes ])
  
 +AC_SUBST(TK)
 +AC_ARG_ENABLE(tk,
 +	AS_HELP_STRING([--disable-tk], [disable tk]),
 +	[ TK="${enableval}" ], [ TK=yes ])
 +
-+if test "$TK" = "no"; then
-+   DISABLED_EXTENSIONS="${DISABLED_EXTENSIONS} _tkinter"
-+fi
-+
- AC_SUBST(PYDOC)
- 
- AC_ARG_ENABLE(pydoc,
+ # Check for enable-ipv6
+ AH_TEMPLATE(ENABLE_IPV6, [Define if --enable-ipv6 is specified])
+ AC_MSG_CHECKING([if --enable-ipv6 is specified])
 -- 
 2.34.1
 
diff --git a/package/python3/python3.mk b/package/python3/python3.mk
index 8b06c28fde..04105289d6 100644
--- a/package/python3/python3.mk
+++ b/package/python3/python3.mk
@@ -43,6 +43,7 @@ HOST_PYTHON3_CONF_ENV += \
 	py_cv_module__codecs_kr=n/a \
 	py_cv_module__codecs_tw=n/a \
 	py_cv_module__sqlite3=n/a \
+	py_cv_module__tkinter=n/a \
 	py_cv_module__uuid=n/a \
 	ac_cv_prog_HAS_HG=/bin/false
 
@@ -176,6 +177,7 @@ PYTHON3_CONF_ENV += \
 	ac_cv_file__dev_ptc=yes \
 	ac_cv_working_tzset=yes \
 	py_cv_module_nis=n/a \
+	py_cv_module__tkinter=n/a \
 	ac_cv_prog_HAS_HG=/bin/false
 
 # GCC is always compliant with IEEE754
-- 
2.41.0

_______________________________________________
buildroot mailing list
buildroot@buildroot.org
https://lists.buildroot.org/mailman/listinfo/buildroot

^ permalink raw reply related	[flat|nested] 54+ messages in thread

* [Buildroot] [PATCH 14/30] package/python3: Remove infrastructure to disable the build of certain extensions
  2023-10-26  9:26 [Buildroot] [PATCH 00/30] package/python3: bump version to 3.12.0 Adam Duskett
                   ` (12 preceding siblings ...)
  2023-10-26  9:26 ` [Buildroot] [PATCH 13/30] package/python3: update patch and partly use upstream build system to disable tk module Adam Duskett
@ 2023-10-26  9:26 ` Adam Duskett
  2023-10-26  9:26 ` [Buildroot] [PATCH 15/30] package/scons: bump version to 4.5.2 Adam Duskett
                   ` (16 subsequent siblings)
  30 siblings, 0 replies; 54+ messages in thread
From: Adam Duskett @ 2023-10-26  9:26 UTC (permalink / raw)
  To: buildroot
  Cc: Adam Duskett, Andrey Smirnov, Bernd Kuhls, Julien Olivain,
	Asaf Kahlon, James Hilliard, Thomas Petazzoni, Mauro Condarelli

From: Bernd Kuhls <bernd@kuhls.net>

All modules are now excluded using features of the upstream build system,
partly by backported patches from Python 3.12 branch.

Renumber remaining patches.

Signed-off-by: Bernd Kuhls <bernd@kuhls.net>
Signed-off-by: Adam Duskett <adam.duskett@amarulasolutions.com>
---
 .checkpackageignore                           |   1 -
 ...re-to-disable-the-build-of-certain-e.patch | 108 ------------------
 ...-header-paths-for-cross-compilation.patch} |   0
 ...ok-in-usr-lib-termcap-for-libraries.patch} |   0
 ...h => 0005-Don-t-add-multiarch-paths.patch} |   0
 ...> 0006-Abort-on-failed-module-build.patch} |   0
 ...tch => 0007-Serial-ioctl-workaround.patch} |   0
 ...-shebang-of-Python-scripts-for-cros.patch} |   0
 ...g.sh.in-ensure-sed-invocations-only.patch} |   0
 ...0010-Add-an-option-to-disable-pydoc.patch} |   0
 ...11-Add-an-option-to-disable-lib2to3.patch} |   0
 ... 0012-Add-an-option-to-disable-IDLE.patch} |   0
 ...hon-config.sh-don-t-reassign-prefix.patch} |   0
 ...fix-building-on-older-distributions.patch} |   0
 ...p-CC-print-multiarch-output-for-mus.patch} |   0
 ...ng-doesn-t-set-errno-when-encryptio.patch} |   0
 16 files changed, 109 deletions(-)
 delete mode 100644 package/python3/0003-Add-infrastructure-to-disable-the-build-of-certain-e.patch
 rename package/python3/{0004-Adjust-library-header-paths-for-cross-compilation.patch => 0003-Adjust-library-header-paths-for-cross-compilation.patch} (100%)
 rename package/python3/{0005-Don-t-look-in-usr-lib-termcap-for-libraries.patch => 0004-Don-t-look-in-usr-lib-termcap-for-libraries.patch} (100%)
 rename package/python3/{0006-Don-t-add-multiarch-paths.patch => 0005-Don-t-add-multiarch-paths.patch} (100%)
 rename package/python3/{0007-Abort-on-failed-module-build.patch => 0006-Abort-on-failed-module-build.patch} (100%)
 rename package/python3/{0008-Serial-ioctl-workaround.patch => 0007-Serial-ioctl-workaround.patch} (100%)
 rename package/python3/{0009-Do-not-adjust-the-shebang-of-Python-scripts-for-cros.patch => 0008-Do-not-adjust-the-shebang-of-Python-scripts-for-cros.patch} (100%)
 rename package/python3/{0010-Misc-python-config.sh.in-ensure-sed-invocations-only.patch => 0009-Misc-python-config.sh.in-ensure-sed-invocations-only.patch} (100%)
 rename package/python3/{0011-Add-an-option-to-disable-pydoc.patch => 0010-Add-an-option-to-disable-pydoc.patch} (100%)
 rename package/python3/{0012-Add-an-option-to-disable-lib2to3.patch => 0011-Add-an-option-to-disable-lib2to3.patch} (100%)
 rename package/python3/{0020-Add-an-option-to-disable-IDLE.patch => 0012-Add-an-option-to-disable-IDLE.patch} (100%)
 rename package/python3/{0026-python-config.sh-don-t-reassign-prefix.patch => 0013-python-config.sh-don-t-reassign-prefix.patch} (100%)
 rename package/python3/{0028-fix-building-on-older-distributions.patch => 0015-fix-building-on-older-distributions.patch} (100%)
 rename package/python3/{0029-configure.ac-fixup-CC-print-multiarch-output-for-mus.patch => 0016-configure.ac-fixup-CC-print-multiarch-output-for-mus.patch} (100%)
 rename package/python3/{0031-lib-crypt-uClibc-ng-doesn-t-set-errno-when-encryptio.patch => 0017-lib-crypt-uClibc-ng-doesn-t-set-errno-when-encryptio.patch} (100%)

diff --git a/.checkpackageignore b/.checkpackageignore
index 4c35220d42..c986a242e1 100644
--- a/.checkpackageignore
+++ b/.checkpackageignore
@@ -1153,7 +1153,6 @@ package/python-web2py/S51web2py Shellcheck Variables
 package/python-ws4py/0001-Adjust-ws4py-for-Python-3.7-syntax.patch Upstream
 package/python3/0001-Make-the-build-of-pyc-files-conditional.patch Upstream
 package/python3/0002-Disable-buggy_getaddrinfo-configure-test-when-cross-.patch Upstream
-package/python3/0003-Add-infrastructure-to-disable-the-build-of-certain-e.patch Upstream
 package/python3/0004-Adjust-library-header-paths-for-cross-compilation.patch Upstream
 package/python3/0005-Don-t-look-in-usr-lib-termcap-for-libraries.patch Upstream
 package/python3/0006-Don-t-add-multiarch-paths.patch Upstream
diff --git a/package/python3/0003-Add-infrastructure-to-disable-the-build-of-certain-e.patch b/package/python3/0003-Add-infrastructure-to-disable-the-build-of-certain-e.patch
deleted file mode 100644
index 5b3911374e..0000000000
--- a/package/python3/0003-Add-infrastructure-to-disable-the-build-of-certain-e.patch
+++ /dev/null
@@ -1,108 +0,0 @@
-From 8e02cebdac536dfb6748da2c50656a26f70d9da7 Mon Sep 17 00:00:00 2001
-From: Thomas Petazzoni <thomas.petazzoni@free-electrons.com>
-Date: Wed, 22 Feb 2017 16:33:22 -0800
-Subject: [PATCH] Add infrastructure to disable the build of certain extensions
-
-Some of the extensions part of the Python core have dependencies on
-external libraries (sqlite, tk, etc.) or are relatively big and not
-necessarly always useful (CJK codecs for example). By extensions, we
-mean part of Python modules that are written in C and therefore
-compiled to binary code.
-
-Therefore, we introduce a small infrastructure that allows to disable
-some of those extensions. This can be done inside the configure.ac by
-adding values to the DISABLED_EXTENSIONS variable (which is a
-word-separated list of extensions).
-
-The implementation works as follow :
-
- * configure.ac defines a DISABLED_EXTENSIONS variable, which is
-   substituted (so that when Makefile.pre is generated from
-   Makefile.pre.in, the value of the variable is substituted). For
-   now, this DISABLED_EXTENSIONS variable is empty, later patches will
-   use it.
-
- * Makefile.pre.in passes the DISABLED_EXTENSIONS value down to the
-   variables passed in the environment when calling the setup.py
-   script that actually builds and installs those extensions.
-
- * setup.py is modified so that the existing "disabled_module_list" is
-   filled with those pre-disabled extensions listed in
-   DISABLED_EXTENSIONS.
-
-Patch ported to python2.7 by Maxime Ripard <ripard@archos.com>, and
-then extended by Thomas Petazzoni
-<thomas.petazzoni@free-electrons.com>.
-
-Signed-off-by: Thomas Petazzoni <thomas.petazzoni@free-electrons.com>
-[ Andrey Smirnov: ported to Python 3.6 ]
-Signed-off-by: Andrey Smirnov <andrew.smirnov@gmail.com>
----
- Makefile.pre.in | 6 +++++-
- configure.ac    | 2 ++
- setup.py        | 5 ++++-
- 3 files changed, 11 insertions(+), 2 deletions(-)
-
-diff --git a/Makefile.pre.in b/Makefile.pre.in
-index 2957c8e5a1..c1cfb96767 100644
---- a/Makefile.pre.in
-+++ b/Makefile.pre.in
-@@ -239,6 +239,8 @@ FILEMODE=	644
- # configure script arguments
- CONFIG_ARGS=	@CONFIG_ARGS@
- 
-+# disabled extensions
-+DISABLED_EXTENSIONS=	@DISABLED_EXTENSIONS@
- 
- # Subdirectories with code
- SRCDIRS= 	@SRCDIRS@
-@@ -739,6 +741,7 @@ sharedmods: $(PYTHON_FOR_BUILD_DEPS) pybuilddir.txt @LIBMPDEC_INTERNAL@ @LIBEXPA
- 	    *) quiet="";; \
- 	esac; \
- 	echo "$(RUNSHARED) CC='$(CC)' LDSHARED='$(BLDSHARED)' OPT='$(OPT)' \
-+		DISABLED_EXTENSIONS="$(DISABLED_EXTENSIONS)" \
- 		$(PYTHON_FOR_BUILD) $(srcdir)/setup.py $$quiet build"; \
- 	$(RUNSHARED) CC='$(CC)' LDSHARED='$(BLDSHARED)' OPT='$(OPT)' \
- 		$(PYTHON_FOR_BUILD) $(srcdir)/setup.py $$quiet build
-@@ -2228,7 +2231,8 @@ libainstall: all python-config
- # Install the dynamically loadable modules
- # This goes into $(exec_prefix)
- sharedinstall: all
--	$(RUNSHARED) $(PYTHON_FOR_BUILD) $(srcdir)/setup.py install \
-+	$(RUNSHARED) DISABLED_EXTENSIONS="$(DISABLED_EXTENSIONS)" \
-+		$(PYTHON_FOR_BUILD) $(srcdir)/setup.py install \
- 	   	--prefix=$(prefix) \
- 		--install-scripts=$(BINDIR) \
- 		--install-platlib=$(DESTSHARED) \
-diff --git a/configure.ac b/configure.ac
-index 830885fcb3..5a6a1fe608 100644
---- a/configure.ac
-+++ b/configure.ac
-@@ -3562,6 +3562,8 @@ LIBS="$withval $LIBS"
- ],
- [AC_MSG_RESULT(no)])
- 
-+AC_SUBST(DISABLED_EXTENSIONS)
-+
- # Check for use of the system expat library
- AC_MSG_CHECKING(for --with-system-expat)
- AC_ARG_WITH(system_expat,
-diff --git a/setup.py b/setup.py
-index 15d0d4576a..e496ee34c2 100644
---- a/setup.py
-+++ b/setup.py
-@@ -56,7 +56,10 @@
- 
- 
- # This global variable is used to hold the list of modules to be disabled.
--DISABLED_MODULE_LIST = []
-+try:
-+    DISABLED_MODULE_LIST = sysconfig.get_config_var("DISABLED_EXTENSIONS").split(" ")
-+except KeyError:
-+    DISABLED_MODULE_LIST = list()
- 
- # --list-module-names option used by Tools/scripts/generate_module_names.py
- LIST_MODULE_NAMES = False
--- 
-2.34.1
-
diff --git a/package/python3/0004-Adjust-library-header-paths-for-cross-compilation.patch b/package/python3/0003-Adjust-library-header-paths-for-cross-compilation.patch
similarity index 100%
rename from package/python3/0004-Adjust-library-header-paths-for-cross-compilation.patch
rename to package/python3/0003-Adjust-library-header-paths-for-cross-compilation.patch
diff --git a/package/python3/0005-Don-t-look-in-usr-lib-termcap-for-libraries.patch b/package/python3/0004-Don-t-look-in-usr-lib-termcap-for-libraries.patch
similarity index 100%
rename from package/python3/0005-Don-t-look-in-usr-lib-termcap-for-libraries.patch
rename to package/python3/0004-Don-t-look-in-usr-lib-termcap-for-libraries.patch
diff --git a/package/python3/0006-Don-t-add-multiarch-paths.patch b/package/python3/0005-Don-t-add-multiarch-paths.patch
similarity index 100%
rename from package/python3/0006-Don-t-add-multiarch-paths.patch
rename to package/python3/0005-Don-t-add-multiarch-paths.patch
diff --git a/package/python3/0007-Abort-on-failed-module-build.patch b/package/python3/0006-Abort-on-failed-module-build.patch
similarity index 100%
rename from package/python3/0007-Abort-on-failed-module-build.patch
rename to package/python3/0006-Abort-on-failed-module-build.patch
diff --git a/package/python3/0008-Serial-ioctl-workaround.patch b/package/python3/0007-Serial-ioctl-workaround.patch
similarity index 100%
rename from package/python3/0008-Serial-ioctl-workaround.patch
rename to package/python3/0007-Serial-ioctl-workaround.patch
diff --git a/package/python3/0009-Do-not-adjust-the-shebang-of-Python-scripts-for-cros.patch b/package/python3/0008-Do-not-adjust-the-shebang-of-Python-scripts-for-cros.patch
similarity index 100%
rename from package/python3/0009-Do-not-adjust-the-shebang-of-Python-scripts-for-cros.patch
rename to package/python3/0008-Do-not-adjust-the-shebang-of-Python-scripts-for-cros.patch
diff --git a/package/python3/0010-Misc-python-config.sh.in-ensure-sed-invocations-only.patch b/package/python3/0009-Misc-python-config.sh.in-ensure-sed-invocations-only.patch
similarity index 100%
rename from package/python3/0010-Misc-python-config.sh.in-ensure-sed-invocations-only.patch
rename to package/python3/0009-Misc-python-config.sh.in-ensure-sed-invocations-only.patch
diff --git a/package/python3/0011-Add-an-option-to-disable-pydoc.patch b/package/python3/0010-Add-an-option-to-disable-pydoc.patch
similarity index 100%
rename from package/python3/0011-Add-an-option-to-disable-pydoc.patch
rename to package/python3/0010-Add-an-option-to-disable-pydoc.patch
diff --git a/package/python3/0012-Add-an-option-to-disable-lib2to3.patch b/package/python3/0011-Add-an-option-to-disable-lib2to3.patch
similarity index 100%
rename from package/python3/0012-Add-an-option-to-disable-lib2to3.patch
rename to package/python3/0011-Add-an-option-to-disable-lib2to3.patch
diff --git a/package/python3/0020-Add-an-option-to-disable-IDLE.patch b/package/python3/0012-Add-an-option-to-disable-IDLE.patch
similarity index 100%
rename from package/python3/0020-Add-an-option-to-disable-IDLE.patch
rename to package/python3/0012-Add-an-option-to-disable-IDLE.patch
diff --git a/package/python3/0026-python-config.sh-don-t-reassign-prefix.patch b/package/python3/0013-python-config.sh-don-t-reassign-prefix.patch
similarity index 100%
rename from package/python3/0026-python-config.sh-don-t-reassign-prefix.patch
rename to package/python3/0013-python-config.sh-don-t-reassign-prefix.patch
diff --git a/package/python3/0028-fix-building-on-older-distributions.patch b/package/python3/0015-fix-building-on-older-distributions.patch
similarity index 100%
rename from package/python3/0028-fix-building-on-older-distributions.patch
rename to package/python3/0015-fix-building-on-older-distributions.patch
diff --git a/package/python3/0029-configure.ac-fixup-CC-print-multiarch-output-for-mus.patch b/package/python3/0016-configure.ac-fixup-CC-print-multiarch-output-for-mus.patch
similarity index 100%
rename from package/python3/0029-configure.ac-fixup-CC-print-multiarch-output-for-mus.patch
rename to package/python3/0016-configure.ac-fixup-CC-print-multiarch-output-for-mus.patch
diff --git a/package/python3/0031-lib-crypt-uClibc-ng-doesn-t-set-errno-when-encryptio.patch b/package/python3/0017-lib-crypt-uClibc-ng-doesn-t-set-errno-when-encryptio.patch
similarity index 100%
rename from package/python3/0031-lib-crypt-uClibc-ng-doesn-t-set-errno-when-encryptio.patch
rename to package/python3/0017-lib-crypt-uClibc-ng-doesn-t-set-errno-when-encryptio.patch
-- 
2.41.0

_______________________________________________
buildroot mailing list
buildroot@buildroot.org
https://lists.buildroot.org/mailman/listinfo/buildroot

^ permalink raw reply related	[flat|nested] 54+ messages in thread

* [Buildroot] [PATCH 15/30] package/scons: bump version to 4.5.2
  2023-10-26  9:26 [Buildroot] [PATCH 00/30] package/python3: bump version to 3.12.0 Adam Duskett
                   ` (13 preceding siblings ...)
  2023-10-26  9:26 ` [Buildroot] [PATCH 14/30] package/python3: Remove infrastructure to disable the build of certain extensions Adam Duskett
@ 2023-10-26  9:26 ` Adam Duskett
  2023-11-04 17:54   ` Arnout Vandecappelle via buildroot
  2023-10-26  9:26 ` [Buildroot] [PATCH 16/30] package/python-wsaccel: bump version to 0.6.6 Adam Duskett
                   ` (15 subsequent siblings)
  30 siblings, 1 reply; 54+ messages in thread
From: Adam Duskett @ 2023-10-26  9:26 UTC (permalink / raw)
  To: buildroot
  Cc: Adam Duskett, Andrey Smirnov, Julien Olivain, Asaf Kahlon,
	James Hilliard, Thomas Petazzoni, Mauro Condarelli

Scons is a setuptools package now.

Signed-off-by: Adam Duskett <adam.duskett@amarulasolutions.com>
---
 package/scons/scons.hash | 4 ++--
 package/scons/scons.mk   | 7 ++++---
 2 files changed, 6 insertions(+), 5 deletions(-)

diff --git a/package/scons/scons.hash b/package/scons/scons.hash
index a72fbaee5a..48b5077b2e 100644
--- a/package/scons/scons.hash
+++ b/package/scons/scons.hash
@@ -1,3 +1,3 @@
 # Locally computed:
-sha256  7801f3f62f654528e272df780be10c0e9337e897650b62ddcee9f39fde13f8fb  scons-3.1.2.tar.gz
-sha256  72ed889165fb28378cadac14552be4a959f1ebab6b148abb5dd2b49712c3c6f6  LICENSE.txt
+sha256  ce26aac95d350a79a4192196b0beac3cb24f4ccabce0123eb28d3370f576f072  SCons-4.5.2.tar.gz
+sha256  2f6ac9a1fc98394d18b80dba9bedb9d5626006d44db3fecf7cf3e21cff7e8b1c  LICENSE
diff --git a/package/scons/scons.mk b/package/scons/scons.mk
index 6b75d3ddca..767cf64b26 100644
--- a/package/scons/scons.mk
+++ b/package/scons/scons.mk
@@ -4,11 +4,12 @@
 #
 ################################################################################
 
-SCONS_VERSION = 3.1.2
+SCONS_VERSION = 4.5.2
+SCONS_SOURCE = SCons-$(SCONS_VERSION).tar.gz
 SCONS_SITE = http://downloads.sourceforge.net/project/scons/scons/$(SCONS_VERSION)
 SCONS_LICENSE = MIT
-SCONS_LICENSE_FILES = LICENSE.txt
-SCONS_SETUP_TYPE = distutils
+SCONS_LICENSE_FILES = LICENSE
+SCONS_SETUP_TYPE = setuptools
 
 HOST_SCONS_INSTALL_OPTS = \
 	--install-lib=$(HOST_DIR)/lib/scons-$(SCONS_VERSION)
-- 
2.41.0

_______________________________________________
buildroot mailing list
buildroot@buildroot.org
https://lists.buildroot.org/mailman/listinfo/buildroot

^ permalink raw reply related	[flat|nested] 54+ messages in thread

* [Buildroot] [PATCH 16/30] package/python-wsaccel: bump version to 0.6.6
  2023-10-26  9:26 [Buildroot] [PATCH 00/30] package/python3: bump version to 3.12.0 Adam Duskett
                   ` (14 preceding siblings ...)
  2023-10-26  9:26 ` [Buildroot] [PATCH 15/30] package/scons: bump version to 4.5.2 Adam Duskett
@ 2023-10-26  9:26 ` Adam Duskett
  2023-11-04 17:56   ` Arnout Vandecappelle via buildroot
  2023-10-26  9:26 ` [Buildroot] [PATCH 17/30] package/python-systemd: bump version to 235 Adam Duskett
                   ` (14 subsequent siblings)
  30 siblings, 1 reply; 54+ messages in thread
From: Adam Duskett @ 2023-10-26  9:26 UTC (permalink / raw)
  To: buildroot
  Cc: Adam Duskett, Andrey Smirnov, Julien Olivain, Asaf Kahlon,
	James Hilliard, Thomas Petazzoni, Mauro Condarelli

Signed-off-by: Adam Duskett <adam.duskett@amarulasolutions.com>
---
 package/python-wsaccel/python-wsaccel.hash | 4 ++--
 package/python-wsaccel/python-wsaccel.mk   | 4 ++--
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/package/python-wsaccel/python-wsaccel.hash b/package/python-wsaccel/python-wsaccel.hash
index b388c3cc7c..9991f8f454 100644
--- a/package/python-wsaccel/python-wsaccel.hash
+++ b/package/python-wsaccel/python-wsaccel.hash
@@ -1,5 +1,5 @@
 # md5, sha256 from https://pypi.org/pypi/wsaccel/json
-md5  73bdc70035813ded14f9a4b81e82622a  wsaccel-0.6.4.tar.gz
-sha256  cbf66a88bcaf6c6ad16d50ea29215891526b6e993c4bc7ed44b044ee6fe3ad3d  wsaccel-0.6.4.tar.gz
+md5  b05eecfac9cb19326bfc3b538de495ac  wsaccel-0.6.6.tar.gz
+sha256  18efec0a7182587ba97102b4cd8df7b4f665f45d7ca36f19783f5f081ea114ea  wsaccel-0.6.6.tar.gz
 # Locally computed sha256 checksums
 sha256  b6982974cb838b985b54b663d1780d280735086249c2e28015f25dd455df25da  LICENSE
diff --git a/package/python-wsaccel/python-wsaccel.mk b/package/python-wsaccel/python-wsaccel.mk
index ce41d431ff..1f63aa5c40 100644
--- a/package/python-wsaccel/python-wsaccel.mk
+++ b/package/python-wsaccel/python-wsaccel.mk
@@ -4,9 +4,9 @@
 #
 ################################################################################
 
-PYTHON_WSACCEL_VERSION = 0.6.4
+PYTHON_WSACCEL_VERSION = 0.6.6
 PYTHON_WSACCEL_SOURCE = wsaccel-$(PYTHON_WSACCEL_VERSION).tar.gz
-PYTHON_WSACCEL_SITE = https://files.pythonhosted.org/packages/77/0b/a44df15382a76b6768184630d483b8363d65b9f70d1aacf76153d496bbb9
+PYTHON_WSACCEL_SITE = https://files.pythonhosted.org/packages/94/28/41c0e711b538f6031a247ab4ec5352267f12ed416e3a638b8d55fc33f609
 PYTHON_WSACCEL_LICENSE = Apache-2.0
 PYTHON_WSACCEL_LICENSE_FILES = LICENSE
 PYTHON_WSACCEL_SETUP_TYPE = setuptools
-- 
2.41.0

_______________________________________________
buildroot mailing list
buildroot@buildroot.org
https://lists.buildroot.org/mailman/listinfo/buildroot

^ permalink raw reply related	[flat|nested] 54+ messages in thread

* [Buildroot] [PATCH 17/30] package/python-systemd: bump version to 235
  2023-10-26  9:26 [Buildroot] [PATCH 00/30] package/python3: bump version to 3.12.0 Adam Duskett
                   ` (15 preceding siblings ...)
  2023-10-26  9:26 ` [Buildroot] [PATCH 16/30] package/python-wsaccel: bump version to 0.6.6 Adam Duskett
@ 2023-10-26  9:26 ` Adam Duskett
  2023-11-04 17:59   ` Arnout Vandecappelle via buildroot
  2023-10-26  9:26 ` [Buildroot] [PATCH 18/30] package/python-iptables: bump version to 1.0.1 Adam Duskett
                   ` (13 subsequent siblings)
  30 siblings, 1 reply; 54+ messages in thread
From: Adam Duskett @ 2023-10-26  9:26 UTC (permalink / raw)
  To: buildroot
  Cc: Adam Duskett, Andrey Smirnov, Julien Olivain, Asaf Kahlon,
	James Hilliard, Thomas Petazzoni, Mauro Condarelli

Signed-off-by: Adam Duskett <adam.duskett@amarulasolutions.com>
---
 package/python-systemd/python-systemd.hash | 4 ++--
 package/python-systemd/python-systemd.mk   | 6 +++---
 2 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/package/python-systemd/python-systemd.hash b/package/python-systemd/python-systemd.hash
index 789b48590d..6cc6639218 100644
--- a/package/python-systemd/python-systemd.hash
+++ b/package/python-systemd/python-systemd.hash
@@ -1,5 +1,5 @@
 # md5 from https://pypi.python.org/pypi/systemd-python/
-md5  5071ea5bcb976186e92a3f5e75df221d  systemd-python-234.tar.gz
+md5  93f3ca09f35719ca6a4edd1d62d38dd4  systemd-python-235.tar.gz
 # Locally computed
-sha256  fd0e44bf70eadae45aadc292cb0a7eb5b0b6372cd1b391228047d33895db83e7  systemd-python-234.tar.gz
+sha256  4e57f39797fd5d9e2d22b8806a252d7c0106c936039d1e71c8c6b8008e695c0a  systemd-python-235.tar.gz
 sha256  dc626520dcd53a22f727af3ee42c770e56c97a64fe3adb063799d8ab032fe551  LICENSE.txt
diff --git a/package/python-systemd/python-systemd.mk b/package/python-systemd/python-systemd.mk
index 3ccfd57ce5..24ba84df76 100644
--- a/package/python-systemd/python-systemd.mk
+++ b/package/python-systemd/python-systemd.mk
@@ -4,10 +4,10 @@
 #
 ################################################################################
 
-PYTHON_SYSTEMD_VERSION = 234 # Should be kept in sync with $(SYSTEMD_VERSION)
+PYTHON_SYSTEMD_VERSION = 235 # Should be kept in sync with $(SYSTEMD_VERSION)
 PYTHON_SYSTEMD_SOURCE = systemd-python-$(PYTHON_SYSTEMD_VERSION).tar.gz
-PYTHON_SYSTEMD_SITE = https://pypi.python.org/packages/e8/a8/00ba0f605837a8f69523e6c3a4fb14675a6430c163f836540129c50b3aef
-PYTHON_SYSTEMD_SETUP_TYPE = distutils
+PYTHON_SYSTEMD_SITE = https://files.pythonhosted.org/packages/10/9e/ab4458e00367223bda2dd7ccf0849a72235ee3e29b36dce732685d9b7ad9
+PYTHON_SYSTEMD_SETUP_TYPE = setuptools
 PYTHON_SYSTEMD_LICENSE = LGPL-2.1
 PYTHON_SYSTEMD_LICENSE_FILES = LICENSE.txt
 PYTHON_SYSTEMD_DEPENDENCIES = systemd # To be able to link against libsystemd
-- 
2.41.0

_______________________________________________
buildroot mailing list
buildroot@buildroot.org
https://lists.buildroot.org/mailman/listinfo/buildroot

^ permalink raw reply related	[flat|nested] 54+ messages in thread

* [Buildroot] [PATCH 18/30] package/python-iptables: bump version to 1.0.1
  2023-10-26  9:26 [Buildroot] [PATCH 00/30] package/python3: bump version to 3.12.0 Adam Duskett
                   ` (16 preceding siblings ...)
  2023-10-26  9:26 ` [Buildroot] [PATCH 17/30] package/python-systemd: bump version to 235 Adam Duskett
@ 2023-10-26  9:26 ` Adam Duskett
  2023-11-04 18:14   ` Arnout Vandecappelle via buildroot
  2023-10-26  9:26 ` [Buildroot] [PATCH 19/30] package/libftdi: add patch to move from distutils to sysconfig Adam Duskett
                   ` (12 subsequent siblings)
  30 siblings, 1 reply; 54+ messages in thread
From: Adam Duskett @ 2023-10-26  9:26 UTC (permalink / raw)
  To: buildroot
  Cc: Adam Duskett, Andrey Smirnov, Julien Olivain, Asaf Kahlon,
	James Hilliard, Thomas Petazzoni, Mauro Condarelli

Signed-off-by: Adam Duskett <adam.duskett@amarulasolutions.com>
---
 package/python-iptables/python-iptables.hash | 4 ++--
 package/python-iptables/python-iptables.mk   | 4 ++--
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/package/python-iptables/python-iptables.hash b/package/python-iptables/python-iptables.hash
index ee5f765fba..1b7c9e9324 100644
--- a/package/python-iptables/python-iptables.hash
+++ b/package/python-iptables/python-iptables.hash
@@ -1,6 +1,6 @@
 # md5, sha256 from https://pypi.org/pypi/python-iptables/json
-md5  3fb27da1107bdb62196850fa70e8b0d4  python-iptables-1.0.0.tar.gz
-sha256  480470adb5f29bf84269b4e53dbad9623af91c79aa666cc0274dec199a555bc5  python-iptables-1.0.0.tar.gz
+md5  bd6950c1ed9c6b48b7d552ff8e6766c5  python-iptables-1.0.1.tar.gz
+sha256  1989f2b48598392c3574052a95f456985cb06fb4287b61bf8794e93ebc37eddb  python-iptables-1.0.1.tar.gz
 
 # Locally calculated
 sha256  b827789c74144d9bb92595ed3bc568aef767a7e8d930fba61c2cdd9f6ec27599  NOTICE
diff --git a/package/python-iptables/python-iptables.mk b/package/python-iptables/python-iptables.mk
index 9cb4285ec3..d01545d8b2 100644
--- a/package/python-iptables/python-iptables.mk
+++ b/package/python-iptables/python-iptables.mk
@@ -4,8 +4,8 @@
 #
 ################################################################################
 
-PYTHON_IPTABLES_VERSION = 1.0.0
-PYTHON_IPTABLES_SITE = https://files.pythonhosted.org/packages/ca/6e/cba9c6f4b5a1963b7f5b015f5ed5e2eec7a94ac460570e3474177c4004d6
+PYTHON_IPTABLES_VERSION = 1.0.1
+PYTHON_IPTABLES_SITE = https://files.pythonhosted.org/packages/35/e4/33e639b9e153c2d798d73342a96715a4edca6f46431d763b275a34b3aeca
 PYTHON_IPTABLES_SETUP_TYPE = setuptools
 PYTHON_IPTABLES_LICENSE = Apache-2.0
 PYTHON_IPTABLES_LICENSE_FILES = NOTICE
-- 
2.41.0

_______________________________________________
buildroot mailing list
buildroot@buildroot.org
https://lists.buildroot.org/mailman/listinfo/buildroot

^ permalink raw reply related	[flat|nested] 54+ messages in thread

* [Buildroot] [PATCH 19/30] package/libftdi: add patch to move from distutils to sysconfig
  2023-10-26  9:26 [Buildroot] [PATCH 00/30] package/python3: bump version to 3.12.0 Adam Duskett
                   ` (17 preceding siblings ...)
  2023-10-26  9:26 ` [Buildroot] [PATCH 18/30] package/python-iptables: bump version to 1.0.1 Adam Duskett
@ 2023-10-26  9:26 ` Adam Duskett
  2023-10-26 11:49   ` Yegor Yefremov via buildroot
  2023-11-04 18:29   ` Arnout Vandecappelle via buildroot
  2023-10-26  9:26 ` [Buildroot] [PATCH 20/30] package/python-constantly: update versioneer to 0.29 Adam Duskett
                   ` (11 subsequent siblings)
  30 siblings, 2 replies; 54+ messages in thread
From: Adam Duskett @ 2023-10-26  9:26 UTC (permalink / raw)
  To: buildroot
  Cc: Adam Duskett, Andrey Smirnov, Julien Olivain, Asaf Kahlon,
	James Hilliard, Thomas Petazzoni, Mauro Condarelli

In preperation of python 3.12.0, distutils has been removed completely.
This is a patch taken from the Fedora 39 RPM: libftdi-1.5-10.fc39.x86_64.

Upstream commit: abd19b721f7e9b4d514ed319ece173ebc7b1ea72

Signed-off-by: Adam Duskett <adam.duskett@amarulasolutions.com>
---
 ...003-move-from-distutils-to-sysconfig.patch | 32 +++++++++++++++++++
 1 file changed, 32 insertions(+)
 create mode 100644 package/libftdi/0003-move-from-distutils-to-sysconfig.patch

diff --git a/package/libftdi/0003-move-from-distutils-to-sysconfig.patch b/package/libftdi/0003-move-from-distutils-to-sysconfig.patch
new file mode 100644
index 0000000000..cb179c7cde
--- /dev/null
+++ b/package/libftdi/0003-move-from-distutils-to-sysconfig.patch
@@ -0,0 +1,32 @@
+From 872cd480990e6536d2cf48568627903ebf5acbeb Mon Sep 17 00:00:00 2001
+From: Dan Hor <dan@danny.cz>
+Date: Tue, 24 Oct 2023 10:36:09 +0200
+Subject: [PATCH] move from distutils to sysconfig
+
+The distutils module was deprecated in Python 3.10, thus switch to the
+sysconfig module instead.
+
+Upstream: committed abd19b721f7e9b4d514ed319ece173ebc7b1ea72
+
+Signed-off-by: Dan Hor <dan@danny.cz>
+Signed-off-by: Adam Duskett <adam.duskett@amarulasolutions.com>
+---
+ python/CMakeLists.txt | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt
+index 5b6f420..19ce500 100644
+--- a/python/CMakeLists.txt
++++ b/python/CMakeLists.txt
+@@ -42,7 +42,7 @@ endif ()
+ 
+ set_target_properties ( ${SWIG_MODULE_ftdi1_REAL_NAME} PROPERTIES NO_SONAME ON )
+ 
+-execute_process ( COMMAND ${PYTHON_EXECUTABLE} -c "from distutils import sysconfig; print( sysconfig.get_python_lib( plat_specific=True, prefix='${CMAKE_INSTALL_PREFIX}' ) )"
++execute_process ( COMMAND ${PYTHON_EXECUTABLE} -c "import sysconfig; print( sysconfig.get_path( 'platlib', vars={'platbase': '${CMAKE_INSTALL_PREFIX}'} ) )"
+                   OUTPUT_VARIABLE _ABS_PYTHON_MODULE_PATH
+                   OUTPUT_STRIP_TRAILING_WHITESPACE )
+ 
+-- 
+2.41.0
+
-- 
2.41.0

_______________________________________________
buildroot mailing list
buildroot@buildroot.org
https://lists.buildroot.org/mailman/listinfo/buildroot

^ permalink raw reply related	[flat|nested] 54+ messages in thread

* [Buildroot] [PATCH 20/30] package/python-constantly: update versioneer to 0.29
  2023-10-26  9:26 [Buildroot] [PATCH 00/30] package/python3: bump version to 3.12.0 Adam Duskett
                   ` (18 preceding siblings ...)
  2023-10-26  9:26 ` [Buildroot] [PATCH 19/30] package/libftdi: add patch to move from distutils to sysconfig Adam Duskett
@ 2023-10-26  9:26 ` Adam Duskett
  2023-11-04 21:20   ` Arnout Vandecappelle via buildroot
  2023-10-26  9:26 ` [Buildroot] [PATCH 21/30] package/python-magic-wormhole: " Adam Duskett
                   ` (10 subsequent siblings)
  30 siblings, 1 reply; 54+ messages in thread
From: Adam Duskett @ 2023-10-26  9:26 UTC (permalink / raw)
  To: buildroot
  Cc: Adam Duskett, Andrey Smirnov, Julien Olivain, Asaf Kahlon,
	James Hilliard, Thomas Petazzoni, Mauro Condarelli

Versioneer < 0.21 is incompatible with Python 3.12.0. Use the latest version
which is 0.29 as of this commit.

Signed-off-by: Adam Duskett <adam.duskett@amarulasolutions.com>
---
 .../0001-Update-versioneer-to-0.29.patch      | 2619 +++++++++++++++++
 1 file changed, 2619 insertions(+)
 create mode 100644 package/python-constantly/0001-Update-versioneer-to-0.29.patch

diff --git a/package/python-constantly/0001-Update-versioneer-to-0.29.patch b/package/python-constantly/0001-Update-versioneer-to-0.29.patch
new file mode 100644
index 0000000000..1c865fe9c1
--- /dev/null
+++ b/package/python-constantly/0001-Update-versioneer-to-0.29.patch
@@ -0,0 +1,2619 @@
+From 2ab3f529c070e3efae209a46128cca6669029cec Mon Sep 17 00:00:00 2001
+From: Adam Duskett <adam.duskett@amarulasolutions.com>
+Date: Mon, 23 Oct 2023 18:01:12 +0200
+Subject: [PATCH] Update versioneer to 0.29
+
+Fixes builds against Python 3.12.0
+
+Upstream: https://github.com/twisted/constantly/pull/30
+
+Signed-off-by: Adam Duskett <adam.duskett@amarulasolutions.com>
+---
+ versioneer.py | 1851 +++++++++++++++++++++++++++++++++----------------
+ 1 file changed, 1243 insertions(+), 608 deletions(-)
+
+diff --git a/versioneer.py b/versioneer.py
+index c010f63..de97d90 100644
+--- a/versioneer.py
++++ b/versioneer.py
+@@ -1,23 +1,19 @@
++# Version: 0.29
+ 
+-# Version: 0.15
++"""The Versioneer - like a rocketeer, but for versions.
+ 
+-"""
+ The Versioneer
+ ==============
+ 
+ * like a rocketeer, but for versions!
+-* https://github.com/warner/python-versioneer
++* https://github.com/python-versioneer/python-versioneer
+ * Brian Warner
+-* License: Public Domain
+-* Compatible With: python2.6, 2.7, 3.2, 3.3, 3.4, and pypy
+-* [![Latest Version]
+-(https://pypip.in/version/versioneer/badge.svg?style=flat)
+-](https://pypi.python.org/pypi/versioneer/)
+-* [![Build Status]
+-(https://travis-ci.org/warner/python-versioneer.png?branch=master)
+-](https://travis-ci.org/warner/python-versioneer)
+-
+-This is a tool for managing a recorded version number in distutils-based
++* License: Public Domain (Unlicense)
++* Compatible with: Python 3.7, 3.8, 3.9, 3.10, 3.11 and pypy3
++* [![Latest Version][pypi-image]][pypi-url]
++* [![Build Status][travis-image]][travis-url]
++
++This is a tool for managing a recorded version number in setuptools-based
+ python projects. The goal is to remove the tedious and error-prone "update
+ the embedded version string" step from your release process. Making a new
+ release should be as easy as recording a new tag in your version-control
+@@ -26,9 +22,38 @@ system, and maybe making new tarballs.
+ 
+ ## Quick Install
+ 
+-* `pip install versioneer` to somewhere to your $PATH
+-* add a `[versioneer]` section to your setup.cfg (see below)
+-* run `versioneer install` in your source tree, commit the results
++Versioneer provides two installation modes. The "classic" vendored mode installs
++a copy of versioneer into your repository. The experimental build-time dependency mode
++is intended to allow you to skip this step and simplify the process of upgrading.
++
++### Vendored mode
++
++* `pip install versioneer` to somewhere in your $PATH
++   * A [conda-forge recipe](https://github.com/conda-forge/versioneer-feedstock) is
++     available, so you can also use `conda install -c conda-forge versioneer`
++* add a `[tool.versioneer]` section to your `pyproject.toml` or a
++  `[versioneer]` section to your `setup.cfg` (see [Install](INSTALL.md))
++   * Note that you will need to add `tomli; python_version < "3.11"` to your
++     build-time dependencies if you use `pyproject.toml`
++* run `versioneer install --vendor` in your source tree, commit the results
++* verify version information with `python setup.py version`
++
++### Build-time dependency mode
++
++* `pip install versioneer` to somewhere in your $PATH
++   * A [conda-forge recipe](https://github.com/conda-forge/versioneer-feedstock) is
++     available, so you can also use `conda install -c conda-forge versioneer`
++* add a `[tool.versioneer]` section to your `pyproject.toml` or a
++  `[versioneer]` section to your `setup.cfg` (see [Install](INSTALL.md))
++* add `versioneer` (with `[toml]` extra, if configuring in `pyproject.toml`)
++  to the `requires` key of the `build-system` table in `pyproject.toml`:
++  ```toml
++  [build-system]
++  requires = ["setuptools", "versioneer[toml]"]
++  build-backend = "setuptools.build_meta"
++  ```
++* run `versioneer install --no-vendor` in your source tree, commit the results
++* verify version information with `python setup.py version`
+ 
+ ## Version Identifiers
+ 
+@@ -60,7 +85,7 @@ version 1.3). Many VCS systems can report a description that captures this,
+ for example `git describe --tags --dirty --always` reports things like
+ "0.7-1-g574ab98-dirty" to indicate that the checkout is one revision past the
+ 0.7 tag, has a unique revision id of "574ab98", and is "dirty" (it has
+-uncommitted changes.
++uncommitted changes).
+ 
+ The version identifier is used for multiple purposes:
+ 
+@@ -87,125 +112,7 @@ the generated version data.
+ 
+ ## Installation
+ 
+-First, decide on values for the following configuration variables:
+-
+-* `VCS`: the version control system you use. Currently accepts "git".
+-
+-* `style`: the style of version string to be produced. See "Styles" below for
+-  details. Defaults to "pep440", which looks like
+-  `TAG[+DISTANCE.gSHORTHASH[.dirty]]`.
+-
+-* `versionfile_source`:
+-
+-  A project-relative pathname into which the generated version strings should
+-  be written. This is usually a `_version.py` next to your project's main
+-  `__init__.py` file, so it can be imported at runtime. If your project uses
+-  `src/myproject/__init__.py`, this should be `src/myproject/_version.py`.
+-  This file should be checked in to your VCS as usual: the copy created below
+-  by `setup.py setup_versioneer` will include code that parses expanded VCS
+-  keywords in generated tarballs. The 'build' and 'sdist' commands will
+-  replace it with a copy that has just the calculated version string.
+-
+-  This must be set even if your project does not have any modules (and will
+-  therefore never import `_version.py`), since "setup.py sdist" -based trees
+-  still need somewhere to record the pre-calculated version strings. Anywhere
+-  in the source tree should do. If there is a `__init__.py` next to your
+-  `_version.py`, the `setup.py setup_versioneer` command (described below)
+-  will append some `__version__`-setting assignments, if they aren't already
+-  present.
+-
+-* `versionfile_build`:
+-
+-  Like `versionfile_source`, but relative to the build directory instead of
+-  the source directory. These will differ when your setup.py uses
+-  'package_dir='. If you have `package_dir={'myproject': 'src/myproject'}`,
+-  then you will probably have `versionfile_build='myproject/_version.py'` and
+-  `versionfile_source='src/myproject/_version.py'`.
+-
+-  If this is set to None, then `setup.py build` will not attempt to rewrite
+-  any `_version.py` in the built tree. If your project does not have any
+-  libraries (e.g. if it only builds a script), then you should use
+-  `versionfile_build = None` and override `distutils.command.build_scripts`
+-  to explicitly insert a copy of `versioneer.get_version()` into your
+-  generated script.
+-
+-* `tag_prefix`:
+-
+-  a string, like 'PROJECTNAME-', which appears at the start of all VCS tags.
+-  If your tags look like 'myproject-1.2.0', then you should use
+-  tag_prefix='myproject-'. If you use unprefixed tags like '1.2.0', this
+-  should be an empty string.
+-
+-* `parentdir_prefix`:
+-
+-  a optional string, frequently the same as tag_prefix, which appears at the
+-  start of all unpacked tarball filenames. If your tarball unpacks into
+-  'myproject-1.2.0', this should be 'myproject-'. To disable this feature,
+-  just omit the field from your `setup.cfg`.
+-
+-This tool provides one script, named `versioneer`. That script has one mode,
+-"install", which writes a copy of `versioneer.py` into the current directory
+-and runs `versioneer.py setup` to finish the installation.
+-
+-To versioneer-enable your project:
+-
+-* 1: Modify your `setup.cfg`, adding a section named `[versioneer]` and
+-  populating it with the configuration values you decided earlier (note that
+-  the option names are not case-sensitive):
+-
+-  ````
+-  [versioneer]
+-  VCS = git
+-  style = pep440
+-  versionfile_source = src/myproject/_version.py
+-  versionfile_build = myproject/_version.py
+-  tag_prefix = ""
+-  parentdir_prefix = myproject-
+-  ````
+-
+-* 2: Run `versioneer install`. This will do the following:
+-
+-  * copy `versioneer.py` into the top of your source tree
+-  * create `_version.py` in the right place (`versionfile_source`)
+-  * modify your `__init__.py` (if one exists next to `_version.py`) to define
+-    `__version__` (by calling a function from `_version.py`)
+-  * modify your `MANIFEST.in` to include both `versioneer.py` and the
+-    generated `_version.py` in sdist tarballs
+-
+-  `versioneer install` will complain about any problems it finds with your
+-  `setup.py` or `setup.cfg`. Run it multiple times until you have fixed all
+-  the problems.
+-
+-* 3: add a `import versioneer` to your setup.py, and add the following
+-  arguments to the setup() call:
+-
+-        version=versioneer.get_version(),
+-        cmdclass=versioneer.get_cmdclass(),
+-
+-* 4: commit these changes to your VCS. To make sure you won't forget,
+-  `versioneer install` will mark everything it touched for addition using
+-  `git add`. Don't forget to add `setup.py` and `setup.cfg` too.
+-
+-## Post-Installation Usage
+-
+-Once established, all uses of your tree from a VCS checkout should get the
+-current version string. All generated tarballs should include an embedded
+-version string (so users who unpack them will not need a VCS tool installed).
+-
+-If you distribute your project through PyPI, then the release process should
+-boil down to two steps:
+-
+-* 1: git tag 1.0
+-* 2: python setup.py register sdist upload
+-
+-If you distribute it through github (i.e. users use github to generate
+-tarballs with `git archive`), the process is:
+-
+-* 1: git tag 1.0
+-* 2: git push; git push --tags
+-
+-Versioneer will report "0+untagged.NUMCOMMITS.gHASH" until your tree has at
+-least one tag in its history.
++See [INSTALL.md](./INSTALL.md) for detailed installation instructions.
+ 
+ ## Version-String Flavors
+ 
+@@ -226,6 +133,10 @@ information:
+ * `['full-revisionid']`: detailed revision identifier. For Git, this is the
+   full SHA1 commit id, e.g. "1076c978a8d3cfc70f408fe5974aa6c092c949ac".
+ 
++* `['date']`: Date and time of the latest `HEAD` commit. For Git, it is the
++  commit date in ISO 8601 format. This will be None if the date is not
++  available.
++
+ * `['dirty']`: a boolean, True if the tree has uncommitted changes. Note that
+   this is only accurate if run in a VCS checkout, otherwise it is likely to
+   be False or None
+@@ -264,8 +175,8 @@ that this commit is two revisions ("+2") beyond the "0.11" tag. For released
+ software (exactly equal to a known tag), the identifier will only contain the
+ stripped tag, e.g. "0.11".
+ 
+-Other styles are available. See details.md in the Versioneer source tree for
+-descriptions.
++Other styles are available. See [details.md](details.md) in the Versioneer
++source tree for descriptions.
+ 
+ ## Debugging
+ 
+@@ -275,47 +186,84 @@ version`, which will run the version-lookup code in a verbose mode, and will
+ display the full contents of `get_versions()` (including the `error` string,
+ which may help identify what went wrong).
+ 
+-## Updating Versioneer
++## Known Limitations
+ 
+-To upgrade your project to a new release of Versioneer, do the following:
++Some situations are known to cause problems for Versioneer. This details the
++most significant ones. More can be found on Github
++[issues page](https://github.com/python-versioneer/python-versioneer/issues).
+ 
+-* install the new Versioneer (`pip install -U versioneer` or equivalent)
+-* edit `setup.cfg`, if necessary, to include any new configuration settings
+-  indicated by the release notes
+-* re-run `versioneer install` in your source tree, to replace
+-  `SRC/_version.py`
+-* commit any changed files
++### Subprojects
++
++Versioneer has limited support for source trees in which `setup.py` is not in
++the root directory (e.g. `setup.py` and `.git/` are *not* siblings). The are
++two common reasons why `setup.py` might not be in the root:
++
++* Source trees which contain multiple subprojects, such as
++  [Buildbot](https://github.com/buildbot/buildbot), which contains both
++  "master" and "slave" subprojects, each with their own `setup.py`,
++  `setup.cfg`, and `tox.ini`. Projects like these produce multiple PyPI
++  distributions (and upload multiple independently-installable tarballs).
++* Source trees whose main purpose is to contain a C library, but which also
++  provide bindings to Python (and perhaps other languages) in subdirectories.
++
++Versioneer will look for `.git` in parent directories, and most operations
++should get the right version string. However `pip` and `setuptools` have bugs
++and implementation details which frequently cause `pip install .` from a
++subproject directory to fail to find a correct version string (so it usually
++defaults to `0+unknown`).
++
++`pip install --editable .` should work correctly. `setup.py install` might
++work too.
++
++Pip-8.1.1 is known to have this problem, but hopefully it will get fixed in
++some later version.
++
++[Bug #38](https://github.com/python-versioneer/python-versioneer/issues/38) is tracking
++this issue. The discussion in
++[PR #61](https://github.com/python-versioneer/python-versioneer/pull/61) describes the
++issue from the Versioneer side in more detail.
++[pip PR#3176](https://github.com/pypa/pip/pull/3176) and
++[pip PR#3615](https://github.com/pypa/pip/pull/3615) contain work to improve
++pip to let Versioneer work correctly.
++
++Versioneer-0.16 and earlier only looked for a `.git` directory next to the
++`setup.cfg`, so subprojects were completely unsupported with those releases.
+ 
+-### Upgrading to 0.15
++### Editable installs with setuptools <= 18.5
+ 
+-Starting with this version, Versioneer is configured with a `[versioneer]`
+-section in your `setup.cfg` file. Earlier versions required the `setup.py` to
+-set attributes on the `versioneer` module immediately after import. The new
+-version will refuse to run (raising an exception during import) until you
+-have provided the necessary `setup.cfg` section.
++`setup.py develop` and `pip install --editable .` allow you to install a
++project into a virtualenv once, then continue editing the source code (and
++test) without re-installing after every change.
+ 
+-In addition, the Versioneer package provides an executable named
+-`versioneer`, and the installation process is driven by running `versioneer
+-install`. In 0.14 and earlier, the executable was named
+-`versioneer-installer` and was run without an argument.
++"Entry-point scripts" (`setup(entry_points={"console_scripts": ..})`) are a
++convenient way to specify executable scripts that should be installed along
++with the python package.
+ 
+-### Upgrading to 0.14
++These both work as expected when using modern setuptools. When using
++setuptools-18.5 or earlier, however, certain operations will cause
++`pkg_resources.DistributionNotFound` errors when running the entrypoint
++script, which must be resolved by re-installing the package. This happens
++when the install happens with one version, then the egg_info data is
++regenerated while a different version is checked out. Many setup.py commands
++cause egg_info to be rebuilt (including `sdist`, `wheel`, and installing into
++a different virtualenv), so this can be surprising.
+ 
+-0.14 changes the format of the version string. 0.13 and earlier used
+-hyphen-separated strings like "0.11-2-g1076c97-dirty". 0.14 and beyond use a
+-plus-separated "local version" section strings, with dot-separated
+-components, like "0.11+2.g1076c97". PEP440-strict tools did not like the old
+-format, but should be ok with the new one.
++[Bug #83](https://github.com/python-versioneer/python-versioneer/issues/83) describes
++this one, but upgrading to a newer version of setuptools should probably
++resolve it.
+ 
+-### Upgrading from 0.11 to 0.12
+ 
+-Nothing special.
++## Updating Versioneer
+ 
+-### Upgrading from 0.10 to 0.11
++To upgrade your project to a new release of Versioneer, do the following:
+ 
+-You must add a `versioneer.VCS = "git"` to your `setup.py` before re-running
+-`setup.py setup_versioneer`. This will enable the use of additional
+-version-control systems (SVN, etc) in the future.
++* install the new Versioneer (`pip install -U versioneer` or equivalent)
++* edit `setup.cfg` and `pyproject.toml`, if necessary,
++  to include any new configuration settings indicated by the release notes.
++  See [UPGRADING](./UPGRADING.md) for details.
++* re-run `versioneer install --[no-]vendor` in your source tree, to replace
++  `SRC/_version.py`
++* commit any changed files
+ 
+ ## Future Directions
+ 
+@@ -330,49 +278,101 @@ installation by editing setup.py . Alternatively, it might go the other
+ direction and include code from all supported VCS systems, reducing the
+ number of intermediate scripts.
+ 
++## Similar projects
++
++* [setuptools_scm](https://github.com/pypa/setuptools_scm/) - a non-vendored build-time
++  dependency
++* [minver](https://github.com/jbweston/miniver) - a lightweight reimplementation of
++  versioneer
++* [versioningit](https://github.com/jwodder/versioningit) - a PEP 518-based setuptools
++  plugin
+ 
+ ## License
+ 
+-To make Versioneer easier to embed, all its code is hereby released into the
+-public domain. The `_version.py` that it creates is also in the public
+-domain.
++To make Versioneer easier to embed, all its code is dedicated to the public
++domain. The `_version.py` that it creates is also in the public domain.
++Specifically, both are released under the "Unlicense", as described in
++https://unlicense.org/.
++
++[pypi-image]: https://img.shields.io/pypi/v/versioneer.svg
++[pypi-url]: https://pypi.python.org/pypi/versioneer/
++[travis-image]:
++https://img.shields.io/travis/com/python-versioneer/python-versioneer.svg
++[travis-url]: https://travis-ci.com/github/python-versioneer/python-versioneer
+ 
+ """
++# pylint:disable=invalid-name,import-outside-toplevel,missing-function-docstring
++# pylint:disable=missing-class-docstring,too-many-branches,too-many-statements
++# pylint:disable=raise-missing-from,too-many-lines,too-many-locals,import-error
++# pylint:disable=too-few-public-methods,redefined-outer-name,consider-using-with
++# pylint:disable=attribute-defined-outside-init,too-many-arguments
+ 
+-from __future__ import print_function
+-try:
+-    import configparser
+-except ImportError:
+-    import ConfigParser as configparser
++import configparser
+ import errno
+ import json
+ import os
+ import re
+ import subprocess
+ import sys
++from pathlib import Path
++from typing import Any, Callable, cast, Dict, List, Optional, Tuple, Union
++from typing import NoReturn
++import functools
++
++have_tomllib = True
++if sys.version_info >= (3, 11):
++    import tomllib
++else:
++    try:
++        import tomli as tomllib
++    except ImportError:
++        have_tomllib = False
+ 
+ 
+ class VersioneerConfig:
+-    pass
++    """Container for Versioneer configuration parameters."""
+ 
++    VCS: str
++    style: str
++    tag_prefix: str
++    versionfile_source: str
++    versionfile_build: Optional[str]
++    parentdir_prefix: Optional[str]
++    verbose: Optional[bool]
+ 
+-def get_root():
+-    # we require that all commands are run from the project root, i.e. the
+-    # directory that contains setup.py, setup.cfg, and versioneer.py .
++
++def get_root() -> str:
++    """Get the project root directory.
++
++    We require that all commands are run from the project root, i.e. the
++    directory that contains setup.py, setup.cfg, and versioneer.py .
++    """
+     root = os.path.realpath(os.path.abspath(os.getcwd()))
+     setup_py = os.path.join(root, "setup.py")
++    pyproject_toml = os.path.join(root, "pyproject.toml")
+     versioneer_py = os.path.join(root, "versioneer.py")
+-    if not (os.path.exists(setup_py) or os.path.exists(versioneer_py)):
++    if not (
++        os.path.exists(setup_py)
++        or os.path.exists(pyproject_toml)
++        or os.path.exists(versioneer_py)
++    ):
+         # allow 'python path/to/setup.py COMMAND'
+         root = os.path.dirname(os.path.realpath(os.path.abspath(sys.argv[0])))
+         setup_py = os.path.join(root, "setup.py")
++        pyproject_toml = os.path.join(root, "pyproject.toml")
+         versioneer_py = os.path.join(root, "versioneer.py")
+-    if not (os.path.exists(setup_py) or os.path.exists(versioneer_py)):
+-        err = ("Versioneer was unable to run the project root directory. "
+-               "Versioneer requires setup.py to be executed from "
+-               "its immediate directory (like 'python setup.py COMMAND'), "
+-               "or in a way that lets it use sys.argv[0] to find the root "
+-               "(like 'python path/to/setup.py COMMAND').")
++    if not (
++        os.path.exists(setup_py)
++        or os.path.exists(pyproject_toml)
++        or os.path.exists(versioneer_py)
++    ):
++        err = (
++            "Versioneer was unable to run the project root directory. "
++            "Versioneer requires setup.py to be executed from "
++            "its immediate directory (like 'python setup.py COMMAND'), "
++            "or in a way that lets it use sys.argv[0] to find the root "
++            "(like 'python path/to/setup.py COMMAND')."
++        )
+         raise VersioneerBadRootError(err)
+     try:
+         # Certain runtime workflows (setup.py install/develop in a setuptools
+@@ -381,122 +381,189 @@ def get_root():
+         # module-import table will cache the first one. So we can't use
+         # os.path.dirname(__file__), as that will find whichever
+         # versioneer.py was first imported, even in later projects.
+-        me = os.path.realpath(os.path.abspath(__file__))
+-        if os.path.splitext(me)[0] != os.path.splitext(versioneer_py)[0]:
+-            print("Warning: build in %s is using versioneer.py from %s"
+-                  % (os.path.dirname(me), versioneer_py))
++        my_path = os.path.realpath(os.path.abspath(__file__))
++        me_dir = os.path.normcase(os.path.splitext(my_path)[0])
++        vsr_dir = os.path.normcase(os.path.splitext(versioneer_py)[0])
++        if me_dir != vsr_dir and "VERSIONEER_PEP518" not in globals():
++            print(
++                "Warning: build in %s is using versioneer.py from %s"
++                % (os.path.dirname(my_path), versioneer_py)
++            )
+     except NameError:
+         pass
+     return root
+ 
+ 
+-def get_config_from_root(root):
+-    # This might raise EnvironmentError (if setup.cfg is missing), or
++def get_config_from_root(root: str) -> VersioneerConfig:
++    """Read the project setup.cfg file to determine Versioneer config."""
++    # This might raise OSError (if setup.cfg is missing), or
+     # configparser.NoSectionError (if it lacks a [versioneer] section), or
+     # configparser.NoOptionError (if it lacks "VCS="). See the docstring at
+     # the top of versioneer.py for instructions on writing your setup.cfg .
+-    setup_cfg = os.path.join(root, "setup.cfg")
+-    parser = configparser.SafeConfigParser()
+-    with open(setup_cfg, "r") as f:
+-        parser.readfp(f)
+-    VCS = parser.get("versioneer", "VCS")  # mandatory
+-
+-    def get(parser, name):
+-        if parser.has_option("versioneer", name):
+-            return parser.get("versioneer", name)
+-        return None
++    root_pth = Path(root)
++    pyproject_toml = root_pth / "pyproject.toml"
++    setup_cfg = root_pth / "setup.cfg"
++    section: Union[Dict[str, Any], configparser.SectionProxy, None] = None
++    if pyproject_toml.exists() and have_tomllib:
++        try:
++            with open(pyproject_toml, "rb") as fobj:
++                pp = tomllib.load(fobj)
++            section = pp["tool"]["versioneer"]
++        except (tomllib.TOMLDecodeError, KeyError) as e:
++            print(f"Failed to load config from {pyproject_toml}: {e}")
++            print("Try to load it from setup.cfg")
++    if not section:
++        parser = configparser.ConfigParser()
++        with open(setup_cfg) as cfg_file:
++            parser.read_file(cfg_file)
++        parser.get("versioneer", "VCS")  # raise error if missing
++
++        section = parser["versioneer"]
++
++    # `cast`` really shouldn't be used, but its simplest for the
++    # common VersioneerConfig users at the moment. We verify against
++    # `None` values elsewhere where it matters
++
+     cfg = VersioneerConfig()
+-    cfg.VCS = VCS
+-    cfg.style = get(parser, "style") or ""
+-    cfg.versionfile_source = get(parser, "versionfile_source")
+-    cfg.versionfile_build = get(parser, "versionfile_build")
+-    cfg.tag_prefix = get(parser, "tag_prefix")
+-    cfg.parentdir_prefix = get(parser, "parentdir_prefix")
+-    cfg.verbose = get(parser, "verbose")
++    cfg.VCS = section["VCS"]
++    cfg.style = section.get("style", "")
++    cfg.versionfile_source = cast(str, section.get("versionfile_source"))
++    cfg.versionfile_build = section.get("versionfile_build")
++    cfg.tag_prefix = cast(str, section.get("tag_prefix"))
++    if cfg.tag_prefix in ("''", '""', None):
++        cfg.tag_prefix = ""
++    cfg.parentdir_prefix = section.get("parentdir_prefix")
++    if isinstance(section, configparser.SectionProxy):
++        # Make sure configparser translates to bool
++        cfg.verbose = section.getboolean("verbose")
++    else:
++        cfg.verbose = section.get("verbose")
++
+     return cfg
+ 
+ 
+ class NotThisMethod(Exception):
+-    pass
++    """Exception raised if a method is not valid for the current scenario."""
++
+ 
+ # these dictionaries contain VCS-specific tools
+-LONG_VERSION_PY = {}
+-HANDLERS = {}
++LONG_VERSION_PY: Dict[str, str] = {}
++HANDLERS: Dict[str, Dict[str, Callable]] = {}
+ 
+ 
+-def register_vcs_handler(vcs, method):  # decorator
+-    def decorate(f):
+-        if vcs not in HANDLERS:
+-            HANDLERS[vcs] = {}
+-        HANDLERS[vcs][method] = f
++def register_vcs_handler(vcs: str, method: str) -> Callable:  # decorator
++    """Create decorator to mark a method as the handler of a VCS."""
++
++    def decorate(f: Callable) -> Callable:
++        """Store f in HANDLERS[vcs][method]."""
++        HANDLERS.setdefault(vcs, {})[method] = f
+         return f
++
+     return decorate
+ 
+ 
+-def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False):
++def run_command(
++    commands: List[str],
++    args: List[str],
++    cwd: Optional[str] = None,
++    verbose: bool = False,
++    hide_stderr: bool = False,
++    env: Optional[Dict[str, str]] = None,
++) -> Tuple[Optional[str], Optional[int]]:
++    """Call the given command(s)."""
+     assert isinstance(commands, list)
+-    p = None
+-    for c in commands:
++    process = None
++
++    popen_kwargs: Dict[str, Any] = {}
++    if sys.platform == "win32":
++        # This hides the console window if pythonw.exe is used
++        startupinfo = subprocess.STARTUPINFO()
++        startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
++        popen_kwargs["startupinfo"] = startupinfo
++
++    for command in commands:
+         try:
+-            dispcmd = str([c] + args)
++            dispcmd = str([command] + args)
+             # remember shell=False, so use git.cmd on windows, not just git
+-            p = subprocess.Popen([c] + args, cwd=cwd, stdout=subprocess.PIPE,
+-                                 stderr=(subprocess.PIPE if hide_stderr
+-                                         else None))
++            process = subprocess.Popen(
++                [command] + args,
++                cwd=cwd,
++                env=env,
++                stdout=subprocess.PIPE,
++                stderr=(subprocess.PIPE if hide_stderr else None),
++                **popen_kwargs,
++            )
+             break
+-        except EnvironmentError:
+-            e = sys.exc_info()[1]
++        except OSError as e:
+             if e.errno == errno.ENOENT:
+                 continue
+             if verbose:
+                 print("unable to run %s" % dispcmd)
+                 print(e)
+-            return None
++            return None, None
+     else:
+         if verbose:
+             print("unable to find command, tried %s" % (commands,))
+-        return None
+-    stdout = p.communicate()[0].strip()
+-    if sys.version_info[0] >= 3:
+-        stdout = stdout.decode()
+-    if p.returncode != 0:
++        return None, None
++    stdout = process.communicate()[0].strip().decode()
++    if process.returncode != 0:
+         if verbose:
+             print("unable to run %s (error)" % dispcmd)
+-        return None
+-    return stdout
+-LONG_VERSION_PY['git'] = '''
++            print("stdout was %s" % stdout)
++        return None, process.returncode
++    return stdout, process.returncode
++
++
++LONG_VERSION_PY[
++    "git"
++] = r'''
+ # This file helps to compute a version number in source trees obtained from
+ # git-archive tarball (such as those provided by githubs download-from-tag
+ # feature). Distribution tarballs (built by setup.py sdist) and build
+ # directories (produced by setup.py build) will contain a much shorter file
+ # that just contains the computed version number.
+ 
+-# This file is released into the public domain. Generated by
+-# versioneer-0.15 (https://github.com/warner/python-versioneer)
++# This file is released into the public domain.
++# Generated by versioneer-0.29
++# https://github.com/python-versioneer/python-versioneer
++
++"""Git implementation of _version.py."""
+ 
+ import errno
+ import os
+ import re
+ import subprocess
+ import sys
++from typing import Any, Callable, Dict, List, Optional, Tuple
++import functools
+ 
+ 
+-def get_keywords():
++def get_keywords() -> Dict[str, str]:
++    """Get the keywords needed to look up the version information."""
+     # these strings will be replaced by git during git-archive.
+     # setup.py/versioneer.py will grep for the variable names, so they must
+     # each be defined on a line of their own. _version.py will just call
+     # get_keywords().
+     git_refnames = "%(DOLLAR)sFormat:%%d%(DOLLAR)s"
+     git_full = "%(DOLLAR)sFormat:%%H%(DOLLAR)s"
+-    keywords = {"refnames": git_refnames, "full": git_full}
++    git_date = "%(DOLLAR)sFormat:%%ci%(DOLLAR)s"
++    keywords = {"refnames": git_refnames, "full": git_full, "date": git_date}
+     return keywords
+ 
+ 
+ class VersioneerConfig:
+-    pass
++    """Container for Versioneer configuration parameters."""
+ 
++    VCS: str
++    style: str
++    tag_prefix: str
++    parentdir_prefix: str
++    versionfile_source: str
++    verbose: bool
+ 
+-def get_config():
++
++def get_config() -> VersioneerConfig:
++    """Create, populate and return the VersioneerConfig() object."""
+     # these strings are filled in when 'setup.py versioneer' creates
+     # _version.py
+     cfg = VersioneerConfig()
+@@ -510,15 +577,17 @@ def get_config():
+ 
+ 
+ class NotThisMethod(Exception):
+-    pass
++    """Exception raised if a method is not valid for the current scenario."""
+ 
+ 
+-LONG_VERSION_PY = {}
+-HANDLERS = {}
++LONG_VERSION_PY: Dict[str, str] = {}
++HANDLERS: Dict[str, Dict[str, Callable]] = {}
+ 
+ 
+-def register_vcs_handler(vcs, method):  # decorator
+-    def decorate(f):
++def register_vcs_handler(vcs: str, method: str) -> Callable:  # decorator
++    """Create decorator to mark a method as the handler of a VCS."""
++    def decorate(f: Callable) -> Callable:
++        """Store f in HANDLERS[vcs][method]."""
+         if vcs not in HANDLERS:
+             HANDLERS[vcs] = {}
+         HANDLERS[vcs][method] = f
+@@ -526,91 +595,142 @@ def register_vcs_handler(vcs, method):  # decorator
+     return decorate
+ 
+ 
+-def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False):
++def run_command(
++    commands: List[str],
++    args: List[str],
++    cwd: Optional[str] = None,
++    verbose: bool = False,
++    hide_stderr: bool = False,
++    env: Optional[Dict[str, str]] = None,
++) -> Tuple[Optional[str], Optional[int]]:
++    """Call the given command(s)."""
+     assert isinstance(commands, list)
+-    p = None
+-    for c in commands:
++    process = None
++
++    popen_kwargs: Dict[str, Any] = {}
++    if sys.platform == "win32":
++        # This hides the console window if pythonw.exe is used
++        startupinfo = subprocess.STARTUPINFO()
++        startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
++        popen_kwargs["startupinfo"] = startupinfo
++
++    for command in commands:
+         try:
+-            dispcmd = str([c] + args)
++            dispcmd = str([command] + args)
+             # remember shell=False, so use git.cmd on windows, not just git
+-            p = subprocess.Popen([c] + args, cwd=cwd, stdout=subprocess.PIPE,
+-                                 stderr=(subprocess.PIPE if hide_stderr
+-                                         else None))
++            process = subprocess.Popen([command] + args, cwd=cwd, env=env,
++                                       stdout=subprocess.PIPE,
++                                       stderr=(subprocess.PIPE if hide_stderr
++                                               else None), **popen_kwargs)
+             break
+-        except EnvironmentError:
+-            e = sys.exc_info()[1]
++        except OSError as e:
+             if e.errno == errno.ENOENT:
+                 continue
+             if verbose:
+                 print("unable to run %%s" %% dispcmd)
+                 print(e)
+-            return None
++            return None, None
+     else:
+         if verbose:
+             print("unable to find command, tried %%s" %% (commands,))
+-        return None
+-    stdout = p.communicate()[0].strip()
+-    if sys.version_info[0] >= 3:
+-        stdout = stdout.decode()
+-    if p.returncode != 0:
++        return None, None
++    stdout = process.communicate()[0].strip().decode()
++    if process.returncode != 0:
+         if verbose:
+             print("unable to run %%s (error)" %% dispcmd)
+-        return None
+-    return stdout
++            print("stdout was %%s" %% stdout)
++        return None, process.returncode
++    return stdout, process.returncode
++
++
++def versions_from_parentdir(
++    parentdir_prefix: str,
++    root: str,
++    verbose: bool,
++) -> Dict[str, Any]:
++    """Try to determine the version from the parent directory name.
++
++    Source tarballs conventionally unpack into a directory that includes both
++    the project name and a version string. We will also support searching up
++    two directory levels for an appropriately named parent directory
++    """
++    rootdirs = []
++
++    for _ in range(3):
++        dirname = os.path.basename(root)
++        if dirname.startswith(parentdir_prefix):
++            return {"version": dirname[len(parentdir_prefix):],
++                    "full-revisionid": None,
++                    "dirty": False, "error": None, "date": None}
++        rootdirs.append(root)
++        root = os.path.dirname(root)  # up a level
+ 
+-
+-def versions_from_parentdir(parentdir_prefix, root, verbose):
+-    # Source tarballs conventionally unpack into a directory that includes
+-    # both the project name and a version string.
+-    dirname = os.path.basename(root)
+-    if not dirname.startswith(parentdir_prefix):
+-        if verbose:
+-            print("guessing rootdir is '%%s', but '%%s' doesn't start with "
+-                  "prefix '%%s'" %% (root, dirname, parentdir_prefix))
+-        raise NotThisMethod("rootdir doesn't start with parentdir_prefix")
+-    return {"version": dirname[len(parentdir_prefix):],
+-            "full-revisionid": None,
+-            "dirty": False, "error": None}
++    if verbose:
++        print("Tried directories %%s but none started with prefix %%s" %%
++              (str(rootdirs), parentdir_prefix))
++    raise NotThisMethod("rootdir doesn't start with parentdir_prefix")
+ 
+ 
+ @register_vcs_handler("git", "get_keywords")
+-def git_get_keywords(versionfile_abs):
++def git_get_keywords(versionfile_abs: str) -> Dict[str, str]:
++    """Extract version information from the given file."""
+     # the code embedded in _version.py can just fetch the value of these
+     # keywords. When used from setup.py, we don't want to import _version.py,
+     # so we do it with a regexp instead. This function is not used from
+     # _version.py.
+-    keywords = {}
++    keywords: Dict[str, str] = {}
+     try:
+-        f = open(versionfile_abs, "r")
+-        for line in f.readlines():
+-            if line.strip().startswith("git_refnames ="):
+-                mo = re.search(r'=\s*"(.*)"', line)
+-                if mo:
+-                    keywords["refnames"] = mo.group(1)
+-            if line.strip().startswith("git_full ="):
+-                mo = re.search(r'=\s*"(.*)"', line)
+-                if mo:
+-                    keywords["full"] = mo.group(1)
+-        f.close()
+-    except EnvironmentError:
++        with open(versionfile_abs, "r") as fobj:
++            for line in fobj:
++                if line.strip().startswith("git_refnames ="):
++                    mo = re.search(r'=\s*"(.*)"', line)
++                    if mo:
++                        keywords["refnames"] = mo.group(1)
++                if line.strip().startswith("git_full ="):
++                    mo = re.search(r'=\s*"(.*)"', line)
++                    if mo:
++                        keywords["full"] = mo.group(1)
++                if line.strip().startswith("git_date ="):
++                    mo = re.search(r'=\s*"(.*)"', line)
++                    if mo:
++                        keywords["date"] = mo.group(1)
++    except OSError:
+         pass
+     return keywords
+ 
+ 
+ @register_vcs_handler("git", "keywords")
+-def git_versions_from_keywords(keywords, tag_prefix, verbose):
+-    if not keywords:
+-        raise NotThisMethod("no keywords at all, weird")
++def git_versions_from_keywords(
++    keywords: Dict[str, str],
++    tag_prefix: str,
++    verbose: bool,
++) -> Dict[str, Any]:
++    """Get version information from git keywords."""
++    if "refnames" not in keywords:
++        raise NotThisMethod("Short version file found")
++    date = keywords.get("date")
++    if date is not None:
++        # Use only the last line.  Previous lines may contain GPG signature
++        # information.
++        date = date.splitlines()[-1]
++
++        # git-2.2.0 added "%%cI", which expands to an ISO-8601 -compliant
++        # datestamp. However we prefer "%%ci" (which expands to an "ISO-8601
++        # -like" string, which we must then edit to make compliant), because
++        # it's been around since git-1.5.3, and it's too difficult to
++        # discover which version we're using, or to work around using an
++        # older one.
++        date = date.strip().replace(" ", "T", 1).replace(" ", "", 1)
+     refnames = keywords["refnames"].strip()
+     if refnames.startswith("$Format"):
+         if verbose:
+             print("keywords are unexpanded, not using")
+         raise NotThisMethod("unexpanded keywords, not a git-archive tarball")
+-    refs = set([r.strip() for r in refnames.strip("()").split(",")])
++    refs = {r.strip() for r in refnames.strip("()").split(",")}
+     # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of
+     # just "foo-1.0". If we see a "tag: " prefix, prefer those.
+     TAG = "tag: "
+-    tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)])
++    tags = {r[len(TAG):] for r in refs if r.startswith(TAG)}
+     if not tags:
+         # Either we're using git < 1.8.3, or there really are no tags. We use
+         # a heuristic: assume all version tags have a digit. The old git %%d
+@@ -619,63 +739,118 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose):
+         # between branches and tags. By ignoring refnames without digits, we
+         # filter out many common branch names like "release" and
+         # "stabilization", as well as "HEAD" and "master".
+-        tags = set([r for r in refs if re.search(r'\d', r)])
++        tags = {r for r in refs if re.search(r'\d', r)}
+         if verbose:
+-            print("discarding '%%s', no digits" %% ",".join(refs-tags))
++            print("discarding '%%s', no digits" %% ",".join(refs - tags))
+     if verbose:
+         print("likely tags: %%s" %% ",".join(sorted(tags)))
+     for ref in sorted(tags):
+         # sorting will prefer e.g. "2.0" over "2.0rc1"
+         if ref.startswith(tag_prefix):
+             r = ref[len(tag_prefix):]
++            # Filter out refs that exactly match prefix or that don't start
++            # with a number once the prefix is stripped (mostly a concern
++            # when prefix is '')
++            if not re.match(r'\d', r):
++                continue
+             if verbose:
+                 print("picking %%s" %% r)
+             return {"version": r,
+                     "full-revisionid": keywords["full"].strip(),
+-                    "dirty": False, "error": None
+-                    }
++                    "dirty": False, "error": None,
++                    "date": date}
+     # no suitable tags, so version is "0+unknown", but full hex is still there
+     if verbose:
+         print("no suitable tags, using unknown + full revision id")
+     return {"version": "0+unknown",
+             "full-revisionid": keywords["full"].strip(),
+-            "dirty": False, "error": "no suitable tags"}
++            "dirty": False, "error": "no suitable tags", "date": None}
+ 
+ 
+ @register_vcs_handler("git", "pieces_from_vcs")
+-def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
+-    # this runs 'git' from the root of the source tree. This only gets called
+-    # if the git-archive 'subst' keywords were *not* expanded, and
+-    # _version.py hasn't already been rewritten with a short version string,
+-    # meaning we're inside a checked out source tree.
+-
+-    if not os.path.exists(os.path.join(root, ".git")):
+-        if verbose:
+-            print("no .git in %%s" %% root)
+-        raise NotThisMethod("no .git directory")
+-
++def git_pieces_from_vcs(
++    tag_prefix: str,
++    root: str,
++    verbose: bool,
++    runner: Callable = run_command
++) -> Dict[str, Any]:
++    """Get version from 'git describe' in the root of the source tree.
++
++    This only gets called if the git-archive 'subst' keywords were *not*
++    expanded, and _version.py hasn't already been rewritten with a short
++    version string, meaning we're inside a checked out source tree.
++    """
+     GITS = ["git"]
+     if sys.platform == "win32":
+         GITS = ["git.cmd", "git.exe"]
+-    # if there is a tag, this yields TAG-NUM-gHEX[-dirty]
+-    # if there are no tags, this yields HEX[-dirty] (no NUM)
+-    describe_out = run_command(GITS, ["describe", "--tags", "--dirty",
+-                                      "--always", "--long"],
+-                               cwd=root)
++
++    # GIT_DIR can interfere with correct operation of Versioneer.
++    # It may be intended to be passed to the Versioneer-versioned project,
++    # but that should not change where we get our version from.
++    env = os.environ.copy()
++    env.pop("GIT_DIR", None)
++    runner = functools.partial(runner, env=env)
++
++    _, rc = runner(GITS, ["rev-parse", "--git-dir"], cwd=root,
++                   hide_stderr=not verbose)
++    if rc != 0:
++        if verbose:
++            print("Directory %%s not under git control" %% root)
++        raise NotThisMethod("'git rev-parse --git-dir' returned error")
++
++    # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty]
++    # if there isn't one, this yields HEX[-dirty] (no NUM)
++    describe_out, rc = runner(GITS, [
++        "describe", "--tags", "--dirty", "--always", "--long",
++        "--match", f"{tag_prefix}[[:digit:]]*"
++    ], cwd=root)
+     # --long was added in git-1.5.5
+     if describe_out is None:
+         raise NotThisMethod("'git describe' failed")
+     describe_out = describe_out.strip()
+-    full_out = run_command(GITS, ["rev-parse", "HEAD"], cwd=root)
++    full_out, rc = runner(GITS, ["rev-parse", "HEAD"], cwd=root)
+     if full_out is None:
+         raise NotThisMethod("'git rev-parse' failed")
+     full_out = full_out.strip()
+ 
+-    pieces = {}
++    pieces: Dict[str, Any] = {}
+     pieces["long"] = full_out
+     pieces["short"] = full_out[:7]  # maybe improved later
+     pieces["error"] = None
+ 
++    branch_name, rc = runner(GITS, ["rev-parse", "--abbrev-ref", "HEAD"],
++                             cwd=root)
++    # --abbrev-ref was added in git-1.6.3
++    if rc != 0 or branch_name is None:
++        raise NotThisMethod("'git rev-parse --abbrev-ref' returned error")
++    branch_name = branch_name.strip()
++
++    if branch_name == "HEAD":
++        # If we aren't exactly on a branch, pick a branch which represents
++        # the current commit. If all else fails, we are on a branchless
++        # commit.
++        branches, rc = runner(GITS, ["branch", "--contains"], cwd=root)
++        # --contains was added in git-1.5.4
++        if rc != 0 or branches is None:
++            raise NotThisMethod("'git branch --contains' returned error")
++        branches = branches.split("\n")
++
++        # Remove the first line if we're running detached
++        if "(" in branches[0]:
++            branches.pop(0)
++
++        # Strip off the leading "* " from the list of branches.
++        branches = [branch[2:] for branch in branches]
++        if "master" in branches:
++            branch_name = "master"
++        elif not branches:
++            branch_name = None
++        else:
++            # Pick the first branch that is returned. Good or bad.
++            branch_name = branches[0]
++
++    pieces["branch"] = branch_name
++
+     # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty]
+     # TAG might have hyphens.
+     git_describe = describe_out
+@@ -692,7 +867,7 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
+         # TAG-NUM-gHEX
+         mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe)
+         if not mo:
+-            # unparseable. Maybe git-describe is misbehaving?
++            # unparsable. Maybe git-describe is misbehaving?
+             pieces["error"] = ("unable to parse git-describe output: '%%s'"
+                                %% describe_out)
+             return pieces
+@@ -717,27 +892,35 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
+     else:
+         # HEX: no tags
+         pieces["closest-tag"] = None
+-        count_out = run_command(GITS, ["rev-list", "HEAD", "--count"],
+-                                cwd=root)
+-        pieces["distance"] = int(count_out)  # total number of commits
++        out, rc = runner(GITS, ["rev-list", "HEAD", "--left-right"], cwd=root)
++        pieces["distance"] = len(out.split())  # total number of commits
++
++    # commit date: see ISO-8601 comment in git_versions_from_keywords()
++    date = runner(GITS, ["show", "-s", "--format=%%ci", "HEAD"], cwd=root)[0].strip()
++    # Use only the last line.  Previous lines may contain GPG signature
++    # information.
++    date = date.splitlines()[-1]
++    pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1)
+ 
+     return pieces
+ 
+ 
+-def plus_or_dot(pieces):
++def plus_or_dot(pieces: Dict[str, Any]) -> str:
++    """Return a + if we don't already have one, else return a ."""
+     if "+" in pieces.get("closest-tag", ""):
+         return "."
+     return "+"
+ 
+ 
+-def render_pep440(pieces):
+-    # now build up version string, with post-release "local version
+-    # identifier". Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you
+-    # get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty
++def render_pep440(pieces: Dict[str, Any]) -> str:
++    """Build up version string, with post-release "local version identifier".
+ 
+-    # exceptions:
+-    # 1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty]
++    Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you
++    get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty
+ 
++    Exceptions:
++    1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty]
++    """
+     if pieces["closest-tag"]:
+         rendered = pieces["closest-tag"]
+         if pieces["distance"] or pieces["dirty"]:
+@@ -754,31 +937,80 @@ def render_pep440(pieces):
+     return rendered
+ 
+ 
+-def render_pep440_pre(pieces):
+-    # TAG[.post.devDISTANCE] . No -dirty
++def render_pep440_branch(pieces: Dict[str, Any]) -> str:
++    """TAG[[.dev0]+DISTANCE.gHEX[.dirty]] .
+ 
+-    # exceptions:
+-    # 1: no tags. 0.post.devDISTANCE
++    The ".dev0" means not master branch. Note that .dev0 sorts backwards
++    (a feature branch will appear "older" than the master branch).
+ 
++    Exceptions:
++    1: no tags. 0[.dev0]+untagged.DISTANCE.gHEX[.dirty]
++    """
+     if pieces["closest-tag"]:
+         rendered = pieces["closest-tag"]
++        if pieces["distance"] or pieces["dirty"]:
++            if pieces["branch"] != "master":
++                rendered += ".dev0"
++            rendered += plus_or_dot(pieces)
++            rendered += "%%d.g%%s" %% (pieces["distance"], pieces["short"])
++            if pieces["dirty"]:
++                rendered += ".dirty"
++    else:
++        # exception #1
++        rendered = "0"
++        if pieces["branch"] != "master":
++            rendered += ".dev0"
++        rendered += "+untagged.%%d.g%%s" %% (pieces["distance"],
++                                          pieces["short"])
++        if pieces["dirty"]:
++            rendered += ".dirty"
++    return rendered
++
++
++def pep440_split_post(ver: str) -> Tuple[str, Optional[int]]:
++    """Split pep440 version string at the post-release segment.
++
++    Returns the release segments before the post-release and the
++    post-release version number (or -1 if no post-release segment is present).
++    """
++    vc = str.split(ver, ".post")
++    return vc[0], int(vc[1] or 0) if len(vc) == 2 else None
++
++
++def render_pep440_pre(pieces: Dict[str, Any]) -> str:
++    """TAG[.postN.devDISTANCE] -- No -dirty.
++
++    Exceptions:
++    1: no tags. 0.post0.devDISTANCE
++    """
++    if pieces["closest-tag"]:
+         if pieces["distance"]:
+-            rendered += ".post.dev%%d" %% pieces["distance"]
++            # update the post release segment
++            tag_version, post_version = pep440_split_post(pieces["closest-tag"])
++            rendered = tag_version
++            if post_version is not None:
++                rendered += ".post%%d.dev%%d" %% (post_version + 1, pieces["distance"])
++            else:
++                rendered += ".post0.dev%%d" %% (pieces["distance"])
++        else:
++            # no commits, use the tag as the version
++            rendered = pieces["closest-tag"]
+     else:
+         # exception #1
+-        rendered = "0.post.dev%%d" %% pieces["distance"]
++        rendered = "0.post0.dev%%d" %% pieces["distance"]
+     return rendered
+ 
+ 
+-def render_pep440_post(pieces):
+-    # TAG[.postDISTANCE[.dev0]+gHEX] . The ".dev0" means dirty. Note that
+-    # .dev0 sorts backwards (a dirty tree will appear "older" than the
+-    # corresponding clean one), but you shouldn't be releasing software with
+-    # -dirty anyways.
++def render_pep440_post(pieces: Dict[str, Any]) -> str:
++    """TAG[.postDISTANCE[.dev0]+gHEX] .
+ 
+-    # exceptions:
+-    # 1: no tags. 0.postDISTANCE[.dev0]
++    The ".dev0" means dirty. Note that .dev0 sorts backwards
++    (a dirty tree will appear "older" than the corresponding clean one),
++    but you shouldn't be releasing software with -dirty anyways.
+ 
++    Exceptions:
++    1: no tags. 0.postDISTANCE[.dev0]
++    """
+     if pieces["closest-tag"]:
+         rendered = pieces["closest-tag"]
+         if pieces["distance"] or pieces["dirty"]:
+@@ -796,12 +1028,43 @@ def render_pep440_post(pieces):
+     return rendered
+ 
+ 
+-def render_pep440_old(pieces):
+-    # TAG[.postDISTANCE[.dev0]] . The ".dev0" means dirty.
++def render_pep440_post_branch(pieces: Dict[str, Any]) -> str:
++    """TAG[.postDISTANCE[.dev0]+gHEX[.dirty]] .
++
++    The ".dev0" means not master branch.
++
++    Exceptions:
++    1: no tags. 0.postDISTANCE[.dev0]+gHEX[.dirty]
++    """
++    if pieces["closest-tag"]:
++        rendered = pieces["closest-tag"]
++        if pieces["distance"] or pieces["dirty"]:
++            rendered += ".post%%d" %% pieces["distance"]
++            if pieces["branch"] != "master":
++                rendered += ".dev0"
++            rendered += plus_or_dot(pieces)
++            rendered += "g%%s" %% pieces["short"]
++            if pieces["dirty"]:
++                rendered += ".dirty"
++    else:
++        # exception #1
++        rendered = "0.post%%d" %% pieces["distance"]
++        if pieces["branch"] != "master":
++            rendered += ".dev0"
++        rendered += "+g%%s" %% pieces["short"]
++        if pieces["dirty"]:
++            rendered += ".dirty"
++    return rendered
++
++
++def render_pep440_old(pieces: Dict[str, Any]) -> str:
++    """TAG[.postDISTANCE[.dev0]] .
+ 
+-    # exceptions:
+-    # 1: no tags. 0.postDISTANCE[.dev0]
++    The ".dev0" means dirty.
+ 
++    Exceptions:
++    1: no tags. 0.postDISTANCE[.dev0]
++    """
+     if pieces["closest-tag"]:
+         rendered = pieces["closest-tag"]
+         if pieces["distance"] or pieces["dirty"]:
+@@ -816,13 +1079,14 @@ def render_pep440_old(pieces):
+     return rendered
+ 
+ 
+-def render_git_describe(pieces):
+-    # TAG[-DISTANCE-gHEX][-dirty], like 'git describe --tags --dirty
+-    # --always'
++def render_git_describe(pieces: Dict[str, Any]) -> str:
++    """TAG[-DISTANCE-gHEX][-dirty].
+ 
+-    # exceptions:
+-    # 1: no tags. HEX[-dirty]  (note: no 'g' prefix)
++    Like 'git describe --tags --dirty --always'.
+ 
++    Exceptions:
++    1: no tags. HEX[-dirty]  (note: no 'g' prefix)
++    """
+     if pieces["closest-tag"]:
+         rendered = pieces["closest-tag"]
+         if pieces["distance"]:
+@@ -835,13 +1099,15 @@ def render_git_describe(pieces):
+     return rendered
+ 
+ 
+-def render_git_describe_long(pieces):
+-    # TAG-DISTANCE-gHEX[-dirty], like 'git describe --tags --dirty
+-    # --always -long'. The distance/hash is unconditional.
++def render_git_describe_long(pieces: Dict[str, Any]) -> str:
++    """TAG-DISTANCE-gHEX[-dirty].
+ 
+-    # exceptions:
+-    # 1: no tags. HEX[-dirty]  (note: no 'g' prefix)
++    Like 'git describe --tags --dirty --always -long'.
++    The distance/hash is unconditional.
+ 
++    Exceptions:
++    1: no tags. HEX[-dirty]  (note: no 'g' prefix)
++    """
+     if pieces["closest-tag"]:
+         rendered = pieces["closest-tag"]
+         rendered += "-%%d-g%%s" %% (pieces["distance"], pieces["short"])
+@@ -853,22 +1119,28 @@ def render_git_describe_long(pieces):
+     return rendered
+ 
+ 
+-def render(pieces, style):
++def render(pieces: Dict[str, Any], style: str) -> Dict[str, Any]:
++    """Render the given version pieces into the requested style."""
+     if pieces["error"]:
+         return {"version": "unknown",
+                 "full-revisionid": pieces.get("long"),
+                 "dirty": None,
+-                "error": pieces["error"]}
++                "error": pieces["error"],
++                "date": None}
+ 
+     if not style or style == "default":
+         style = "pep440"  # the default
+ 
+     if style == "pep440":
+         rendered = render_pep440(pieces)
++    elif style == "pep440-branch":
++        rendered = render_pep440_branch(pieces)
+     elif style == "pep440-pre":
+         rendered = render_pep440_pre(pieces)
+     elif style == "pep440-post":
+         rendered = render_pep440_post(pieces)
++    elif style == "pep440-post-branch":
++        rendered = render_pep440_post_branch(pieces)
+     elif style == "pep440-old":
+         rendered = render_pep440_old(pieces)
+     elif style == "git-describe":
+@@ -879,10 +1151,12 @@ def render(pieces, style):
+         raise ValueError("unknown style '%%s'" %% style)
+ 
+     return {"version": rendered, "full-revisionid": pieces["long"],
+-            "dirty": pieces["dirty"], "error": None}
++            "dirty": pieces["dirty"], "error": None,
++            "date": pieces.get("date")}
+ 
+ 
+-def get_versions():
++def get_versions() -> Dict[str, Any]:
++    """Get version information or return default if unable to do so."""
+     # I am in _version.py, which lives at ROOT/VERSIONFILE_SOURCE. If we have
+     # __file__, we can work backwards from there to the root. Some
+     # py2exe/bbfreeze/non-CPython implementations don't do __file__, in which
+@@ -902,12 +1176,13 @@ def get_versions():
+         # versionfile_source is the relative path from the top of the source
+         # tree (where the .git directory might live) to this file. Invert
+         # this to find the root from __file__.
+-        for i in cfg.versionfile_source.split('/'):
++        for _ in cfg.versionfile_source.split('/'):
+             root = os.path.dirname(root)
+     except NameError:
+         return {"version": "0+unknown", "full-revisionid": None,
+                 "dirty": None,
+-                "error": "unable to find root of source tree"}
++                "error": "unable to find root of source tree",
++                "date": None}
+ 
+     try:
+         pieces = git_pieces_from_vcs(cfg.tag_prefix, root, verbose)
+@@ -923,48 +1198,70 @@ def get_versions():
+ 
+     return {"version": "0+unknown", "full-revisionid": None,
+             "dirty": None,
+-            "error": "unable to compute version"}
++            "error": "unable to compute version", "date": None}
+ '''
+ 
+ 
+ @register_vcs_handler("git", "get_keywords")
+-def git_get_keywords(versionfile_abs):
++def git_get_keywords(versionfile_abs: str) -> Dict[str, str]:
++    """Extract version information from the given file."""
+     # the code embedded in _version.py can just fetch the value of these
+     # keywords. When used from setup.py, we don't want to import _version.py,
+     # so we do it with a regexp instead. This function is not used from
+     # _version.py.
+-    keywords = {}
++    keywords: Dict[str, str] = {}
+     try:
+-        f = open(versionfile_abs, "r")
+-        for line in f.readlines():
+-            if line.strip().startswith("git_refnames ="):
+-                mo = re.search(r'=\s*"(.*)"', line)
+-                if mo:
+-                    keywords["refnames"] = mo.group(1)
+-            if line.strip().startswith("git_full ="):
+-                mo = re.search(r'=\s*"(.*)"', line)
+-                if mo:
+-                    keywords["full"] = mo.group(1)
+-        f.close()
+-    except EnvironmentError:
++        with open(versionfile_abs, "r") as fobj:
++            for line in fobj:
++                if line.strip().startswith("git_refnames ="):
++                    mo = re.search(r'=\s*"(.*)"', line)
++                    if mo:
++                        keywords["refnames"] = mo.group(1)
++                if line.strip().startswith("git_full ="):
++                    mo = re.search(r'=\s*"(.*)"', line)
++                    if mo:
++                        keywords["full"] = mo.group(1)
++                if line.strip().startswith("git_date ="):
++                    mo = re.search(r'=\s*"(.*)"', line)
++                    if mo:
++                        keywords["date"] = mo.group(1)
++    except OSError:
+         pass
+     return keywords
+ 
+ 
+ @register_vcs_handler("git", "keywords")
+-def git_versions_from_keywords(keywords, tag_prefix, verbose):
+-    if not keywords:
+-        raise NotThisMethod("no keywords at all, weird")
++def git_versions_from_keywords(
++    keywords: Dict[str, str],
++    tag_prefix: str,
++    verbose: bool,
++) -> Dict[str, Any]:
++    """Get version information from git keywords."""
++    if "refnames" not in keywords:
++        raise NotThisMethod("Short version file found")
++    date = keywords.get("date")
++    if date is not None:
++        # Use only the last line.  Previous lines may contain GPG signature
++        # information.
++        date = date.splitlines()[-1]
++
++        # git-2.2.0 added "%cI", which expands to an ISO-8601 -compliant
++        # datestamp. However we prefer "%ci" (which expands to an "ISO-8601
++        # -like" string, which we must then edit to make compliant), because
++        # it's been around since git-1.5.3, and it's too difficult to
++        # discover which version we're using, or to work around using an
++        # older one.
++        date = date.strip().replace(" ", "T", 1).replace(" ", "", 1)
+     refnames = keywords["refnames"].strip()
+     if refnames.startswith("$Format"):
+         if verbose:
+             print("keywords are unexpanded, not using")
+         raise NotThisMethod("unexpanded keywords, not a git-archive tarball")
+-    refs = set([r.strip() for r in refnames.strip("()").split(",")])
++    refs = {r.strip() for r in refnames.strip("()").split(",")}
+     # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of
+     # just "foo-1.0". If we see a "tag: " prefix, prefer those.
+     TAG = "tag: "
+-    tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)])
++    tags = {r[len(TAG) :] for r in refs if r.startswith(TAG)}
+     if not tags:
+         # Either we're using git < 1.8.3, or there really are no tags. We use
+         # a heuristic: assume all version tags have a digit. The old git %d
+@@ -973,63 +1270,129 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose):
+         # between branches and tags. By ignoring refnames without digits, we
+         # filter out many common branch names like "release" and
+         # "stabilization", as well as "HEAD" and "master".
+-        tags = set([r for r in refs if re.search(r'\d', r)])
++        tags = {r for r in refs if re.search(r"\d", r)}
+         if verbose:
+-            print("discarding '%s', no digits" % ",".join(refs-tags))
++            print("discarding '%s', no digits" % ",".join(refs - tags))
+     if verbose:
+         print("likely tags: %s" % ",".join(sorted(tags)))
+     for ref in sorted(tags):
+         # sorting will prefer e.g. "2.0" over "2.0rc1"
+         if ref.startswith(tag_prefix):
+-            r = ref[len(tag_prefix):]
++            r = ref[len(tag_prefix) :]
++            # Filter out refs that exactly match prefix or that don't start
++            # with a number once the prefix is stripped (mostly a concern
++            # when prefix is '')
++            if not re.match(r"\d", r):
++                continue
+             if verbose:
+                 print("picking %s" % r)
+-            return {"version": r,
+-                    "full-revisionid": keywords["full"].strip(),
+-                    "dirty": False, "error": None
+-                    }
++            return {
++                "version": r,
++                "full-revisionid": keywords["full"].strip(),
++                "dirty": False,
++                "error": None,
++                "date": date,
++            }
+     # no suitable tags, so version is "0+unknown", but full hex is still there
+     if verbose:
+         print("no suitable tags, using unknown + full revision id")
+-    return {"version": "0+unknown",
+-            "full-revisionid": keywords["full"].strip(),
+-            "dirty": False, "error": "no suitable tags"}
++    return {
++        "version": "0+unknown",
++        "full-revisionid": keywords["full"].strip(),
++        "dirty": False,
++        "error": "no suitable tags",
++        "date": None,
++    }
+ 
+ 
+ @register_vcs_handler("git", "pieces_from_vcs")
+-def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
+-    # this runs 'git' from the root of the source tree. This only gets called
+-    # if the git-archive 'subst' keywords were *not* expanded, and
+-    # _version.py hasn't already been rewritten with a short version string,
+-    # meaning we're inside a checked out source tree.
+-
+-    if not os.path.exists(os.path.join(root, ".git")):
+-        if verbose:
+-            print("no .git in %s" % root)
+-        raise NotThisMethod("no .git directory")
+-
++def git_pieces_from_vcs(
++    tag_prefix: str, root: str, verbose: bool, runner: Callable = run_command
++) -> Dict[str, Any]:
++    """Get version from 'git describe' in the root of the source tree.
++
++    This only gets called if the git-archive 'subst' keywords were *not*
++    expanded, and _version.py hasn't already been rewritten with a short
++    version string, meaning we're inside a checked out source tree.
++    """
+     GITS = ["git"]
+     if sys.platform == "win32":
+         GITS = ["git.cmd", "git.exe"]
+-    # if there is a tag, this yields TAG-NUM-gHEX[-dirty]
+-    # if there are no tags, this yields HEX[-dirty] (no NUM)
+-    describe_out = run_command(GITS, ["describe", "--tags", "--dirty",
+-                                      "--always", "--long"],
+-                               cwd=root)
++
++    # GIT_DIR can interfere with correct operation of Versioneer.
++    # It may be intended to be passed to the Versioneer-versioned project,
++    # but that should not change where we get our version from.
++    env = os.environ.copy()
++    env.pop("GIT_DIR", None)
++    runner = functools.partial(runner, env=env)
++
++    _, rc = runner(GITS, ["rev-parse", "--git-dir"], cwd=root, hide_stderr=not verbose)
++    if rc != 0:
++        if verbose:
++            print("Directory %s not under git control" % root)
++        raise NotThisMethod("'git rev-parse --git-dir' returned error")
++
++    # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty]
++    # if there isn't one, this yields HEX[-dirty] (no NUM)
++    describe_out, rc = runner(
++        GITS,
++        [
++            "describe",
++            "--tags",
++            "--dirty",
++            "--always",
++            "--long",
++            "--match",
++            f"{tag_prefix}[[:digit:]]*",
++        ],
++        cwd=root,
++    )
+     # --long was added in git-1.5.5
+     if describe_out is None:
+         raise NotThisMethod("'git describe' failed")
+     describe_out = describe_out.strip()
+-    full_out = run_command(GITS, ["rev-parse", "HEAD"], cwd=root)
++    full_out, rc = runner(GITS, ["rev-parse", "HEAD"], cwd=root)
+     if full_out is None:
+         raise NotThisMethod("'git rev-parse' failed")
+     full_out = full_out.strip()
+ 
+-    pieces = {}
++    pieces: Dict[str, Any] = {}
+     pieces["long"] = full_out
+     pieces["short"] = full_out[:7]  # maybe improved later
+     pieces["error"] = None
+ 
++    branch_name, rc = runner(GITS, ["rev-parse", "--abbrev-ref", "HEAD"], cwd=root)
++    # --abbrev-ref was added in git-1.6.3
++    if rc != 0 or branch_name is None:
++        raise NotThisMethod("'git rev-parse --abbrev-ref' returned error")
++    branch_name = branch_name.strip()
++
++    if branch_name == "HEAD":
++        # If we aren't exactly on a branch, pick a branch which represents
++        # the current commit. If all else fails, we are on a branchless
++        # commit.
++        branches, rc = runner(GITS, ["branch", "--contains"], cwd=root)
++        # --contains was added in git-1.5.4
++        if rc != 0 or branches is None:
++            raise NotThisMethod("'git branch --contains' returned error")
++        branches = branches.split("\n")
++
++        # Remove the first line if we're running detached
++        if "(" in branches[0]:
++            branches.pop(0)
++
++        # Strip off the leading "* " from the list of branches.
++        branches = [branch[2:] for branch in branches]
++        if "master" in branches:
++            branch_name = "master"
++        elif not branches:
++            branch_name = None
++        else:
++            # Pick the first branch that is returned. Good or bad.
++            branch_name = branches[0]
++
++    pieces["branch"] = branch_name
++
+     # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty]
+     # TAG might have hyphens.
+     git_describe = describe_out
+@@ -1038,17 +1401,16 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
+     dirty = git_describe.endswith("-dirty")
+     pieces["dirty"] = dirty
+     if dirty:
+-        git_describe = git_describe[:git_describe.rindex("-dirty")]
++        git_describe = git_describe[: git_describe.rindex("-dirty")]
+ 
+     # now we have TAG-NUM-gHEX or HEX
+ 
+     if "-" in git_describe:
+         # TAG-NUM-gHEX
+-        mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe)
++        mo = re.search(r"^(.+)-(\d+)-g([0-9a-f]+)$", git_describe)
+         if not mo:
+-            # unparseable. Maybe git-describe is misbehaving?
+-            pieces["error"] = ("unable to parse git-describe output: '%s'"
+-                               % describe_out)
++            # unparsable. Maybe git-describe is misbehaving?
++            pieces["error"] = "unable to parse git-describe output: '%s'" % describe_out
+             return pieces
+ 
+         # tag
+@@ -1057,10 +1419,12 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
+             if verbose:
+                 fmt = "tag '%s' doesn't start with prefix '%s'"
+                 print(fmt % (full_tag, tag_prefix))
+-            pieces["error"] = ("tag '%s' doesn't start with prefix '%s'"
+-                               % (full_tag, tag_prefix))
++            pieces["error"] = "tag '%s' doesn't start with prefix '%s'" % (
++                full_tag,
++                tag_prefix,
++            )
+             return pieces
+-        pieces["closest-tag"] = full_tag[len(tag_prefix):]
++        pieces["closest-tag"] = full_tag[len(tag_prefix) :]
+ 
+         # distance: number of commits since tag
+         pieces["distance"] = int(mo.group(2))
+@@ -1071,67 +1435,98 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
+     else:
+         # HEX: no tags
+         pieces["closest-tag"] = None
+-        count_out = run_command(GITS, ["rev-list", "HEAD", "--count"],
+-                                cwd=root)
+-        pieces["distance"] = int(count_out)  # total number of commits
++        out, rc = runner(GITS, ["rev-list", "HEAD", "--left-right"], cwd=root)
++        pieces["distance"] = len(out.split())  # total number of commits
++
++    # commit date: see ISO-8601 comment in git_versions_from_keywords()
++    date = runner(GITS, ["show", "-s", "--format=%ci", "HEAD"], cwd=root)[0].strip()
++    # Use only the last line.  Previous lines may contain GPG signature
++    # information.
++    date = date.splitlines()[-1]
++    pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1)
+ 
+     return pieces
+ 
+ 
+-def do_vcs_install(manifest_in, versionfile_source, ipy):
++def do_vcs_install(versionfile_source: str, ipy: Optional[str]) -> None:
++    """Git-specific installation logic for Versioneer.
++
++    For Git, this means creating/changing .gitattributes to mark _version.py
++    for export-subst keyword substitution.
++    """
+     GITS = ["git"]
+     if sys.platform == "win32":
+         GITS = ["git.cmd", "git.exe"]
+-    files = [manifest_in, versionfile_source]
++    files = [versionfile_source]
+     if ipy:
+         files.append(ipy)
+-    try:
+-        me = __file__
+-        if me.endswith(".pyc") or me.endswith(".pyo"):
+-            me = os.path.splitext(me)[0] + ".py"
+-        versioneer_file = os.path.relpath(me)
+-    except NameError:
+-        versioneer_file = "versioneer.py"
+-    files.append(versioneer_file)
++    if "VERSIONEER_PEP518" not in globals():
++        try:
++            my_path = __file__
++            if my_path.endswith((".pyc", ".pyo")):
++                my_path = os.path.splitext(my_path)[0] + ".py"
++            versioneer_file = os.path.relpath(my_path)
++        except NameError:
++            versioneer_file = "versioneer.py"
++        files.append(versioneer_file)
+     present = False
+     try:
+-        f = open(".gitattributes", "r")
+-        for line in f.readlines():
+-            if line.strip().startswith(versionfile_source):
+-                if "export-subst" in line.strip().split()[1:]:
+-                    present = True
+-        f.close()
+-    except EnvironmentError:
++        with open(".gitattributes", "r") as fobj:
++            for line in fobj:
++                if line.strip().startswith(versionfile_source):
++                    if "export-subst" in line.strip().split()[1:]:
++                        present = True
++                        break
++    except OSError:
+         pass
+     if not present:
+-        f = open(".gitattributes", "a+")
+-        f.write("%s export-subst\n" % versionfile_source)
+-        f.close()
++        with open(".gitattributes", "a+") as fobj:
++            fobj.write(f"{versionfile_source} export-subst\n")
+         files.append(".gitattributes")
+     run_command(GITS, ["add", "--"] + files)
+ 
+ 
+-def versions_from_parentdir(parentdir_prefix, root, verbose):
+-    # Source tarballs conventionally unpack into a directory that includes
+-    # both the project name and a version string.
+-    dirname = os.path.basename(root)
+-    if not dirname.startswith(parentdir_prefix):
+-        if verbose:
+-            print("guessing rootdir is '%s', but '%s' doesn't start with "
+-                  "prefix '%s'" % (root, dirname, parentdir_prefix))
+-        raise NotThisMethod("rootdir doesn't start with parentdir_prefix")
+-    return {"version": dirname[len(parentdir_prefix):],
+-            "full-revisionid": None,
+-            "dirty": False, "error": None}
++def versions_from_parentdir(
++    parentdir_prefix: str,
++    root: str,
++    verbose: bool,
++) -> Dict[str, Any]:
++    """Try to determine the version from the parent directory name.
++
++    Source tarballs conventionally unpack into a directory that includes both
++    the project name and a version string. We will also support searching up
++    two directory levels for an appropriately named parent directory
++    """
++    rootdirs = []
++
++    for _ in range(3):
++        dirname = os.path.basename(root)
++        if dirname.startswith(parentdir_prefix):
++            return {
++                "version": dirname[len(parentdir_prefix) :],
++                "full-revisionid": None,
++                "dirty": False,
++                "error": None,
++                "date": None,
++            }
++        rootdirs.append(root)
++        root = os.path.dirname(root)  # up a level
++
++    if verbose:
++        print(
++            "Tried directories %s but none started with prefix %s"
++            % (str(rootdirs), parentdir_prefix)
++        )
++    raise NotThisMethod("rootdir doesn't start with parentdir_prefix")
++
+ 
+ SHORT_VERSION_PY = """
+-# This file was generated by 'versioneer.py' (0.15) from
++# This file was generated by 'versioneer.py' (0.29) from
+ # revision-control system data, or from the parent directory name of an
+ # unpacked source archive. Distribution tarballs contain a pre-generated copy
+ # of this file.
+ 
+ import json
+-import sys
+ 
+ version_json = '''
+ %s
+@@ -1143,43 +1538,50 @@ def get_versions():
+ """
+ 
+ 
+-def versions_from_file(filename):
++def versions_from_file(filename: str) -> Dict[str, Any]:
++    """Try to determine the version from _version.py if present."""
+     try:
+         with open(filename) as f:
+             contents = f.read()
+-    except EnvironmentError:
++    except OSError:
+         raise NotThisMethod("unable to read _version.py")
+-    mo = re.search(r"version_json = '''\n(.*)'''  # END VERSION_JSON",
+-                   contents, re.M | re.S)
++    mo = re.search(
++        r"version_json = '''\n(.*)'''  # END VERSION_JSON", contents, re.M | re.S
++    )
++    if not mo:
++        mo = re.search(
++            r"version_json = '''\r\n(.*)'''  # END VERSION_JSON", contents, re.M | re.S
++        )
+     if not mo:
+         raise NotThisMethod("no version_json in _version.py")
+     return json.loads(mo.group(1))
+ 
+ 
+-def write_to_version_file(filename, versions):
+-    os.unlink(filename)
+-    contents = json.dumps(versions, sort_keys=True,
+-                          indent=1, separators=(",", ": "))
++def write_to_version_file(filename: str, versions: Dict[str, Any]) -> None:
++    """Write the given version number to the given _version.py file."""
++    contents = json.dumps(versions, sort_keys=True, indent=1, separators=(",", ": "))
+     with open(filename, "w") as f:
+         f.write(SHORT_VERSION_PY % contents)
+ 
+     print("set %s to '%s'" % (filename, versions["version"]))
+ 
+ 
+-def plus_or_dot(pieces):
++def plus_or_dot(pieces: Dict[str, Any]) -> str:
++    """Return a + if we don't already have one, else return a ."""
+     if "+" in pieces.get("closest-tag", ""):
+         return "."
+     return "+"
+ 
+ 
+-def render_pep440(pieces):
+-    # now build up version string, with post-release "local version
+-    # identifier". Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you
+-    # get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty
++def render_pep440(pieces: Dict[str, Any]) -> str:
++    """Build up version string, with post-release "local version identifier".
+ 
+-    # exceptions:
+-    # 1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty]
++    Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you
++    get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty
+ 
++    Exceptions:
++    1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty]
++    """
+     if pieces["closest-tag"]:
+         rendered = pieces["closest-tag"]
+         if pieces["distance"] or pieces["dirty"]:
+@@ -1189,38 +1591,85 @@ def render_pep440(pieces):
+                 rendered += ".dirty"
+     else:
+         # exception #1
+-        rendered = "0+untagged.%d.g%s" % (pieces["distance"],
+-                                          pieces["short"])
++        rendered = "0+untagged.%d.g%s" % (pieces["distance"], pieces["short"])
+         if pieces["dirty"]:
+             rendered += ".dirty"
+     return rendered
+ 
+ 
+-def render_pep440_pre(pieces):
+-    # TAG[.post.devDISTANCE] . No -dirty
++def render_pep440_branch(pieces: Dict[str, Any]) -> str:
++    """TAG[[.dev0]+DISTANCE.gHEX[.dirty]] .
+ 
+-    # exceptions:
+-    # 1: no tags. 0.post.devDISTANCE
++    The ".dev0" means not master branch. Note that .dev0 sorts backwards
++    (a feature branch will appear "older" than the master branch).
+ 
++    Exceptions:
++    1: no tags. 0[.dev0]+untagged.DISTANCE.gHEX[.dirty]
++    """
+     if pieces["closest-tag"]:
+         rendered = pieces["closest-tag"]
++        if pieces["distance"] or pieces["dirty"]:
++            if pieces["branch"] != "master":
++                rendered += ".dev0"
++            rendered += plus_or_dot(pieces)
++            rendered += "%d.g%s" % (pieces["distance"], pieces["short"])
++            if pieces["dirty"]:
++                rendered += ".dirty"
++    else:
++        # exception #1
++        rendered = "0"
++        if pieces["branch"] != "master":
++            rendered += ".dev0"
++        rendered += "+untagged.%d.g%s" % (pieces["distance"], pieces["short"])
++        if pieces["dirty"]:
++            rendered += ".dirty"
++    return rendered
++
++
++def pep440_split_post(ver: str) -> Tuple[str, Optional[int]]:
++    """Split pep440 version string at the post-release segment.
++
++    Returns the release segments before the post-release and the
++    post-release version number (or -1 if no post-release segment is present).
++    """
++    vc = str.split(ver, ".post")
++    return vc[0], int(vc[1] or 0) if len(vc) == 2 else None
++
++
++def render_pep440_pre(pieces: Dict[str, Any]) -> str:
++    """TAG[.postN.devDISTANCE] -- No -dirty.
++
++    Exceptions:
++    1: no tags. 0.post0.devDISTANCE
++    """
++    if pieces["closest-tag"]:
+         if pieces["distance"]:
+-            rendered += ".post.dev%d" % pieces["distance"]
++            # update the post release segment
++            tag_version, post_version = pep440_split_post(pieces["closest-tag"])
++            rendered = tag_version
++            if post_version is not None:
++                rendered += ".post%d.dev%d" % (post_version + 1, pieces["distance"])
++            else:
++                rendered += ".post0.dev%d" % (pieces["distance"])
++        else:
++            # no commits, use the tag as the version
++            rendered = pieces["closest-tag"]
+     else:
+         # exception #1
+-        rendered = "0.post.dev%d" % pieces["distance"]
++        rendered = "0.post0.dev%d" % pieces["distance"]
+     return rendered
+ 
+ 
+-def render_pep440_post(pieces):
+-    # TAG[.postDISTANCE[.dev0]+gHEX] . The ".dev0" means dirty. Note that
+-    # .dev0 sorts backwards (a dirty tree will appear "older" than the
+-    # corresponding clean one), but you shouldn't be releasing software with
+-    # -dirty anyways.
++def render_pep440_post(pieces: Dict[str, Any]) -> str:
++    """TAG[.postDISTANCE[.dev0]+gHEX] .
+ 
+-    # exceptions:
+-    # 1: no tags. 0.postDISTANCE[.dev0]
++    The ".dev0" means dirty. Note that .dev0 sorts backwards
++    (a dirty tree will appear "older" than the corresponding clean one),
++    but you shouldn't be releasing software with -dirty anyways.
+ 
++    Exceptions:
++    1: no tags. 0.postDISTANCE[.dev0]
++    """
+     if pieces["closest-tag"]:
+         rendered = pieces["closest-tag"]
+         if pieces["distance"] or pieces["dirty"]:
+@@ -1238,12 +1687,43 @@ def render_pep440_post(pieces):
+     return rendered
+ 
+ 
+-def render_pep440_old(pieces):
+-    # TAG[.postDISTANCE[.dev0]] . The ".dev0" means dirty.
++def render_pep440_post_branch(pieces: Dict[str, Any]) -> str:
++    """TAG[.postDISTANCE[.dev0]+gHEX[.dirty]] .
++
++    The ".dev0" means not master branch.
++
++    Exceptions:
++    1: no tags. 0.postDISTANCE[.dev0]+gHEX[.dirty]
++    """
++    if pieces["closest-tag"]:
++        rendered = pieces["closest-tag"]
++        if pieces["distance"] or pieces["dirty"]:
++            rendered += ".post%d" % pieces["distance"]
++            if pieces["branch"] != "master":
++                rendered += ".dev0"
++            rendered += plus_or_dot(pieces)
++            rendered += "g%s" % pieces["short"]
++            if pieces["dirty"]:
++                rendered += ".dirty"
++    else:
++        # exception #1
++        rendered = "0.post%d" % pieces["distance"]
++        if pieces["branch"] != "master":
++            rendered += ".dev0"
++        rendered += "+g%s" % pieces["short"]
++        if pieces["dirty"]:
++            rendered += ".dirty"
++    return rendered
++
++
++def render_pep440_old(pieces: Dict[str, Any]) -> str:
++    """TAG[.postDISTANCE[.dev0]] .
+ 
+-    # exceptions:
+-    # 1: no tags. 0.postDISTANCE[.dev0]
++    The ".dev0" means dirty.
+ 
++    Exceptions:
++    1: no tags. 0.postDISTANCE[.dev0]
++    """
+     if pieces["closest-tag"]:
+         rendered = pieces["closest-tag"]
+         if pieces["distance"] or pieces["dirty"]:
+@@ -1258,13 +1738,14 @@ def render_pep440_old(pieces):
+     return rendered
+ 
+ 
+-def render_git_describe(pieces):
+-    # TAG[-DISTANCE-gHEX][-dirty], like 'git describe --tags --dirty
+-    # --always'
++def render_git_describe(pieces: Dict[str, Any]) -> str:
++    """TAG[-DISTANCE-gHEX][-dirty].
+ 
+-    # exceptions:
+-    # 1: no tags. HEX[-dirty]  (note: no 'g' prefix)
++    Like 'git describe --tags --dirty --always'.
+ 
++    Exceptions:
++    1: no tags. HEX[-dirty]  (note: no 'g' prefix)
++    """
+     if pieces["closest-tag"]:
+         rendered = pieces["closest-tag"]
+         if pieces["distance"]:
+@@ -1277,13 +1758,15 @@ def render_git_describe(pieces):
+     return rendered
+ 
+ 
+-def render_git_describe_long(pieces):
+-    # TAG-DISTANCE-gHEX[-dirty], like 'git describe --tags --dirty
+-    # --always -long'. The distance/hash is unconditional.
++def render_git_describe_long(pieces: Dict[str, Any]) -> str:
++    """TAG-DISTANCE-gHEX[-dirty].
+ 
+-    # exceptions:
+-    # 1: no tags. HEX[-dirty]  (note: no 'g' prefix)
++    Like 'git describe --tags --dirty --always -long'.
++    The distance/hash is unconditional.
+ 
++    Exceptions:
++    1: no tags. HEX[-dirty]  (note: no 'g' prefix)
++    """
+     if pieces["closest-tag"]:
+         rendered = pieces["closest-tag"]
+         rendered += "-%d-g%s" % (pieces["distance"], pieces["short"])
+@@ -1295,22 +1778,30 @@ def render_git_describe_long(pieces):
+     return rendered
+ 
+ 
+-def render(pieces, style):
++def render(pieces: Dict[str, Any], style: str) -> Dict[str, Any]:
++    """Render the given version pieces into the requested style."""
+     if pieces["error"]:
+-        return {"version": "unknown",
+-                "full-revisionid": pieces.get("long"),
+-                "dirty": None,
+-                "error": pieces["error"]}
++        return {
++            "version": "unknown",
++            "full-revisionid": pieces.get("long"),
++            "dirty": None,
++            "error": pieces["error"],
++            "date": None,
++        }
+ 
+     if not style or style == "default":
+         style = "pep440"  # the default
+ 
+     if style == "pep440":
+         rendered = render_pep440(pieces)
++    elif style == "pep440-branch":
++        rendered = render_pep440_branch(pieces)
+     elif style == "pep440-pre":
+         rendered = render_pep440_pre(pieces)
+     elif style == "pep440-post":
+         rendered = render_pep440_post(pieces)
++    elif style == "pep440-post-branch":
++        rendered = render_pep440_post_branch(pieces)
+     elif style == "pep440-old":
+         rendered = render_pep440_old(pieces)
+     elif style == "git-describe":
+@@ -1320,17 +1811,24 @@ def render(pieces, style):
+     else:
+         raise ValueError("unknown style '%s'" % style)
+ 
+-    return {"version": rendered, "full-revisionid": pieces["long"],
+-            "dirty": pieces["dirty"], "error": None}
++    return {
++        "version": rendered,
++        "full-revisionid": pieces["long"],
++        "dirty": pieces["dirty"],
++        "error": None,
++        "date": pieces.get("date"),
++    }
+ 
+ 
+ class VersioneerBadRootError(Exception):
+-    pass
++    """The project root directory is unknown or missing key files."""
+ 
+ 
+-def get_versions(verbose=False):
+-    # returns dict with two keys: 'version' and 'full'
++def get_versions(verbose: bool = False) -> Dict[str, Any]:
++    """Get the project version from whatever source is available.
+ 
++    Returns dict with two keys: 'version' and 'full'.
++    """
+     if "versioneer" in sys.modules:
+         # see the discussion in cmdclass.py:get_cmdclass()
+         del sys.modules["versioneer"]
+@@ -1341,9 +1839,10 @@ def get_versions(verbose=False):
+     assert cfg.VCS is not None, "please set [versioneer]VCS= in setup.cfg"
+     handlers = HANDLERS.get(cfg.VCS)
+     assert handlers, "unrecognized VCS '%s'" % cfg.VCS
+-    verbose = verbose or cfg.verbose
+-    assert cfg.versionfile_source is not None, \
+-        "please set versioneer.versionfile_source"
++    verbose = verbose or bool(cfg.verbose)  # `bool()` used to avoid `None`
++    assert (
++        cfg.versionfile_source is not None
++    ), "please set versioneer.versionfile_source"
+     assert cfg.tag_prefix is not None, "please set versioneer.tag_prefix"
+ 
+     versionfile_abs = os.path.join(root, cfg.versionfile_source)
+@@ -1397,15 +1896,26 @@ def get_versions(verbose=False):
+     if verbose:
+         print("unable to compute version")
+ 
+-    return {"version": "0+unknown", "full-revisionid": None,
+-            "dirty": None, "error": "unable to compute version"}
++    return {
++        "version": "0+unknown",
++        "full-revisionid": None,
++        "dirty": None,
++        "error": "unable to compute version",
++        "date": None,
++    }
+ 
+ 
+-def get_version():
++def get_version() -> str:
++    """Get the short version string for this project."""
+     return get_versions()["version"]
+ 
+ 
+-def get_cmdclass():
++def get_cmdclass(cmdclass: Optional[Dict[str, Any]] = None):
++    """Get the custom setuptools subclasses used by Versioneer.
++
++    If the package uses a different cmdclass (e.g. one from numpy), it
++    should be provide as an argument.
++    """
+     if "versioneer" in sys.modules:
+         del sys.modules["versioneer"]
+         # this fixes the "python setup.py develop" case (also 'install' and
+@@ -1419,34 +1929,36 @@ def get_cmdclass():
+         # parent is protected against the child's "import versioneer". By
+         # removing ourselves from sys.modules here, before the child build
+         # happens, we protect the child from the parent's versioneer too.
+-        # Also see https://github.com/warner/python-versioneer/issues/52
++        # Also see https://github.com/python-versioneer/python-versioneer/issues/52
+ 
+-    cmds = {}
++    cmds = {} if cmdclass is None else cmdclass.copy()
+ 
+-    # we add "version" to both distutils and setuptools
+-    from distutils.core import Command
++    # we add "version" to setuptools
++    from setuptools import Command
+ 
+     class cmd_version(Command):
+         description = "report generated version string"
+-        user_options = []
+-        boolean_options = []
++        user_options: List[Tuple[str, str, str]] = []
++        boolean_options: List[str] = []
+ 
+-        def initialize_options(self):
++        def initialize_options(self) -> None:
+             pass
+ 
+-        def finalize_options(self):
++        def finalize_options(self) -> None:
+             pass
+ 
+-        def run(self):
++        def run(self) -> None:
+             vers = get_versions(verbose=True)
+             print("Version: %s" % vers["version"])
+             print(" full-revisionid: %s" % vers.get("full-revisionid"))
+             print(" dirty: %s" % vers.get("dirty"))
++            print(" date: %s" % vers.get("date"))
+             if vers["error"]:
+                 print(" error: %s" % vers["error"])
++
+     cmds["version"] = cmd_version
+ 
+-    # we override "build_py" in both distutils and setuptools
++    # we override "build_py" in setuptools
+     #
+     # most invocation pathways end up running build_py:
+     #  distutils/build -> build_py
+@@ -1455,29 +1967,86 @@ def get_cmdclass():
+     #  setuptools/bdist_egg -> distutils/install_lib -> build_py
+     #  setuptools/install -> bdist_egg ->..
+     #  setuptools/develop -> ?
+-
+-    from distutils.command.build_py import build_py as _build_py
++    #  pip install:
++    #   copies source tree to a tempdir before running egg_info/etc
++    #   if .git isn't copied too, 'git describe' will fail
++    #   then does setup.py bdist_wheel, or sometimes setup.py install
++    #  setup.py egg_info -> ?
++
++    # pip install -e . and setuptool/editable_wheel will invoke build_py
++    # but the build_py command is not expected to copy any files.
++
++    # we override different "build_py" commands for both environments
++    if "build_py" in cmds:
++        _build_py: Any = cmds["build_py"]
++    else:
++        from setuptools.command.build_py import build_py as _build_py
+ 
+     class cmd_build_py(_build_py):
+-        def run(self):
++        def run(self) -> None:
+             root = get_root()
+             cfg = get_config_from_root(root)
+             versions = get_versions()
+             _build_py.run(self)
++            if getattr(self, "editable_mode", False):
++                # During editable installs `.py` and data files are
++                # not copied to build_lib
++                return
+             # now locate _version.py in the new build/ directory and replace
+             # it with an updated value
+             if cfg.versionfile_build:
+-                target_versionfile = os.path.join(self.build_lib,
+-                                                  cfg.versionfile_build)
++                target_versionfile = os.path.join(self.build_lib, cfg.versionfile_build)
+                 print("UPDATING %s" % target_versionfile)
+                 write_to_version_file(target_versionfile, versions)
++
+     cmds["build_py"] = cmd_build_py
+ 
++    if "build_ext" in cmds:
++        _build_ext: Any = cmds["build_ext"]
++    else:
++        from setuptools.command.build_ext import build_ext as _build_ext
++
++    class cmd_build_ext(_build_ext):
++        def run(self) -> None:
++            root = get_root()
++            cfg = get_config_from_root(root)
++            versions = get_versions()
++            _build_ext.run(self)
++            if self.inplace:
++                # build_ext --inplace will only build extensions in
++                # build/lib<..> dir with no _version.py to write to.
++                # As in place builds will already have a _version.py
++                # in the module dir, we do not need to write one.
++                return
++            # now locate _version.py in the new build/ directory and replace
++            # it with an updated value
++            if not cfg.versionfile_build:
++                return
++            target_versionfile = os.path.join(self.build_lib, cfg.versionfile_build)
++            if not os.path.exists(target_versionfile):
++                print(
++                    f"Warning: {target_versionfile} does not exist, skipping "
++                    "version update. This can happen if you are running build_ext "
++                    "without first running build_py."
++                )
++                return
++            print("UPDATING %s" % target_versionfile)
++            write_to_version_file(target_versionfile, versions)
++
++    cmds["build_ext"] = cmd_build_ext
++
+     if "cx_Freeze" in sys.modules:  # cx_freeze enabled?
+-        from cx_Freeze.dist import build_exe as _build_exe
++        from cx_Freeze.dist import build_exe as _build_exe  # type: ignore
++
++        # nczeczulin reports that py2exe won't like the pep440-style string
++        # as FILEVERSION, but it can be used for PRODUCTVERSION, e.g.
++        # setup(console=[{
++        #   "version": versioneer.get_version().split("+", 1)[0], # FILEVERSION
++        #   "product_version": versioneer.get_version(),
++        #   ...
+ 
+         class cmd_build_exe(_build_exe):
+-            def run(self):
++            def run(self) -> None:
+                 root = get_root()
+                 cfg = get_config_from_root(root)
+                 versions = get_versions()
+@@ -1489,24 +2058,100 @@ def get_cmdclass():
+                 os.unlink(target_versionfile)
+                 with open(cfg.versionfile_source, "w") as f:
+                     LONG = LONG_VERSION_PY[cfg.VCS]
+-                    f.write(LONG %
+-                            {"DOLLAR": "$",
+-                             "STYLE": cfg.style,
+-                             "TAG_PREFIX": cfg.tag_prefix,
+-                             "PARENTDIR_PREFIX": cfg.parentdir_prefix,
+-                             "VERSIONFILE_SOURCE": cfg.versionfile_source,
+-                             })
++                    f.write(
++                        LONG
++                        % {
++                            "DOLLAR": "$",
++                            "STYLE": cfg.style,
++                            "TAG_PREFIX": cfg.tag_prefix,
++                            "PARENTDIR_PREFIX": cfg.parentdir_prefix,
++                            "VERSIONFILE_SOURCE": cfg.versionfile_source,
++                        }
++                    )
++
+         cmds["build_exe"] = cmd_build_exe
+         del cmds["build_py"]
+ 
++    if "py2exe" in sys.modules:  # py2exe enabled?
++        try:
++            from py2exe.setuptools_buildexe import py2exe as _py2exe  # type: ignore
++        except ImportError:
++            from py2exe.distutils_buildexe import py2exe as _py2exe  # type: ignore
++
++        class cmd_py2exe(_py2exe):
++            def run(self) -> None:
++                root = get_root()
++                cfg = get_config_from_root(root)
++                versions = get_versions()
++                target_versionfile = cfg.versionfile_source
++                print("UPDATING %s" % target_versionfile)
++                write_to_version_file(target_versionfile, versions)
++
++                _py2exe.run(self)
++                os.unlink(target_versionfile)
++                with open(cfg.versionfile_source, "w") as f:
++                    LONG = LONG_VERSION_PY[cfg.VCS]
++                    f.write(
++                        LONG
++                        % {
++                            "DOLLAR": "$",
++                            "STYLE": cfg.style,
++                            "TAG_PREFIX": cfg.tag_prefix,
++                            "PARENTDIR_PREFIX": cfg.parentdir_prefix,
++                            "VERSIONFILE_SOURCE": cfg.versionfile_source,
++                        }
++                    )
++
++        cmds["py2exe"] = cmd_py2exe
++
++    # sdist farms its file list building out to egg_info
++    if "egg_info" in cmds:
++        _egg_info: Any = cmds["egg_info"]
++    else:
++        from setuptools.command.egg_info import egg_info as _egg_info
++
++    class cmd_egg_info(_egg_info):
++        def find_sources(self) -> None:
++            # egg_info.find_sources builds the manifest list and writes it
++            # in one shot
++            super().find_sources()
++
++            # Modify the filelist and normalize it
++            root = get_root()
++            cfg = get_config_from_root(root)
++            self.filelist.append("versioneer.py")
++            if cfg.versionfile_source:
++                # There are rare cases where versionfile_source might not be
++                # included by default, so we must be explicit
++                self.filelist.append(cfg.versionfile_source)
++            self.filelist.sort()
++            self.filelist.remove_duplicates()
++
++            # The write method is hidden in the manifest_maker instance that
++            # generated the filelist and was thrown away
++            # We will instead replicate their final normalization (to unicode,
++            # and POSIX-style paths)
++            from setuptools import unicode_utils
++
++            normalized = [
++                unicode_utils.filesys_decode(f).replace(os.sep, "/")
++                for f in self.filelist.files
++            ]
++
++            manifest_filename = os.path.join(self.egg_info, "SOURCES.txt")
++            with open(manifest_filename, "w") as fobj:
++                fobj.write("\n".join(normalized))
++
++    cmds["egg_info"] = cmd_egg_info
++
+     # we override different "sdist" commands for both environments
+-    if "setuptools" in sys.modules:
+-        from setuptools.command.sdist import sdist as _sdist
++    if "sdist" in cmds:
++        _sdist: Any = cmds["sdist"]
+     else:
+-        from distutils.command.sdist import sdist as _sdist
++        from setuptools.command.sdist import sdist as _sdist
+ 
+     class cmd_sdist(_sdist):
+-        def run(self):
++        def run(self) -> None:
+             versions = get_versions()
+             self._versioneer_generated_versions = versions
+             # unless we update this, the command will keep using the old
+@@ -1514,7 +2159,7 @@ def get_cmdclass():
+             self.distribution.metadata.version = versions["version"]
+             return _sdist.run(self)
+ 
+-        def make_release_tree(self, base_dir, files):
++        def make_release_tree(self, base_dir: str, files: List[str]) -> None:
+             root = get_root()
+             cfg = get_config_from_root(root)
+             _sdist.make_release_tree(self, base_dir, files)
+@@ -1523,8 +2168,10 @@ def get_cmdclass():
+             # updated value
+             target_versionfile = os.path.join(base_dir, cfg.versionfile_source)
+             print("UPDATING %s" % target_versionfile)
+-            write_to_version_file(target_versionfile,
+-                                  self._versioneer_generated_versions)
++            write_to_version_file(
++                target_versionfile, self._versioneer_generated_versions
++            )
++
+     cmds["sdist"] = cmd_sdist
+ 
+     return cmds
+@@ -1539,7 +2186,7 @@ a section like:
+  style = pep440
+  versionfile_source = src/myproject/_version.py
+  versionfile_build = myproject/_version.py
+- tag_prefix = ""
++ tag_prefix =
+  parentdir_prefix = myproject-
+ 
+ You will also need to edit your setup.py to use the results:
+@@ -1567,22 +2214,26 @@ SAMPLE_CONFIG = """
+ 
+ """
+ 
+-INIT_PY_SNIPPET = """
++OLD_SNIPPET = """
+ from ._version import get_versions
+ __version__ = get_versions()['version']
+ del get_versions
+ """
+ 
++INIT_PY_SNIPPET = """
++from . import {0}
++__version__ = {0}.get_versions()['version']
++"""
++
+ 
+-def do_setup():
++def do_setup() -> int:
++    """Do main VCS-independent setup function for installing Versioneer."""
+     root = get_root()
+     try:
+         cfg = get_config_from_root(root)
+-    except (EnvironmentError, configparser.NoSectionError,
+-            configparser.NoOptionError) as e:
+-        if isinstance(e, (EnvironmentError, configparser.NoSectionError)):
+-            print("Adding sample versioneer config to setup.cfg",
+-                  file=sys.stderr)
++    except (OSError, configparser.NoSectionError, configparser.NoOptionError) as e:
++        if isinstance(e, (OSError, configparser.NoSectionError)):
++            print("Adding sample versioneer config to setup.cfg", file=sys.stderr)
+             with open(os.path.join(root, "setup.cfg"), "a") as f:
+                 f.write(SAMPLE_CONFIG)
+         print(CONFIG_ERROR, file=sys.stderr)
+@@ -1591,71 +2242,50 @@ def do_setup():
+     print(" creating %s" % cfg.versionfile_source)
+     with open(cfg.versionfile_source, "w") as f:
+         LONG = LONG_VERSION_PY[cfg.VCS]
+-        f.write(LONG % {"DOLLAR": "$",
+-                        "STYLE": cfg.style,
+-                        "TAG_PREFIX": cfg.tag_prefix,
+-                        "PARENTDIR_PREFIX": cfg.parentdir_prefix,
+-                        "VERSIONFILE_SOURCE": cfg.versionfile_source,
+-                        })
+-
+-    ipy = os.path.join(os.path.dirname(cfg.versionfile_source),
+-                       "__init__.py")
++        f.write(
++            LONG
++            % {
++                "DOLLAR": "$",
++                "STYLE": cfg.style,
++                "TAG_PREFIX": cfg.tag_prefix,
++                "PARENTDIR_PREFIX": cfg.parentdir_prefix,
++                "VERSIONFILE_SOURCE": cfg.versionfile_source,
++            }
++        )
++
++    ipy = os.path.join(os.path.dirname(cfg.versionfile_source), "__init__.py")
++    maybe_ipy: Optional[str] = ipy
+     if os.path.exists(ipy):
+         try:
+             with open(ipy, "r") as f:
+                 old = f.read()
+-        except EnvironmentError:
++        except OSError:
+             old = ""
+-        if INIT_PY_SNIPPET not in old:
++        module = os.path.splitext(os.path.basename(cfg.versionfile_source))[0]
++        snippet = INIT_PY_SNIPPET.format(module)
++        if OLD_SNIPPET in old:
++            print(" replacing boilerplate in %s" % ipy)
++            with open(ipy, "w") as f:
++                f.write(old.replace(OLD_SNIPPET, snippet))
++        elif snippet not in old:
+             print(" appending to %s" % ipy)
+             with open(ipy, "a") as f:
+-                f.write(INIT_PY_SNIPPET)
++                f.write(snippet)
+         else:
+             print(" %s unmodified" % ipy)
+     else:
+         print(" %s doesn't exist, ok" % ipy)
+-        ipy = None
+-
+-    # Make sure both the top-level "versioneer.py" and versionfile_source
+-    # (PKG/_version.py, used by runtime code) are in MANIFEST.in, so
+-    # they'll be copied into source distributions. Pip won't be able to
+-    # install the package without this.
+-    manifest_in = os.path.join(root, "MANIFEST.in")
+-    simple_includes = set()
+-    try:
+-        with open(manifest_in, "r") as f:
+-            for line in f:
+-                if line.startswith("include "):
+-                    for include in line.split()[1:]:
+-                        simple_includes.add(include)
+-    except EnvironmentError:
+-        pass
+-    # That doesn't cover everything MANIFEST.in can do
+-    # (http://docs.python.org/2/distutils/sourcedist.html#commands), so
+-    # it might give some false negatives. Appending redundant 'include'
+-    # lines is safe, though.
+-    if "versioneer.py" not in simple_includes:
+-        print(" appending 'versioneer.py' to MANIFEST.in")
+-        with open(manifest_in, "a") as f:
+-            f.write("include versioneer.py\n")
+-    else:
+-        print(" 'versioneer.py' already in MANIFEST.in")
+-    if cfg.versionfile_source not in simple_includes:
+-        print(" appending versionfile_source ('%s') to MANIFEST.in" %
+-              cfg.versionfile_source)
+-        with open(manifest_in, "a") as f:
+-            f.write("include %s\n" % cfg.versionfile_source)
+-    else:
+-        print(" versionfile_source already in MANIFEST.in")
++        maybe_ipy = None
+ 
+     # Make VCS-specific changes. For git, this means creating/changing
+-    # .gitattributes to mark _version.py for export-time keyword
++    # .gitattributes to mark _version.py for export-subst keyword
+     # substitution.
+-    do_vcs_install(manifest_in, cfg.versionfile_source, ipy)
++    do_vcs_install(cfg.versionfile_source, maybe_ipy)
+     return 0
+ 
+ 
+-def scan_setup_py():
++def scan_setup_py() -> int:
++    """Validate the contents of setup.py against Versioneer's expectations."""
+     found = set()
+     setters = False
+     errors = 0
+@@ -1690,10 +2320,15 @@ def scan_setup_py():
+         errors += 1
+     return errors
+ 
++
++def setup_command() -> NoReturn:
++    """Set up Versioneer and exit with appropriate error code."""
++    errors = do_setup()
++    errors += scan_setup_py()
++    sys.exit(1 if errors else 0)
++
++
+ if __name__ == "__main__":
+     cmd = sys.argv[1]
+     if cmd == "setup":
+-        errors = do_setup()
+-        errors += scan_setup_py()
+-        if errors:
+-            sys.exit(1)
++        setup_command()
+-- 
+2.41.0
+
-- 
2.41.0

_______________________________________________
buildroot mailing list
buildroot@buildroot.org
https://lists.buildroot.org/mailman/listinfo/buildroot

^ permalink raw reply related	[flat|nested] 54+ messages in thread

* [Buildroot] [PATCH 21/30] package/python-magic-wormhole: update versioneer to 0.29
  2023-10-26  9:26 [Buildroot] [PATCH 00/30] package/python3: bump version to 3.12.0 Adam Duskett
                   ` (19 preceding siblings ...)
  2023-10-26  9:26 ` [Buildroot] [PATCH 20/30] package/python-constantly: update versioneer to 0.29 Adam Duskett
@ 2023-10-26  9:26 ` Adam Duskett
  2023-11-04 21:30   ` Arnout Vandecappelle via buildroot
  2023-10-26  9:26 ` [Buildroot] [PATCH 22/30] package/python-magic-wormhole-mailbox-server: " Adam Duskett
                   ` (9 subsequent siblings)
  30 siblings, 1 reply; 54+ messages in thread
From: Adam Duskett @ 2023-10-26  9:26 UTC (permalink / raw)
  To: buildroot
  Cc: Adam Duskett, Andrey Smirnov, Julien Olivain, Asaf Kahlon,
	James Hilliard, Thomas Petazzoni, Mauro Condarelli

Versioneer < 0.21 is incompatible with Python 3.12.0. Use the latest version
which is 0.29 as of this commit.

Signed-off-by: Adam Duskett <adam.duskett@amarulasolutions.com>
---
 .../0001-Update-versioneer-to-0.29.patch      | 2185 +++++++++++++++++
 1 file changed, 2185 insertions(+)
 create mode 100644 package/python-magic-wormhole/0001-Update-versioneer-to-0.29.patch

diff --git a/package/python-magic-wormhole/0001-Update-versioneer-to-0.29.patch b/package/python-magic-wormhole/0001-Update-versioneer-to-0.29.patch
new file mode 100644
index 0000000000..54b5fe8b1d
--- /dev/null
+++ b/package/python-magic-wormhole/0001-Update-versioneer-to-0.29.patch
@@ -0,0 +1,2185 @@
+From b283ce50c6f32387d0f474c935ed6015585b986c Mon Sep 17 00:00:00 2001
+From: Adam Duskett <adam.duskett@amarulasolutions.com>
+Date: Tue, 24 Oct 2023 09:54:40 +0200
+Subject: [PATCH] Update versioneer to 0.29
+
+Fixes builds against Python 3.12.0
+
+Upstream: https://github.com/magic-wormhole/magic-wormhole/pull/505
+
+Signed-off-by: Adam Duskett <adam.duskett@amarulasolutions.com>
+---
+ versioneer.py | 1348 ++++++++++++++++++++++++++++++++++---------------
+ 1 file changed, 930 insertions(+), 418 deletions(-)
+
+diff --git a/versioneer.py b/versioneer.py
+index ebe628e..de97d90 100644
+--- a/versioneer.py
++++ b/versioneer.py
+@@ -1,5 +1,4 @@
+-
+-# Version: 0.18
++# Version: 0.29
+ 
+ """The Versioneer - like a rocketeer, but for versions.
+ 
+@@ -7,18 +6,14 @@ The Versioneer
+ ==============
+ 
+ * like a rocketeer, but for versions!
+-* https://github.com/warner/python-versioneer
++* https://github.com/python-versioneer/python-versioneer
+ * Brian Warner
+-* License: Public Domain
+-* Compatible With: python2.6, 2.7, 3.2, 3.3, 3.4, 3.5, 3.6, and pypy
+-* [![Latest Version]
+-(https://pypip.in/version/versioneer/badge.svg?style=flat)
+-](https://pypi.python.org/pypi/versioneer/)
+-* [![Build Status]
+-(https://travis-ci.org/warner/python-versioneer.png?branch=master)
+-](https://travis-ci.org/warner/python-versioneer)
+-
+-This is a tool for managing a recorded version number in distutils-based
++* License: Public Domain (Unlicense)
++* Compatible with: Python 3.7, 3.8, 3.9, 3.10, 3.11 and pypy3
++* [![Latest Version][pypi-image]][pypi-url]
++* [![Build Status][travis-image]][travis-url]
++
++This is a tool for managing a recorded version number in setuptools-based
+ python projects. The goal is to remove the tedious and error-prone "update
+ the embedded version string" step from your release process. Making a new
+ release should be as easy as recording a new tag in your version-control
+@@ -27,9 +22,38 @@ system, and maybe making new tarballs.
+ 
+ ## Quick Install
+ 
+-* `pip install versioneer` to somewhere to your $PATH
+-* add a `[versioneer]` section to your setup.cfg (see below)
+-* run `versioneer install` in your source tree, commit the results
++Versioneer provides two installation modes. The "classic" vendored mode installs
++a copy of versioneer into your repository. The experimental build-time dependency mode
++is intended to allow you to skip this step and simplify the process of upgrading.
++
++### Vendored mode
++
++* `pip install versioneer` to somewhere in your $PATH
++   * A [conda-forge recipe](https://github.com/conda-forge/versioneer-feedstock) is
++     available, so you can also use `conda install -c conda-forge versioneer`
++* add a `[tool.versioneer]` section to your `pyproject.toml` or a
++  `[versioneer]` section to your `setup.cfg` (see [Install](INSTALL.md))
++   * Note that you will need to add `tomli; python_version < "3.11"` to your
++     build-time dependencies if you use `pyproject.toml`
++* run `versioneer install --vendor` in your source tree, commit the results
++* verify version information with `python setup.py version`
++
++### Build-time dependency mode
++
++* `pip install versioneer` to somewhere in your $PATH
++   * A [conda-forge recipe](https://github.com/conda-forge/versioneer-feedstock) is
++     available, so you can also use `conda install -c conda-forge versioneer`
++* add a `[tool.versioneer]` section to your `pyproject.toml` or a
++  `[versioneer]` section to your `setup.cfg` (see [Install](INSTALL.md))
++* add `versioneer` (with `[toml]` extra, if configuring in `pyproject.toml`)
++  to the `requires` key of the `build-system` table in `pyproject.toml`:
++  ```toml
++  [build-system]
++  requires = ["setuptools", "versioneer[toml]"]
++  build-backend = "setuptools.build_meta"
++  ```
++* run `versioneer install --no-vendor` in your source tree, commit the results
++* verify version information with `python setup.py version`
+ 
+ ## Version Identifiers
+ 
+@@ -61,7 +85,7 @@ version 1.3). Many VCS systems can report a description that captures this,
+ for example `git describe --tags --dirty --always` reports things like
+ "0.7-1-g574ab98-dirty" to indicate that the checkout is one revision past the
+ 0.7 tag, has a unique revision id of "574ab98", and is "dirty" (it has
+-uncommitted changes.
++uncommitted changes).
+ 
+ The version identifier is used for multiple purposes:
+ 
+@@ -166,7 +190,7 @@ which may help identify what went wrong).
+ 
+ Some situations are known to cause problems for Versioneer. This details the
+ most significant ones. More can be found on Github
+-[issues page](https://github.com/warner/python-versioneer/issues).
++[issues page](https://github.com/python-versioneer/python-versioneer/issues).
+ 
+ ### Subprojects
+ 
+@@ -194,9 +218,9 @@ work too.
+ Pip-8.1.1 is known to have this problem, but hopefully it will get fixed in
+ some later version.
+ 
+-[Bug #38](https://github.com/warner/python-versioneer/issues/38) is tracking
++[Bug #38](https://github.com/python-versioneer/python-versioneer/issues/38) is tracking
+ this issue. The discussion in
+-[PR #61](https://github.com/warner/python-versioneer/pull/61) describes the
++[PR #61](https://github.com/python-versioneer/python-versioneer/pull/61) describes the
+ issue from the Versioneer side in more detail.
+ [pip PR#3176](https://github.com/pypa/pip/pull/3176) and
+ [pip PR#3615](https://github.com/pypa/pip/pull/3615) contain work to improve
+@@ -224,31 +248,20 @@ regenerated while a different version is checked out. Many setup.py commands
+ cause egg_info to be rebuilt (including `sdist`, `wheel`, and installing into
+ a different virtualenv), so this can be surprising.
+ 
+-[Bug #83](https://github.com/warner/python-versioneer/issues/83) describes
++[Bug #83](https://github.com/python-versioneer/python-versioneer/issues/83) describes
+ this one, but upgrading to a newer version of setuptools should probably
+ resolve it.
+ 
+-### Unicode version strings
+-
+-While Versioneer works (and is continually tested) with both Python 2 and
+-Python 3, it is not entirely consistent with bytes-vs-unicode distinctions.
+-Newer releases probably generate unicode version strings on py2. It's not
+-clear that this is wrong, but it may be surprising for applications when then
+-write these strings to a network connection or include them in bytes-oriented
+-APIs like cryptographic checksums.
+-
+-[Bug #71](https://github.com/warner/python-versioneer/issues/71) investigates
+-this question.
+-
+ 
+ ## Updating Versioneer
+ 
+ To upgrade your project to a new release of Versioneer, do the following:
+ 
+ * install the new Versioneer (`pip install -U versioneer` or equivalent)
+-* edit `setup.cfg`, if necessary, to include any new configuration settings
+-  indicated by the release notes. See [UPGRADING](./UPGRADING.md) for details.
+-* re-run `versioneer install` in your source tree, to replace
++* edit `setup.cfg` and `pyproject.toml`, if necessary,
++  to include any new configuration settings indicated by the release notes.
++  See [UPGRADING](./UPGRADING.md) for details.
++* re-run `versioneer install --[no-]vendor` in your source tree, to replace
+   `SRC/_version.py`
+ * commit any changed files
+ 
+@@ -265,35 +278,70 @@ installation by editing setup.py . Alternatively, it might go the other
+ direction and include code from all supported VCS systems, reducing the
+ number of intermediate scripts.
+ 
++## Similar projects
++
++* [setuptools_scm](https://github.com/pypa/setuptools_scm/) - a non-vendored build-time
++  dependency
++* [minver](https://github.com/jbweston/miniver) - a lightweight reimplementation of
++  versioneer
++* [versioningit](https://github.com/jwodder/versioningit) - a PEP 518-based setuptools
++  plugin
+ 
+ ## License
+ 
+ To make Versioneer easier to embed, all its code is dedicated to the public
+ domain. The `_version.py` that it creates is also in the public domain.
+-Specifically, both are released under the Creative Commons "Public Domain
+-Dedication" license (CC0-1.0), as described in
+-https://creativecommons.org/publicdomain/zero/1.0/ .
++Specifically, both are released under the "Unlicense", as described in
++https://unlicense.org/.
++
++[pypi-image]: https://img.shields.io/pypi/v/versioneer.svg
++[pypi-url]: https://pypi.python.org/pypi/versioneer/
++[travis-image]:
++https://img.shields.io/travis/com/python-versioneer/python-versioneer.svg
++[travis-url]: https://travis-ci.com/github/python-versioneer/python-versioneer
+ 
+ """
++# pylint:disable=invalid-name,import-outside-toplevel,missing-function-docstring
++# pylint:disable=missing-class-docstring,too-many-branches,too-many-statements
++# pylint:disable=raise-missing-from,too-many-lines,too-many-locals,import-error
++# pylint:disable=too-few-public-methods,redefined-outer-name,consider-using-with
++# pylint:disable=attribute-defined-outside-init,too-many-arguments
+ 
+-from __future__ import print_function
+-try:
+-    import configparser
+-except ImportError:
+-    import ConfigParser as configparser
++import configparser
+ import errno
+ import json
+ import os
+ import re
+ import subprocess
+ import sys
++from pathlib import Path
++from typing import Any, Callable, cast, Dict, List, Optional, Tuple, Union
++from typing import NoReturn
++import functools
++
++have_tomllib = True
++if sys.version_info >= (3, 11):
++    import tomllib
++else:
++    try:
++        import tomli as tomllib
++    except ImportError:
++        have_tomllib = False
+ 
+ 
+ class VersioneerConfig:
+     """Container for Versioneer configuration parameters."""
+ 
++    VCS: str
++    style: str
++    tag_prefix: str
++    versionfile_source: str
++    versionfile_build: Optional[str]
++    parentdir_prefix: Optional[str]
++    verbose: Optional[bool]
++
+ 
+-def get_root():
++def get_root() -> str:
+     """Get the project root directory.
+ 
+     We require that all commands are run from the project root, i.e. the
+@@ -301,18 +349,30 @@ def get_root():
+     """
+     root = os.path.realpath(os.path.abspath(os.getcwd()))
+     setup_py = os.path.join(root, "setup.py")
++    pyproject_toml = os.path.join(root, "pyproject.toml")
+     versioneer_py = os.path.join(root, "versioneer.py")
+-    if not (os.path.exists(setup_py) or os.path.exists(versioneer_py)):
++    if not (
++        os.path.exists(setup_py)
++        or os.path.exists(pyproject_toml)
++        or os.path.exists(versioneer_py)
++    ):
+         # allow 'python path/to/setup.py COMMAND'
+         root = os.path.dirname(os.path.realpath(os.path.abspath(sys.argv[0])))
+         setup_py = os.path.join(root, "setup.py")
++        pyproject_toml = os.path.join(root, "pyproject.toml")
+         versioneer_py = os.path.join(root, "versioneer.py")
+-    if not (os.path.exists(setup_py) or os.path.exists(versioneer_py)):
+-        err = ("Versioneer was unable to run the project root directory. "
+-               "Versioneer requires setup.py to be executed from "
+-               "its immediate directory (like 'python setup.py COMMAND'), "
+-               "or in a way that lets it use sys.argv[0] to find the root "
+-               "(like 'python path/to/setup.py COMMAND').")
++    if not (
++        os.path.exists(setup_py)
++        or os.path.exists(pyproject_toml)
++        or os.path.exists(versioneer_py)
++    ):
++        err = (
++            "Versioneer was unable to run the project root directory. "
++            "Versioneer requires setup.py to be executed from "
++            "its immediate directory (like 'python setup.py COMMAND'), "
++            "or in a way that lets it use sys.argv[0] to find the root "
++            "(like 'python path/to/setup.py COMMAND')."
++        )
+         raise VersioneerBadRootError(err)
+     try:
+         # Certain runtime workflows (setup.py install/develop in a setuptools
+@@ -321,43 +381,64 @@ def get_root():
+         # module-import table will cache the first one. So we can't use
+         # os.path.dirname(__file__), as that will find whichever
+         # versioneer.py was first imported, even in later projects.
+-        me = os.path.realpath(os.path.abspath(__file__))
+-        me_dir = os.path.normcase(os.path.splitext(me)[0])
++        my_path = os.path.realpath(os.path.abspath(__file__))
++        me_dir = os.path.normcase(os.path.splitext(my_path)[0])
+         vsr_dir = os.path.normcase(os.path.splitext(versioneer_py)[0])
+-        if me_dir != vsr_dir:
+-            print("Warning: build in %s is using versioneer.py from %s"
+-                  % (os.path.dirname(me), versioneer_py))
++        if me_dir != vsr_dir and "VERSIONEER_PEP518" not in globals():
++            print(
++                "Warning: build in %s is using versioneer.py from %s"
++                % (os.path.dirname(my_path), versioneer_py)
++            )
+     except NameError:
+         pass
+     return root
+ 
+ 
+-def get_config_from_root(root):
++def get_config_from_root(root: str) -> VersioneerConfig:
+     """Read the project setup.cfg file to determine Versioneer config."""
+-    # This might raise EnvironmentError (if setup.cfg is missing), or
++    # This might raise OSError (if setup.cfg is missing), or
+     # configparser.NoSectionError (if it lacks a [versioneer] section), or
+     # configparser.NoOptionError (if it lacks "VCS="). See the docstring at
+     # the top of versioneer.py for instructions on writing your setup.cfg .
+-    setup_cfg = os.path.join(root, "setup.cfg")
+-    parser = configparser.SafeConfigParser()
+-    with open(setup_cfg, "r") as f:
+-        parser.readfp(f)
+-    VCS = parser.get("versioneer", "VCS")  # mandatory
+-
+-    def get(parser, name):
+-        if parser.has_option("versioneer", name):
+-            return parser.get("versioneer", name)
+-        return None
++    root_pth = Path(root)
++    pyproject_toml = root_pth / "pyproject.toml"
++    setup_cfg = root_pth / "setup.cfg"
++    section: Union[Dict[str, Any], configparser.SectionProxy, None] = None
++    if pyproject_toml.exists() and have_tomllib:
++        try:
++            with open(pyproject_toml, "rb") as fobj:
++                pp = tomllib.load(fobj)
++            section = pp["tool"]["versioneer"]
++        except (tomllib.TOMLDecodeError, KeyError) as e:
++            print(f"Failed to load config from {pyproject_toml}: {e}")
++            print("Try to load it from setup.cfg")
++    if not section:
++        parser = configparser.ConfigParser()
++        with open(setup_cfg) as cfg_file:
++            parser.read_file(cfg_file)
++        parser.get("versioneer", "VCS")  # raise error if missing
++
++        section = parser["versioneer"]
++
++    # `cast`` really shouldn't be used, but its simplest for the
++    # common VersioneerConfig users at the moment. We verify against
++    # `None` values elsewhere where it matters
++
+     cfg = VersioneerConfig()
+-    cfg.VCS = VCS
+-    cfg.style = get(parser, "style") or ""
+-    cfg.versionfile_source = get(parser, "versionfile_source")
+-    cfg.versionfile_build = get(parser, "versionfile_build")
+-    cfg.tag_prefix = get(parser, "tag_prefix")
+-    if cfg.tag_prefix in ("''", '""'):
++    cfg.VCS = section["VCS"]
++    cfg.style = section.get("style", "")
++    cfg.versionfile_source = cast(str, section.get("versionfile_source"))
++    cfg.versionfile_build = section.get("versionfile_build")
++    cfg.tag_prefix = cast(str, section.get("tag_prefix"))
++    if cfg.tag_prefix in ("''", '""', None):
+         cfg.tag_prefix = ""
+-    cfg.parentdir_prefix = get(parser, "parentdir_prefix")
+-    cfg.verbose = get(parser, "verbose")
++    cfg.parentdir_prefix = section.get("parentdir_prefix")
++    if isinstance(section, configparser.SectionProxy):
++        # Make sure configparser translates to bool
++        cfg.verbose = section.getboolean("verbose")
++    else:
++        cfg.verbose = section.get("verbose")
++
+     return cfg
+ 
+ 
+@@ -366,37 +447,54 @@ class NotThisMethod(Exception):
+ 
+ 
+ # these dictionaries contain VCS-specific tools
+-LONG_VERSION_PY = {}
+-HANDLERS = {}
++LONG_VERSION_PY: Dict[str, str] = {}
++HANDLERS: Dict[str, Dict[str, Callable]] = {}
+ 
+ 
+-def register_vcs_handler(vcs, method):  # decorator
+-    """Decorator to mark a method as the handler for a particular VCS."""
+-    def decorate(f):
++def register_vcs_handler(vcs: str, method: str) -> Callable:  # decorator
++    """Create decorator to mark a method as the handler of a VCS."""
++
++    def decorate(f: Callable) -> Callable:
+         """Store f in HANDLERS[vcs][method]."""
+-        if vcs not in HANDLERS:
+-            HANDLERS[vcs] = {}
+-        HANDLERS[vcs][method] = f
++        HANDLERS.setdefault(vcs, {})[method] = f
+         return f
++
+     return decorate
+ 
+ 
+-def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False,
+-                env=None):
++def run_command(
++    commands: List[str],
++    args: List[str],
++    cwd: Optional[str] = None,
++    verbose: bool = False,
++    hide_stderr: bool = False,
++    env: Optional[Dict[str, str]] = None,
++) -> Tuple[Optional[str], Optional[int]]:
+     """Call the given command(s)."""
+     assert isinstance(commands, list)
+-    p = None
+-    for c in commands:
++    process = None
++
++    popen_kwargs: Dict[str, Any] = {}
++    if sys.platform == "win32":
++        # This hides the console window if pythonw.exe is used
++        startupinfo = subprocess.STARTUPINFO()
++        startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
++        popen_kwargs["startupinfo"] = startupinfo
++
++    for command in commands:
+         try:
+-            dispcmd = str([c] + args)
++            dispcmd = str([command] + args)
+             # remember shell=False, so use git.cmd on windows, not just git
+-            p = subprocess.Popen([c] + args, cwd=cwd, env=env,
+-                                 stdout=subprocess.PIPE,
+-                                 stderr=(subprocess.PIPE if hide_stderr
+-                                         else None))
++            process = subprocess.Popen(
++                [command] + args,
++                cwd=cwd,
++                env=env,
++                stdout=subprocess.PIPE,
++                stderr=(subprocess.PIPE if hide_stderr else None),
++                **popen_kwargs,
++            )
+             break
+-        except EnvironmentError:
+-            e = sys.exc_info()[1]
++        except OSError as e:
+             if e.errno == errno.ENOENT:
+                 continue
+             if verbose:
+@@ -407,26 +505,27 @@ def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False,
+         if verbose:
+             print("unable to find command, tried %s" % (commands,))
+         return None, None
+-    stdout = p.communicate()[0].strip()
+-    if sys.version_info[0] >= 3:
+-        stdout = stdout.decode()
+-    if p.returncode != 0:
++    stdout = process.communicate()[0].strip().decode()
++    if process.returncode != 0:
+         if verbose:
+             print("unable to run %s (error)" % dispcmd)
+             print("stdout was %s" % stdout)
+-        return None, p.returncode
+-    return stdout, p.returncode
++        return None, process.returncode
++    return stdout, process.returncode
+ 
+ 
+-LONG_VERSION_PY['git'] = '''
++LONG_VERSION_PY[
++    "git"
++] = r'''
+ # This file helps to compute a version number in source trees obtained from
+ # git-archive tarball (such as those provided by githubs download-from-tag
+ # feature). Distribution tarballs (built by setup.py sdist) and build
+ # directories (produced by setup.py build) will contain a much shorter file
+ # that just contains the computed version number.
+ 
+-# This file is released into the public domain. Generated by
+-# versioneer-0.18 (https://github.com/warner/python-versioneer)
++# This file is released into the public domain.
++# Generated by versioneer-0.29
++# https://github.com/python-versioneer/python-versioneer
+ 
+ """Git implementation of _version.py."""
+ 
+@@ -435,9 +534,11 @@ import os
+ import re
+ import subprocess
+ import sys
++from typing import Any, Callable, Dict, List, Optional, Tuple
++import functools
+ 
+ 
+-def get_keywords():
++def get_keywords() -> Dict[str, str]:
+     """Get the keywords needed to look up the version information."""
+     # these strings will be replaced by git during git-archive.
+     # setup.py/versioneer.py will grep for the variable names, so they must
+@@ -453,8 +554,15 @@ def get_keywords():
+ class VersioneerConfig:
+     """Container for Versioneer configuration parameters."""
+ 
++    VCS: str
++    style: str
++    tag_prefix: str
++    parentdir_prefix: str
++    versionfile_source: str
++    verbose: bool
++
+ 
+-def get_config():
++def get_config() -> VersioneerConfig:
+     """Create, populate and return the VersioneerConfig() object."""
+     # these strings are filled in when 'setup.py versioneer' creates
+     # _version.py
+@@ -472,13 +580,13 @@ class NotThisMethod(Exception):
+     """Exception raised if a method is not valid for the current scenario."""
+ 
+ 
+-LONG_VERSION_PY = {}
+-HANDLERS = {}
++LONG_VERSION_PY: Dict[str, str] = {}
++HANDLERS: Dict[str, Dict[str, Callable]] = {}
+ 
+ 
+-def register_vcs_handler(vcs, method):  # decorator
+-    """Decorator to mark a method as the handler for a particular VCS."""
+-    def decorate(f):
++def register_vcs_handler(vcs: str, method: str) -> Callable:  # decorator
++    """Create decorator to mark a method as the handler of a VCS."""
++    def decorate(f: Callable) -> Callable:
+         """Store f in HANDLERS[vcs][method]."""
+         if vcs not in HANDLERS:
+             HANDLERS[vcs] = {}
+@@ -487,22 +595,35 @@ def register_vcs_handler(vcs, method):  # decorator
+     return decorate
+ 
+ 
+-def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False,
+-                env=None):
++def run_command(
++    commands: List[str],
++    args: List[str],
++    cwd: Optional[str] = None,
++    verbose: bool = False,
++    hide_stderr: bool = False,
++    env: Optional[Dict[str, str]] = None,
++) -> Tuple[Optional[str], Optional[int]]:
+     """Call the given command(s)."""
+     assert isinstance(commands, list)
+-    p = None
+-    for c in commands:
++    process = None
++
++    popen_kwargs: Dict[str, Any] = {}
++    if sys.platform == "win32":
++        # This hides the console window if pythonw.exe is used
++        startupinfo = subprocess.STARTUPINFO()
++        startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
++        popen_kwargs["startupinfo"] = startupinfo
++
++    for command in commands:
+         try:
+-            dispcmd = str([c] + args)
++            dispcmd = str([command] + args)
+             # remember shell=False, so use git.cmd on windows, not just git
+-            p = subprocess.Popen([c] + args, cwd=cwd, env=env,
+-                                 stdout=subprocess.PIPE,
+-                                 stderr=(subprocess.PIPE if hide_stderr
+-                                         else None))
++            process = subprocess.Popen([command] + args, cwd=cwd, env=env,
++                                       stdout=subprocess.PIPE,
++                                       stderr=(subprocess.PIPE if hide_stderr
++                                               else None), **popen_kwargs)
+             break
+-        except EnvironmentError:
+-            e = sys.exc_info()[1]
++        except OSError as e:
+             if e.errno == errno.ENOENT:
+                 continue
+             if verbose:
+@@ -513,18 +634,20 @@ def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False,
+         if verbose:
+             print("unable to find command, tried %%s" %% (commands,))
+         return None, None
+-    stdout = p.communicate()[0].strip()
+-    if sys.version_info[0] >= 3:
+-        stdout = stdout.decode()
+-    if p.returncode != 0:
++    stdout = process.communicate()[0].strip().decode()
++    if process.returncode != 0:
+         if verbose:
+             print("unable to run %%s (error)" %% dispcmd)
+             print("stdout was %%s" %% stdout)
+-        return None, p.returncode
+-    return stdout, p.returncode
++        return None, process.returncode
++    return stdout, process.returncode
+ 
+ 
+-def versions_from_parentdir(parentdir_prefix, root, verbose):
++def versions_from_parentdir(
++    parentdir_prefix: str,
++    root: str,
++    verbose: bool,
++) -> Dict[str, Any]:
+     """Try to determine the version from the parent directory name.
+ 
+     Source tarballs conventionally unpack into a directory that includes both
+@@ -533,15 +656,14 @@ def versions_from_parentdir(parentdir_prefix, root, verbose):
+     """
+     rootdirs = []
+ 
+-    for i in range(3):
++    for _ in range(3):
+         dirname = os.path.basename(root)
+         if dirname.startswith(parentdir_prefix):
+             return {"version": dirname[len(parentdir_prefix):],
+                     "full-revisionid": None,
+                     "dirty": False, "error": None, "date": None}
+-        else:
+-            rootdirs.append(root)
+-            root = os.path.dirname(root)  # up a level
++        rootdirs.append(root)
++        root = os.path.dirname(root)  # up a level
+ 
+     if verbose:
+         print("Tried directories %%s but none started with prefix %%s" %%
+@@ -550,41 +672,48 @@ def versions_from_parentdir(parentdir_prefix, root, verbose):
+ 
+ 
+ @register_vcs_handler("git", "get_keywords")
+-def git_get_keywords(versionfile_abs):
++def git_get_keywords(versionfile_abs: str) -> Dict[str, str]:
+     """Extract version information from the given file."""
+     # the code embedded in _version.py can just fetch the value of these
+     # keywords. When used from setup.py, we don't want to import _version.py,
+     # so we do it with a regexp instead. This function is not used from
+     # _version.py.
+-    keywords = {}
++    keywords: Dict[str, str] = {}
+     try:
+-        f = open(versionfile_abs, "r")
+-        for line in f.readlines():
+-            if line.strip().startswith("git_refnames ="):
+-                mo = re.search(r'=\s*"(.*)"', line)
+-                if mo:
+-                    keywords["refnames"] = mo.group(1)
+-            if line.strip().startswith("git_full ="):
+-                mo = re.search(r'=\s*"(.*)"', line)
+-                if mo:
+-                    keywords["full"] = mo.group(1)
+-            if line.strip().startswith("git_date ="):
+-                mo = re.search(r'=\s*"(.*)"', line)
+-                if mo:
+-                    keywords["date"] = mo.group(1)
+-        f.close()
+-    except EnvironmentError:
++        with open(versionfile_abs, "r") as fobj:
++            for line in fobj:
++                if line.strip().startswith("git_refnames ="):
++                    mo = re.search(r'=\s*"(.*)"', line)
++                    if mo:
++                        keywords["refnames"] = mo.group(1)
++                if line.strip().startswith("git_full ="):
++                    mo = re.search(r'=\s*"(.*)"', line)
++                    if mo:
++                        keywords["full"] = mo.group(1)
++                if line.strip().startswith("git_date ="):
++                    mo = re.search(r'=\s*"(.*)"', line)
++                    if mo:
++                        keywords["date"] = mo.group(1)
++    except OSError:
+         pass
+     return keywords
+ 
+ 
+ @register_vcs_handler("git", "keywords")
+-def git_versions_from_keywords(keywords, tag_prefix, verbose):
++def git_versions_from_keywords(
++    keywords: Dict[str, str],
++    tag_prefix: str,
++    verbose: bool,
++) -> Dict[str, Any]:
+     """Get version information from git keywords."""
+-    if not keywords:
+-        raise NotThisMethod("no keywords at all, weird")
++    if "refnames" not in keywords:
++        raise NotThisMethod("Short version file found")
+     date = keywords.get("date")
+     if date is not None:
++        # Use only the last line.  Previous lines may contain GPG signature
++        # information.
++        date = date.splitlines()[-1]
++
+         # git-2.2.0 added "%%cI", which expands to an ISO-8601 -compliant
+         # datestamp. However we prefer "%%ci" (which expands to an "ISO-8601
+         # -like" string, which we must then edit to make compliant), because
+@@ -597,11 +726,11 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose):
+         if verbose:
+             print("keywords are unexpanded, not using")
+         raise NotThisMethod("unexpanded keywords, not a git-archive tarball")
+-    refs = set([r.strip() for r in refnames.strip("()").split(",")])
++    refs = {r.strip() for r in refnames.strip("()").split(",")}
+     # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of
+     # just "foo-1.0". If we see a "tag: " prefix, prefer those.
+     TAG = "tag: "
+-    tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)])
++    tags = {r[len(TAG):] for r in refs if r.startswith(TAG)}
+     if not tags:
+         # Either we're using git < 1.8.3, or there really are no tags. We use
+         # a heuristic: assume all version tags have a digit. The old git %%d
+@@ -610,7 +739,7 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose):
+         # between branches and tags. By ignoring refnames without digits, we
+         # filter out many common branch names like "release" and
+         # "stabilization", as well as "HEAD" and "master".
+-        tags = set([r for r in refs if re.search(r'\d', r)])
++        tags = {r for r in refs if re.search(r'\d', r)}
+         if verbose:
+             print("discarding '%%s', no digits" %% ",".join(refs - tags))
+     if verbose:
+@@ -619,6 +748,11 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose):
+         # sorting will prefer e.g. "2.0" over "2.0rc1"
+         if ref.startswith(tag_prefix):
+             r = ref[len(tag_prefix):]
++            # Filter out refs that exactly match prefix or that don't start
++            # with a number once the prefix is stripped (mostly a concern
++            # when prefix is '')
++            if not re.match(r'\d', r):
++                continue
+             if verbose:
+                 print("picking %%s" %% r)
+             return {"version": r,
+@@ -634,7 +768,12 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose):
+ 
+ 
+ @register_vcs_handler("git", "pieces_from_vcs")
+-def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
++def git_pieces_from_vcs(
++    tag_prefix: str,
++    root: str,
++    verbose: bool,
++    runner: Callable = run_command
++) -> Dict[str, Any]:
+     """Get version from 'git describe' in the root of the source tree.
+ 
+     This only gets called if the git-archive 'subst' keywords were *not*
+@@ -645,8 +784,15 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
+     if sys.platform == "win32":
+         GITS = ["git.cmd", "git.exe"]
+ 
+-    out, rc = run_command(GITS, ["rev-parse", "--git-dir"], cwd=root,
+-                          hide_stderr=True)
++    # GIT_DIR can interfere with correct operation of Versioneer.
++    # It may be intended to be passed to the Versioneer-versioned project,
++    # but that should not change where we get our version from.
++    env = os.environ.copy()
++    env.pop("GIT_DIR", None)
++    runner = functools.partial(runner, env=env)
++
++    _, rc = runner(GITS, ["rev-parse", "--git-dir"], cwd=root,
++                   hide_stderr=not verbose)
+     if rc != 0:
+         if verbose:
+             print("Directory %%s not under git control" %% root)
+@@ -654,24 +800,57 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
+ 
+     # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty]
+     # if there isn't one, this yields HEX[-dirty] (no NUM)
+-    describe_out, rc = run_command(GITS, ["describe", "--tags", "--dirty",
+-                                          "--always", "--long",
+-                                          "--match", "%%s*" %% tag_prefix],
+-                                   cwd=root)
++    describe_out, rc = runner(GITS, [
++        "describe", "--tags", "--dirty", "--always", "--long",
++        "--match", f"{tag_prefix}[[:digit:]]*"
++    ], cwd=root)
+     # --long was added in git-1.5.5
+     if describe_out is None:
+         raise NotThisMethod("'git describe' failed")
+     describe_out = describe_out.strip()
+-    full_out, rc = run_command(GITS, ["rev-parse", "HEAD"], cwd=root)
++    full_out, rc = runner(GITS, ["rev-parse", "HEAD"], cwd=root)
+     if full_out is None:
+         raise NotThisMethod("'git rev-parse' failed")
+     full_out = full_out.strip()
+ 
+-    pieces = {}
++    pieces: Dict[str, Any] = {}
+     pieces["long"] = full_out
+     pieces["short"] = full_out[:7]  # maybe improved later
+     pieces["error"] = None
+ 
++    branch_name, rc = runner(GITS, ["rev-parse", "--abbrev-ref", "HEAD"],
++                             cwd=root)
++    # --abbrev-ref was added in git-1.6.3
++    if rc != 0 or branch_name is None:
++        raise NotThisMethod("'git rev-parse --abbrev-ref' returned error")
++    branch_name = branch_name.strip()
++
++    if branch_name == "HEAD":
++        # If we aren't exactly on a branch, pick a branch which represents
++        # the current commit. If all else fails, we are on a branchless
++        # commit.
++        branches, rc = runner(GITS, ["branch", "--contains"], cwd=root)
++        # --contains was added in git-1.5.4
++        if rc != 0 or branches is None:
++            raise NotThisMethod("'git branch --contains' returned error")
++        branches = branches.split("\n")
++
++        # Remove the first line if we're running detached
++        if "(" in branches[0]:
++            branches.pop(0)
++
++        # Strip off the leading "* " from the list of branches.
++        branches = [branch[2:] for branch in branches]
++        if "master" in branches:
++            branch_name = "master"
++        elif not branches:
++            branch_name = None
++        else:
++            # Pick the first branch that is returned. Good or bad.
++            branch_name = branches[0]
++
++    pieces["branch"] = branch_name
++
+     # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty]
+     # TAG might have hyphens.
+     git_describe = describe_out
+@@ -688,7 +867,7 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
+         # TAG-NUM-gHEX
+         mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe)
+         if not mo:
+-            # unparseable. Maybe git-describe is misbehaving?
++            # unparsable. Maybe git-describe is misbehaving?
+             pieces["error"] = ("unable to parse git-describe output: '%%s'"
+                                %% describe_out)
+             return pieces
+@@ -713,26 +892,27 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
+     else:
+         # HEX: no tags
+         pieces["closest-tag"] = None
+-        count_out, rc = run_command(GITS, ["rev-list", "HEAD", "--count"],
+-                                    cwd=root)
+-        pieces["distance"] = int(count_out)  # total number of commits
++        out, rc = runner(GITS, ["rev-list", "HEAD", "--left-right"], cwd=root)
++        pieces["distance"] = len(out.split())  # total number of commits
+ 
+     # commit date: see ISO-8601 comment in git_versions_from_keywords()
+-    date = run_command(GITS, ["show", "-s", "--format=%%ci", "HEAD"],
+-                       cwd=root)[0].strip()
++    date = runner(GITS, ["show", "-s", "--format=%%ci", "HEAD"], cwd=root)[0].strip()
++    # Use only the last line.  Previous lines may contain GPG signature
++    # information.
++    date = date.splitlines()[-1]
+     pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1)
+ 
+     return pieces
+ 
+ 
+-def plus_or_dot(pieces):
++def plus_or_dot(pieces: Dict[str, Any]) -> str:
+     """Return a + if we don't already have one, else return a ."""
+     if "+" in pieces.get("closest-tag", ""):
+         return "."
+     return "+"
+ 
+ 
+-def render_pep440(pieces):
++def render_pep440(pieces: Dict[str, Any]) -> str:
+     """Build up version string, with post-release "local version identifier".
+ 
+     Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you
+@@ -757,23 +937,71 @@ def render_pep440(pieces):
+     return rendered
+ 
+ 
+-def render_pep440_pre(pieces):
+-    """TAG[.post.devDISTANCE] -- No -dirty.
++def render_pep440_branch(pieces: Dict[str, Any]) -> str:
++    """TAG[[.dev0]+DISTANCE.gHEX[.dirty]] .
++
++    The ".dev0" means not master branch. Note that .dev0 sorts backwards
++    (a feature branch will appear "older" than the master branch).
+ 
+     Exceptions:
+-    1: no tags. 0.post.devDISTANCE
++    1: no tags. 0[.dev0]+untagged.DISTANCE.gHEX[.dirty]
+     """
+     if pieces["closest-tag"]:
+         rendered = pieces["closest-tag"]
++        if pieces["distance"] or pieces["dirty"]:
++            if pieces["branch"] != "master":
++                rendered += ".dev0"
++            rendered += plus_or_dot(pieces)
++            rendered += "%%d.g%%s" %% (pieces["distance"], pieces["short"])
++            if pieces["dirty"]:
++                rendered += ".dirty"
++    else:
++        # exception #1
++        rendered = "0"
++        if pieces["branch"] != "master":
++            rendered += ".dev0"
++        rendered += "+untagged.%%d.g%%s" %% (pieces["distance"],
++                                          pieces["short"])
++        if pieces["dirty"]:
++            rendered += ".dirty"
++    return rendered
++
++
++def pep440_split_post(ver: str) -> Tuple[str, Optional[int]]:
++    """Split pep440 version string at the post-release segment.
++
++    Returns the release segments before the post-release and the
++    post-release version number (or -1 if no post-release segment is present).
++    """
++    vc = str.split(ver, ".post")
++    return vc[0], int(vc[1] or 0) if len(vc) == 2 else None
++
++
++def render_pep440_pre(pieces: Dict[str, Any]) -> str:
++    """TAG[.postN.devDISTANCE] -- No -dirty.
++
++    Exceptions:
++    1: no tags. 0.post0.devDISTANCE
++    """
++    if pieces["closest-tag"]:
+         if pieces["distance"]:
+-            rendered += ".post.dev%%d" %% pieces["distance"]
++            # update the post release segment
++            tag_version, post_version = pep440_split_post(pieces["closest-tag"])
++            rendered = tag_version
++            if post_version is not None:
++                rendered += ".post%%d.dev%%d" %% (post_version + 1, pieces["distance"])
++            else:
++                rendered += ".post0.dev%%d" %% (pieces["distance"])
++        else:
++            # no commits, use the tag as the version
++            rendered = pieces["closest-tag"]
+     else:
+         # exception #1
+-        rendered = "0.post.dev%%d" %% pieces["distance"]
++        rendered = "0.post0.dev%%d" %% pieces["distance"]
+     return rendered
+ 
+ 
+-def render_pep440_post(pieces):
++def render_pep440_post(pieces: Dict[str, Any]) -> str:
+     """TAG[.postDISTANCE[.dev0]+gHEX] .
+ 
+     The ".dev0" means dirty. Note that .dev0 sorts backwards
+@@ -800,12 +1028,41 @@ def render_pep440_post(pieces):
+     return rendered
+ 
+ 
+-def render_pep440_old(pieces):
++def render_pep440_post_branch(pieces: Dict[str, Any]) -> str:
++    """TAG[.postDISTANCE[.dev0]+gHEX[.dirty]] .
++
++    The ".dev0" means not master branch.
++
++    Exceptions:
++    1: no tags. 0.postDISTANCE[.dev0]+gHEX[.dirty]
++    """
++    if pieces["closest-tag"]:
++        rendered = pieces["closest-tag"]
++        if pieces["distance"] or pieces["dirty"]:
++            rendered += ".post%%d" %% pieces["distance"]
++            if pieces["branch"] != "master":
++                rendered += ".dev0"
++            rendered += plus_or_dot(pieces)
++            rendered += "g%%s" %% pieces["short"]
++            if pieces["dirty"]:
++                rendered += ".dirty"
++    else:
++        # exception #1
++        rendered = "0.post%%d" %% pieces["distance"]
++        if pieces["branch"] != "master":
++            rendered += ".dev0"
++        rendered += "+g%%s" %% pieces["short"]
++        if pieces["dirty"]:
++            rendered += ".dirty"
++    return rendered
++
++
++def render_pep440_old(pieces: Dict[str, Any]) -> str:
+     """TAG[.postDISTANCE[.dev0]] .
+ 
+     The ".dev0" means dirty.
+ 
+-    Eexceptions:
++    Exceptions:
+     1: no tags. 0.postDISTANCE[.dev0]
+     """
+     if pieces["closest-tag"]:
+@@ -822,7 +1079,7 @@ def render_pep440_old(pieces):
+     return rendered
+ 
+ 
+-def render_git_describe(pieces):
++def render_git_describe(pieces: Dict[str, Any]) -> str:
+     """TAG[-DISTANCE-gHEX][-dirty].
+ 
+     Like 'git describe --tags --dirty --always'.
+@@ -842,7 +1099,7 @@ def render_git_describe(pieces):
+     return rendered
+ 
+ 
+-def render_git_describe_long(pieces):
++def render_git_describe_long(pieces: Dict[str, Any]) -> str:
+     """TAG-DISTANCE-gHEX[-dirty].
+ 
+     Like 'git describe --tags --dirty --always -long'.
+@@ -862,7 +1119,7 @@ def render_git_describe_long(pieces):
+     return rendered
+ 
+ 
+-def render(pieces, style):
++def render(pieces: Dict[str, Any], style: str) -> Dict[str, Any]:
+     """Render the given version pieces into the requested style."""
+     if pieces["error"]:
+         return {"version": "unknown",
+@@ -876,10 +1133,14 @@ def render(pieces, style):
+ 
+     if style == "pep440":
+         rendered = render_pep440(pieces)
++    elif style == "pep440-branch":
++        rendered = render_pep440_branch(pieces)
+     elif style == "pep440-pre":
+         rendered = render_pep440_pre(pieces)
+     elif style == "pep440-post":
+         rendered = render_pep440_post(pieces)
++    elif style == "pep440-post-branch":
++        rendered = render_pep440_post_branch(pieces)
+     elif style == "pep440-old":
+         rendered = render_pep440_old(pieces)
+     elif style == "git-describe":
+@@ -894,7 +1155,7 @@ def render(pieces, style):
+             "date": pieces.get("date")}
+ 
+ 
+-def get_versions():
++def get_versions() -> Dict[str, Any]:
+     """Get version information or return default if unable to do so."""
+     # I am in _version.py, which lives at ROOT/VERSIONFILE_SOURCE. If we have
+     # __file__, we can work backwards from there to the root. Some
+@@ -915,7 +1176,7 @@ def get_versions():
+         # versionfile_source is the relative path from the top of the source
+         # tree (where the .git directory might live) to this file. Invert
+         # this to find the root from __file__.
+-        for i in cfg.versionfile_source.split('/'):
++        for _ in cfg.versionfile_source.split('/'):
+             root = os.path.dirname(root)
+     except NameError:
+         return {"version": "0+unknown", "full-revisionid": None,
+@@ -942,41 +1203,48 @@ def get_versions():
+ 
+ 
+ @register_vcs_handler("git", "get_keywords")
+-def git_get_keywords(versionfile_abs):
++def git_get_keywords(versionfile_abs: str) -> Dict[str, str]:
+     """Extract version information from the given file."""
+     # the code embedded in _version.py can just fetch the value of these
+     # keywords. When used from setup.py, we don't want to import _version.py,
+     # so we do it with a regexp instead. This function is not used from
+     # _version.py.
+-    keywords = {}
++    keywords: Dict[str, str] = {}
+     try:
+-        f = open(versionfile_abs, "r")
+-        for line in f.readlines():
+-            if line.strip().startswith("git_refnames ="):
+-                mo = re.search(r'=\s*"(.*)"', line)
+-                if mo:
+-                    keywords["refnames"] = mo.group(1)
+-            if line.strip().startswith("git_full ="):
+-                mo = re.search(r'=\s*"(.*)"', line)
+-                if mo:
+-                    keywords["full"] = mo.group(1)
+-            if line.strip().startswith("git_date ="):
+-                mo = re.search(r'=\s*"(.*)"', line)
+-                if mo:
+-                    keywords["date"] = mo.group(1)
+-        f.close()
+-    except EnvironmentError:
++        with open(versionfile_abs, "r") as fobj:
++            for line in fobj:
++                if line.strip().startswith("git_refnames ="):
++                    mo = re.search(r'=\s*"(.*)"', line)
++                    if mo:
++                        keywords["refnames"] = mo.group(1)
++                if line.strip().startswith("git_full ="):
++                    mo = re.search(r'=\s*"(.*)"', line)
++                    if mo:
++                        keywords["full"] = mo.group(1)
++                if line.strip().startswith("git_date ="):
++                    mo = re.search(r'=\s*"(.*)"', line)
++                    if mo:
++                        keywords["date"] = mo.group(1)
++    except OSError:
+         pass
+     return keywords
+ 
+ 
+ @register_vcs_handler("git", "keywords")
+-def git_versions_from_keywords(keywords, tag_prefix, verbose):
++def git_versions_from_keywords(
++    keywords: Dict[str, str],
++    tag_prefix: str,
++    verbose: bool,
++) -> Dict[str, Any]:
+     """Get version information from git keywords."""
+-    if not keywords:
+-        raise NotThisMethod("no keywords at all, weird")
++    if "refnames" not in keywords:
++        raise NotThisMethod("Short version file found")
+     date = keywords.get("date")
+     if date is not None:
++        # Use only the last line.  Previous lines may contain GPG signature
++        # information.
++        date = date.splitlines()[-1]
++
+         # git-2.2.0 added "%cI", which expands to an ISO-8601 -compliant
+         # datestamp. However we prefer "%ci" (which expands to an "ISO-8601
+         # -like" string, which we must then edit to make compliant), because
+@@ -989,11 +1257,11 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose):
+         if verbose:
+             print("keywords are unexpanded, not using")
+         raise NotThisMethod("unexpanded keywords, not a git-archive tarball")
+-    refs = set([r.strip() for r in refnames.strip("()").split(",")])
++    refs = {r.strip() for r in refnames.strip("()").split(",")}
+     # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of
+     # just "foo-1.0". If we see a "tag: " prefix, prefer those.
+     TAG = "tag: "
+-    tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)])
++    tags = {r[len(TAG) :] for r in refs if r.startswith(TAG)}
+     if not tags:
+         # Either we're using git < 1.8.3, or there really are no tags. We use
+         # a heuristic: assume all version tags have a digit. The old git %d
+@@ -1002,7 +1270,7 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose):
+         # between branches and tags. By ignoring refnames without digits, we
+         # filter out many common branch names like "release" and
+         # "stabilization", as well as "HEAD" and "master".
+-        tags = set([r for r in refs if re.search(r'\d', r)])
++        tags = {r for r in refs if re.search(r"\d", r)}
+         if verbose:
+             print("discarding '%s', no digits" % ",".join(refs - tags))
+     if verbose:
+@@ -1010,23 +1278,37 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose):
+     for ref in sorted(tags):
+         # sorting will prefer e.g. "2.0" over "2.0rc1"
+         if ref.startswith(tag_prefix):
+-            r = ref[len(tag_prefix):]
++            r = ref[len(tag_prefix) :]
++            # Filter out refs that exactly match prefix or that don't start
++            # with a number once the prefix is stripped (mostly a concern
++            # when prefix is '')
++            if not re.match(r"\d", r):
++                continue
+             if verbose:
+                 print("picking %s" % r)
+-            return {"version": r,
+-                    "full-revisionid": keywords["full"].strip(),
+-                    "dirty": False, "error": None,
+-                    "date": date}
++            return {
++                "version": r,
++                "full-revisionid": keywords["full"].strip(),
++                "dirty": False,
++                "error": None,
++                "date": date,
++            }
+     # no suitable tags, so version is "0+unknown", but full hex is still there
+     if verbose:
+         print("no suitable tags, using unknown + full revision id")
+-    return {"version": "0+unknown",
+-            "full-revisionid": keywords["full"].strip(),
+-            "dirty": False, "error": "no suitable tags", "date": None}
++    return {
++        "version": "0+unknown",
++        "full-revisionid": keywords["full"].strip(),
++        "dirty": False,
++        "error": "no suitable tags",
++        "date": None,
++    }
+ 
+ 
+ @register_vcs_handler("git", "pieces_from_vcs")
+-def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
++def git_pieces_from_vcs(
++    tag_prefix: str, root: str, verbose: bool, runner: Callable = run_command
++) -> Dict[str, Any]:
+     """Get version from 'git describe' in the root of the source tree.
+ 
+     This only gets called if the git-archive 'subst' keywords were *not*
+@@ -1037,8 +1319,14 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
+     if sys.platform == "win32":
+         GITS = ["git.cmd", "git.exe"]
+ 
+-    out, rc = run_command(GITS, ["rev-parse", "--git-dir"], cwd=root,
+-                          hide_stderr=True)
++    # GIT_DIR can interfere with correct operation of Versioneer.
++    # It may be intended to be passed to the Versioneer-versioned project,
++    # but that should not change where we get our version from.
++    env = os.environ.copy()
++    env.pop("GIT_DIR", None)
++    runner = functools.partial(runner, env=env)
++
++    _, rc = runner(GITS, ["rev-parse", "--git-dir"], cwd=root, hide_stderr=not verbose)
+     if rc != 0:
+         if verbose:
+             print("Directory %s not under git control" % root)
+@@ -1046,24 +1334,65 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
+ 
+     # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty]
+     # if there isn't one, this yields HEX[-dirty] (no NUM)
+-    describe_out, rc = run_command(GITS, ["describe", "--tags", "--dirty",
+-                                          "--always", "--long",
+-                                          "--match", "%s*" % tag_prefix],
+-                                   cwd=root)
++    describe_out, rc = runner(
++        GITS,
++        [
++            "describe",
++            "--tags",
++            "--dirty",
++            "--always",
++            "--long",
++            "--match",
++            f"{tag_prefix}[[:digit:]]*",
++        ],
++        cwd=root,
++    )
+     # --long was added in git-1.5.5
+     if describe_out is None:
+         raise NotThisMethod("'git describe' failed")
+     describe_out = describe_out.strip()
+-    full_out, rc = run_command(GITS, ["rev-parse", "HEAD"], cwd=root)
++    full_out, rc = runner(GITS, ["rev-parse", "HEAD"], cwd=root)
+     if full_out is None:
+         raise NotThisMethod("'git rev-parse' failed")
+     full_out = full_out.strip()
+ 
+-    pieces = {}
++    pieces: Dict[str, Any] = {}
+     pieces["long"] = full_out
+     pieces["short"] = full_out[:7]  # maybe improved later
+     pieces["error"] = None
+ 
++    branch_name, rc = runner(GITS, ["rev-parse", "--abbrev-ref", "HEAD"], cwd=root)
++    # --abbrev-ref was added in git-1.6.3
++    if rc != 0 or branch_name is None:
++        raise NotThisMethod("'git rev-parse --abbrev-ref' returned error")
++    branch_name = branch_name.strip()
++
++    if branch_name == "HEAD":
++        # If we aren't exactly on a branch, pick a branch which represents
++        # the current commit. If all else fails, we are on a branchless
++        # commit.
++        branches, rc = runner(GITS, ["branch", "--contains"], cwd=root)
++        # --contains was added in git-1.5.4
++        if rc != 0 or branches is None:
++            raise NotThisMethod("'git branch --contains' returned error")
++        branches = branches.split("\n")
++
++        # Remove the first line if we're running detached
++        if "(" in branches[0]:
++            branches.pop(0)
++
++        # Strip off the leading "* " from the list of branches.
++        branches = [branch[2:] for branch in branches]
++        if "master" in branches:
++            branch_name = "master"
++        elif not branches:
++            branch_name = None
++        else:
++            # Pick the first branch that is returned. Good or bad.
++            branch_name = branches[0]
++
++    pieces["branch"] = branch_name
++
+     # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty]
+     # TAG might have hyphens.
+     git_describe = describe_out
+@@ -1072,17 +1401,16 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
+     dirty = git_describe.endswith("-dirty")
+     pieces["dirty"] = dirty
+     if dirty:
+-        git_describe = git_describe[:git_describe.rindex("-dirty")]
++        git_describe = git_describe[: git_describe.rindex("-dirty")]
+ 
+     # now we have TAG-NUM-gHEX or HEX
+ 
+     if "-" in git_describe:
+         # TAG-NUM-gHEX
+-        mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe)
++        mo = re.search(r"^(.+)-(\d+)-g([0-9a-f]+)$", git_describe)
+         if not mo:
+-            # unparseable. Maybe git-describe is misbehaving?
+-            pieces["error"] = ("unable to parse git-describe output: '%s'"
+-                               % describe_out)
++            # unparsable. Maybe git-describe is misbehaving?
++            pieces["error"] = "unable to parse git-describe output: '%s'" % describe_out
+             return pieces
+ 
+         # tag
+@@ -1091,10 +1419,12 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
+             if verbose:
+                 fmt = "tag '%s' doesn't start with prefix '%s'"
+                 print(fmt % (full_tag, tag_prefix))
+-            pieces["error"] = ("tag '%s' doesn't start with prefix '%s'"
+-                               % (full_tag, tag_prefix))
++            pieces["error"] = "tag '%s' doesn't start with prefix '%s'" % (
++                full_tag,
++                tag_prefix,
++            )
+             return pieces
+-        pieces["closest-tag"] = full_tag[len(tag_prefix):]
++        pieces["closest-tag"] = full_tag[len(tag_prefix) :]
+ 
+         # distance: number of commits since tag
+         pieces["distance"] = int(mo.group(2))
+@@ -1105,19 +1435,20 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
+     else:
+         # HEX: no tags
+         pieces["closest-tag"] = None
+-        count_out, rc = run_command(GITS, ["rev-list", "HEAD", "--count"],
+-                                    cwd=root)
+-        pieces["distance"] = int(count_out)  # total number of commits
++        out, rc = runner(GITS, ["rev-list", "HEAD", "--left-right"], cwd=root)
++        pieces["distance"] = len(out.split())  # total number of commits
+ 
+     # commit date: see ISO-8601 comment in git_versions_from_keywords()
+-    date = run_command(GITS, ["show", "-s", "--format=%ci", "HEAD"],
+-                       cwd=root)[0].strip()
++    date = runner(GITS, ["show", "-s", "--format=%ci", "HEAD"], cwd=root)[0].strip()
++    # Use only the last line.  Previous lines may contain GPG signature
++    # information.
++    date = date.splitlines()[-1]
+     pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1)
+ 
+     return pieces
+ 
+ 
+-def do_vcs_install(manifest_in, versionfile_source, ipy):
++def do_vcs_install(versionfile_source: str, ipy: Optional[str]) -> None:
+     """Git-specific installation logic for Versioneer.
+ 
+     For Git, this means creating/changing .gitattributes to mark _version.py
+@@ -1126,36 +1457,40 @@ def do_vcs_install(manifest_in, versionfile_source, ipy):
+     GITS = ["git"]
+     if sys.platform == "win32":
+         GITS = ["git.cmd", "git.exe"]
+-    files = [manifest_in, versionfile_source]
++    files = [versionfile_source]
+     if ipy:
+         files.append(ipy)
+-    try:
+-        me = __file__
+-        if me.endswith(".pyc") or me.endswith(".pyo"):
+-            me = os.path.splitext(me)[0] + ".py"
+-        versioneer_file = os.path.relpath(me)
+-    except NameError:
+-        versioneer_file = "versioneer.py"
+-    files.append(versioneer_file)
++    if "VERSIONEER_PEP518" not in globals():
++        try:
++            my_path = __file__
++            if my_path.endswith((".pyc", ".pyo")):
++                my_path = os.path.splitext(my_path)[0] + ".py"
++            versioneer_file = os.path.relpath(my_path)
++        except NameError:
++            versioneer_file = "versioneer.py"
++        files.append(versioneer_file)
+     present = False
+     try:
+-        f = open(".gitattributes", "r")
+-        for line in f.readlines():
+-            if line.strip().startswith(versionfile_source):
+-                if "export-subst" in line.strip().split()[1:]:
+-                    present = True
+-        f.close()
+-    except EnvironmentError:
++        with open(".gitattributes", "r") as fobj:
++            for line in fobj:
++                if line.strip().startswith(versionfile_source):
++                    if "export-subst" in line.strip().split()[1:]:
++                        present = True
++                        break
++    except OSError:
+         pass
+     if not present:
+-        f = open(".gitattributes", "a+")
+-        f.write("%s export-subst\n" % versionfile_source)
+-        f.close()
++        with open(".gitattributes", "a+") as fobj:
++            fobj.write(f"{versionfile_source} export-subst\n")
+         files.append(".gitattributes")
+     run_command(GITS, ["add", "--"] + files)
+ 
+ 
+-def versions_from_parentdir(parentdir_prefix, root, verbose):
++def versions_from_parentdir(
++    parentdir_prefix: str,
++    root: str,
++    verbose: bool,
++) -> Dict[str, Any]:
+     """Try to determine the version from the parent directory name.
+ 
+     Source tarballs conventionally unpack into a directory that includes both
+@@ -1164,24 +1499,29 @@ def versions_from_parentdir(parentdir_prefix, root, verbose):
+     """
+     rootdirs = []
+ 
+-    for i in range(3):
++    for _ in range(3):
+         dirname = os.path.basename(root)
+         if dirname.startswith(parentdir_prefix):
+-            return {"version": dirname[len(parentdir_prefix):],
+-                    "full-revisionid": None,
+-                    "dirty": False, "error": None, "date": None}
+-        else:
+-            rootdirs.append(root)
+-            root = os.path.dirname(root)  # up a level
++            return {
++                "version": dirname[len(parentdir_prefix) :],
++                "full-revisionid": None,
++                "dirty": False,
++                "error": None,
++                "date": None,
++            }
++        rootdirs.append(root)
++        root = os.path.dirname(root)  # up a level
+ 
+     if verbose:
+-        print("Tried directories %s but none started with prefix %s" %
+-              (str(rootdirs), parentdir_prefix))
++        print(
++            "Tried directories %s but none started with prefix %s"
++            % (str(rootdirs), parentdir_prefix)
++        )
+     raise NotThisMethod("rootdir doesn't start with parentdir_prefix")
+ 
+ 
+ SHORT_VERSION_PY = """
+-# This file was generated by 'versioneer.py' (0.18) from
++# This file was generated by 'versioneer.py' (0.29) from
+ # revision-control system data, or from the parent directory name of an
+ # unpacked source archive. Distribution tarballs contain a pre-generated copy
+ # of this file.
+@@ -1198,42 +1538,42 @@ def get_versions():
+ """
+ 
+ 
+-def versions_from_file(filename):
++def versions_from_file(filename: str) -> Dict[str, Any]:
+     """Try to determine the version from _version.py if present."""
+     try:
+         with open(filename) as f:
+             contents = f.read()
+-    except EnvironmentError:
++    except OSError:
+         raise NotThisMethod("unable to read _version.py")
+-    mo = re.search(r"version_json = '''\n(.*)'''  # END VERSION_JSON",
+-                   contents, re.M | re.S)
++    mo = re.search(
++        r"version_json = '''\n(.*)'''  # END VERSION_JSON", contents, re.M | re.S
++    )
+     if not mo:
+-        mo = re.search(r"version_json = '''\r\n(.*)'''  # END VERSION_JSON",
+-                       contents, re.M | re.S)
++        mo = re.search(
++            r"version_json = '''\r\n(.*)'''  # END VERSION_JSON", contents, re.M | re.S
++        )
+     if not mo:
+         raise NotThisMethod("no version_json in _version.py")
+     return json.loads(mo.group(1))
+ 
+ 
+-def write_to_version_file(filename, versions):
++def write_to_version_file(filename: str, versions: Dict[str, Any]) -> None:
+     """Write the given version number to the given _version.py file."""
+-    os.unlink(filename)
+-    contents = json.dumps(versions, sort_keys=True,
+-                          indent=1, separators=(",", ": "))
++    contents = json.dumps(versions, sort_keys=True, indent=1, separators=(",", ": "))
+     with open(filename, "w") as f:
+         f.write(SHORT_VERSION_PY % contents)
+ 
+     print("set %s to '%s'" % (filename, versions["version"]))
+ 
+ 
+-def plus_or_dot(pieces):
++def plus_or_dot(pieces: Dict[str, Any]) -> str:
+     """Return a + if we don't already have one, else return a ."""
+     if "+" in pieces.get("closest-tag", ""):
+         return "."
+     return "+"
+ 
+ 
+-def render_pep440(pieces):
++def render_pep440(pieces: Dict[str, Any]) -> str:
+     """Build up version string, with post-release "local version identifier".
+ 
+     Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you
+@@ -1251,30 +1591,76 @@ def render_pep440(pieces):
+                 rendered += ".dirty"
+     else:
+         # exception #1
+-        rendered = "0+untagged.%d.g%s" % (pieces["distance"],
+-                                          pieces["short"])
++        rendered = "0+untagged.%d.g%s" % (pieces["distance"], pieces["short"])
+         if pieces["dirty"]:
+             rendered += ".dirty"
+     return rendered
+ 
+ 
+-def render_pep440_pre(pieces):
+-    """TAG[.post.devDISTANCE] -- No -dirty.
++def render_pep440_branch(pieces: Dict[str, Any]) -> str:
++    """TAG[[.dev0]+DISTANCE.gHEX[.dirty]] .
++
++    The ".dev0" means not master branch. Note that .dev0 sorts backwards
++    (a feature branch will appear "older" than the master branch).
+ 
+     Exceptions:
+-    1: no tags. 0.post.devDISTANCE
++    1: no tags. 0[.dev0]+untagged.DISTANCE.gHEX[.dirty]
+     """
+     if pieces["closest-tag"]:
+         rendered = pieces["closest-tag"]
++        if pieces["distance"] or pieces["dirty"]:
++            if pieces["branch"] != "master":
++                rendered += ".dev0"
++            rendered += plus_or_dot(pieces)
++            rendered += "%d.g%s" % (pieces["distance"], pieces["short"])
++            if pieces["dirty"]:
++                rendered += ".dirty"
++    else:
++        # exception #1
++        rendered = "0"
++        if pieces["branch"] != "master":
++            rendered += ".dev0"
++        rendered += "+untagged.%d.g%s" % (pieces["distance"], pieces["short"])
++        if pieces["dirty"]:
++            rendered += ".dirty"
++    return rendered
++
++
++def pep440_split_post(ver: str) -> Tuple[str, Optional[int]]:
++    """Split pep440 version string at the post-release segment.
++
++    Returns the release segments before the post-release and the
++    post-release version number (or -1 if no post-release segment is present).
++    """
++    vc = str.split(ver, ".post")
++    return vc[0], int(vc[1] or 0) if len(vc) == 2 else None
++
++
++def render_pep440_pre(pieces: Dict[str, Any]) -> str:
++    """TAG[.postN.devDISTANCE] -- No -dirty.
++
++    Exceptions:
++    1: no tags. 0.post0.devDISTANCE
++    """
++    if pieces["closest-tag"]:
+         if pieces["distance"]:
+-            rendered += ".post.dev%d" % pieces["distance"]
++            # update the post release segment
++            tag_version, post_version = pep440_split_post(pieces["closest-tag"])
++            rendered = tag_version
++            if post_version is not None:
++                rendered += ".post%d.dev%d" % (post_version + 1, pieces["distance"])
++            else:
++                rendered += ".post0.dev%d" % (pieces["distance"])
++        else:
++            # no commits, use the tag as the version
++            rendered = pieces["closest-tag"]
+     else:
+         # exception #1
+-        rendered = "0.post.dev%d" % pieces["distance"]
++        rendered = "0.post0.dev%d" % pieces["distance"]
+     return rendered
+ 
+ 
+-def render_pep440_post(pieces):
++def render_pep440_post(pieces: Dict[str, Any]) -> str:
+     """TAG[.postDISTANCE[.dev0]+gHEX] .
+ 
+     The ".dev0" means dirty. Note that .dev0 sorts backwards
+@@ -1301,12 +1687,41 @@ def render_pep440_post(pieces):
+     return rendered
+ 
+ 
+-def render_pep440_old(pieces):
++def render_pep440_post_branch(pieces: Dict[str, Any]) -> str:
++    """TAG[.postDISTANCE[.dev0]+gHEX[.dirty]] .
++
++    The ".dev0" means not master branch.
++
++    Exceptions:
++    1: no tags. 0.postDISTANCE[.dev0]+gHEX[.dirty]
++    """
++    if pieces["closest-tag"]:
++        rendered = pieces["closest-tag"]
++        if pieces["distance"] or pieces["dirty"]:
++            rendered += ".post%d" % pieces["distance"]
++            if pieces["branch"] != "master":
++                rendered += ".dev0"
++            rendered += plus_or_dot(pieces)
++            rendered += "g%s" % pieces["short"]
++            if pieces["dirty"]:
++                rendered += ".dirty"
++    else:
++        # exception #1
++        rendered = "0.post%d" % pieces["distance"]
++        if pieces["branch"] != "master":
++            rendered += ".dev0"
++        rendered += "+g%s" % pieces["short"]
++        if pieces["dirty"]:
++            rendered += ".dirty"
++    return rendered
++
++
++def render_pep440_old(pieces: Dict[str, Any]) -> str:
+     """TAG[.postDISTANCE[.dev0]] .
+ 
+     The ".dev0" means dirty.
+ 
+-    Eexceptions:
++    Exceptions:
+     1: no tags. 0.postDISTANCE[.dev0]
+     """
+     if pieces["closest-tag"]:
+@@ -1323,7 +1738,7 @@ def render_pep440_old(pieces):
+     return rendered
+ 
+ 
+-def render_git_describe(pieces):
++def render_git_describe(pieces: Dict[str, Any]) -> str:
+     """TAG[-DISTANCE-gHEX][-dirty].
+ 
+     Like 'git describe --tags --dirty --always'.
+@@ -1343,7 +1758,7 @@ def render_git_describe(pieces):
+     return rendered
+ 
+ 
+-def render_git_describe_long(pieces):
++def render_git_describe_long(pieces: Dict[str, Any]) -> str:
+     """TAG-DISTANCE-gHEX[-dirty].
+ 
+     Like 'git describe --tags --dirty --always -long'.
+@@ -1363,24 +1778,30 @@ def render_git_describe_long(pieces):
+     return rendered
+ 
+ 
+-def render(pieces, style):
++def render(pieces: Dict[str, Any], style: str) -> Dict[str, Any]:
+     """Render the given version pieces into the requested style."""
+     if pieces["error"]:
+-        return {"version": "unknown",
+-                "full-revisionid": pieces.get("long"),
+-                "dirty": None,
+-                "error": pieces["error"],
+-                "date": None}
++        return {
++            "version": "unknown",
++            "full-revisionid": pieces.get("long"),
++            "dirty": None,
++            "error": pieces["error"],
++            "date": None,
++        }
+ 
+     if not style or style == "default":
+         style = "pep440"  # the default
+ 
+     if style == "pep440":
+         rendered = render_pep440(pieces)
++    elif style == "pep440-branch":
++        rendered = render_pep440_branch(pieces)
+     elif style == "pep440-pre":
+         rendered = render_pep440_pre(pieces)
+     elif style == "pep440-post":
+         rendered = render_pep440_post(pieces)
++    elif style == "pep440-post-branch":
++        rendered = render_pep440_post_branch(pieces)
+     elif style == "pep440-old":
+         rendered = render_pep440_old(pieces)
+     elif style == "git-describe":
+@@ -1390,16 +1811,20 @@ def render(pieces, style):
+     else:
+         raise ValueError("unknown style '%s'" % style)
+ 
+-    return {"version": rendered, "full-revisionid": pieces["long"],
+-            "dirty": pieces["dirty"], "error": None,
+-            "date": pieces.get("date")}
++    return {
++        "version": rendered,
++        "full-revisionid": pieces["long"],
++        "dirty": pieces["dirty"],
++        "error": None,
++        "date": pieces.get("date"),
++    }
+ 
+ 
+ class VersioneerBadRootError(Exception):
+     """The project root directory is unknown or missing key files."""
+ 
+ 
+-def get_versions(verbose=False):
++def get_versions(verbose: bool = False) -> Dict[str, Any]:
+     """Get the project version from whatever source is available.
+ 
+     Returns dict with two keys: 'version' and 'full'.
+@@ -1414,9 +1839,10 @@ def get_versions(verbose=False):
+     assert cfg.VCS is not None, "please set [versioneer]VCS= in setup.cfg"
+     handlers = HANDLERS.get(cfg.VCS)
+     assert handlers, "unrecognized VCS '%s'" % cfg.VCS
+-    verbose = verbose or cfg.verbose
+-    assert cfg.versionfile_source is not None, \
+-        "please set versioneer.versionfile_source"
++    verbose = verbose or bool(cfg.verbose)  # `bool()` used to avoid `None`
++    assert (
++        cfg.versionfile_source is not None
++    ), "please set versioneer.versionfile_source"
+     assert cfg.tag_prefix is not None, "please set versioneer.tag_prefix"
+ 
+     versionfile_abs = os.path.join(root, cfg.versionfile_source)
+@@ -1470,18 +1896,26 @@ def get_versions(verbose=False):
+     if verbose:
+         print("unable to compute version")
+ 
+-    return {"version": "0+unknown", "full-revisionid": None,
+-            "dirty": None, "error": "unable to compute version",
+-            "date": None}
++    return {
++        "version": "0+unknown",
++        "full-revisionid": None,
++        "dirty": None,
++        "error": "unable to compute version",
++        "date": None,
++    }
+ 
+ 
+-def get_version():
++def get_version() -> str:
+     """Get the short version string for this project."""
+     return get_versions()["version"]
+ 
+ 
+-def get_cmdclass():
+-    """Get the custom setuptools/distutils subclasses used by Versioneer."""
++def get_cmdclass(cmdclass: Optional[Dict[str, Any]] = None):
++    """Get the custom setuptools subclasses used by Versioneer.
++
++    If the package uses a different cmdclass (e.g. one from numpy), it
++    should be provide as an argument.
++    """
+     if "versioneer" in sys.modules:
+         del sys.modules["versioneer"]
+         # this fixes the "python setup.py develop" case (also 'install' and
+@@ -1495,25 +1929,25 @@ def get_cmdclass():
+         # parent is protected against the child's "import versioneer". By
+         # removing ourselves from sys.modules here, before the child build
+         # happens, we protect the child from the parent's versioneer too.
+-        # Also see https://github.com/warner/python-versioneer/issues/52
++        # Also see https://github.com/python-versioneer/python-versioneer/issues/52
+ 
+-    cmds = {}
++    cmds = {} if cmdclass is None else cmdclass.copy()
+ 
+-    # we add "version" to both distutils and setuptools
+-    from distutils.core import Command
++    # we add "version" to setuptools
++    from setuptools import Command
+ 
+     class cmd_version(Command):
+         description = "report generated version string"
+-        user_options = []
+-        boolean_options = []
++        user_options: List[Tuple[str, str, str]] = []
++        boolean_options: List[str] = []
+ 
+-        def initialize_options(self):
++        def initialize_options(self) -> None:
+             pass
+ 
+-        def finalize_options(self):
++        def finalize_options(self) -> None:
+             pass
+ 
+-        def run(self):
++        def run(self) -> None:
+             vers = get_versions(verbose=True)
+             print("Version: %s" % vers["version"])
+             print(" full-revisionid: %s" % vers.get("full-revisionid"))
+@@ -1521,9 +1955,10 @@ def get_cmdclass():
+             print(" date: %s" % vers.get("date"))
+             if vers["error"]:
+                 print(" error: %s" % vers["error"])
++
+     cmds["version"] = cmd_version
+ 
+-    # we override "build_py" in both distutils and setuptools
++    # we override "build_py" in setuptools
+     #
+     # most invocation pathways end up running build_py:
+     #  distutils/build -> build_py
+@@ -1538,29 +1973,71 @@ def get_cmdclass():
+     #   then does setup.py bdist_wheel, or sometimes setup.py install
+     #  setup.py egg_info -> ?
+ 
++    # pip install -e . and setuptool/editable_wheel will invoke build_py
++    # but the build_py command is not expected to copy any files.
++
+     # we override different "build_py" commands for both environments
+-    if "setuptools" in sys.modules:
+-        from setuptools.command.build_py import build_py as _build_py
++    if "build_py" in cmds:
++        _build_py: Any = cmds["build_py"]
+     else:
+-        from distutils.command.build_py import build_py as _build_py
++        from setuptools.command.build_py import build_py as _build_py
+ 
+     class cmd_build_py(_build_py):
+-        def run(self):
++        def run(self) -> None:
+             root = get_root()
+             cfg = get_config_from_root(root)
+             versions = get_versions()
+             _build_py.run(self)
++            if getattr(self, "editable_mode", False):
++                # During editable installs `.py` and data files are
++                # not copied to build_lib
++                return
+             # now locate _version.py in the new build/ directory and replace
+             # it with an updated value
+             if cfg.versionfile_build:
+-                target_versionfile = os.path.join(self.build_lib,
+-                                                  cfg.versionfile_build)
++                target_versionfile = os.path.join(self.build_lib, cfg.versionfile_build)
+                 print("UPDATING %s" % target_versionfile)
+                 write_to_version_file(target_versionfile, versions)
++
+     cmds["build_py"] = cmd_build_py
+ 
++    if "build_ext" in cmds:
++        _build_ext: Any = cmds["build_ext"]
++    else:
++        from setuptools.command.build_ext import build_ext as _build_ext
++
++    class cmd_build_ext(_build_ext):
++        def run(self) -> None:
++            root = get_root()
++            cfg = get_config_from_root(root)
++            versions = get_versions()
++            _build_ext.run(self)
++            if self.inplace:
++                # build_ext --inplace will only build extensions in
++                # build/lib<..> dir with no _version.py to write to.
++                # As in place builds will already have a _version.py
++                # in the module dir, we do not need to write one.
++                return
++            # now locate _version.py in the new build/ directory and replace
++            # it with an updated value
++            if not cfg.versionfile_build:
++                return
++            target_versionfile = os.path.join(self.build_lib, cfg.versionfile_build)
++            if not os.path.exists(target_versionfile):
++                print(
++                    f"Warning: {target_versionfile} does not exist, skipping "
++                    "version update. This can happen if you are running build_ext "
++                    "without first running build_py."
++                )
++                return
++            print("UPDATING %s" % target_versionfile)
++            write_to_version_file(target_versionfile, versions)
++
++    cmds["build_ext"] = cmd_build_ext
++
+     if "cx_Freeze" in sys.modules:  # cx_freeze enabled?
+-        from cx_Freeze.dist import build_exe as _build_exe
++        from cx_Freeze.dist import build_exe as _build_exe  # type: ignore
++
+         # nczeczulin reports that py2exe won't like the pep440-style string
+         # as FILEVERSION, but it can be used for PRODUCTVERSION, e.g.
+         # setup(console=[{
+@@ -1569,7 +2046,7 @@ def get_cmdclass():
+         #   ...
+ 
+         class cmd_build_exe(_build_exe):
+-            def run(self):
++            def run(self) -> None:
+                 root = get_root()
+                 cfg = get_config_from_root(root)
+                 versions = get_versions()
+@@ -1581,24 +2058,28 @@ def get_cmdclass():
+                 os.unlink(target_versionfile)
+                 with open(cfg.versionfile_source, "w") as f:
+                     LONG = LONG_VERSION_PY[cfg.VCS]
+-                    f.write(LONG %
+-                            {"DOLLAR": "$",
+-                             "STYLE": cfg.style,
+-                             "TAG_PREFIX": cfg.tag_prefix,
+-                             "PARENTDIR_PREFIX": cfg.parentdir_prefix,
+-                             "VERSIONFILE_SOURCE": cfg.versionfile_source,
+-                             })
++                    f.write(
++                        LONG
++                        % {
++                            "DOLLAR": "$",
++                            "STYLE": cfg.style,
++                            "TAG_PREFIX": cfg.tag_prefix,
++                            "PARENTDIR_PREFIX": cfg.parentdir_prefix,
++                            "VERSIONFILE_SOURCE": cfg.versionfile_source,
++                        }
++                    )
++
+         cmds["build_exe"] = cmd_build_exe
+         del cmds["build_py"]
+ 
+-    if 'py2exe' in sys.modules:  # py2exe enabled?
++    if "py2exe" in sys.modules:  # py2exe enabled?
+         try:
+-            from py2exe.distutils_buildexe import py2exe as _py2exe  # py3
++            from py2exe.setuptools_buildexe import py2exe as _py2exe  # type: ignore
+         except ImportError:
+-            from py2exe.build_exe import py2exe as _py2exe  # py2
++            from py2exe.distutils_buildexe import py2exe as _py2exe  # type: ignore
+ 
+         class cmd_py2exe(_py2exe):
+-            def run(self):
++            def run(self) -> None:
+                 root = get_root()
+                 cfg = get_config_from_root(root)
+                 versions = get_versions()
+@@ -1610,23 +2091,67 @@ def get_cmdclass():
+                 os.unlink(target_versionfile)
+                 with open(cfg.versionfile_source, "w") as f:
+                     LONG = LONG_VERSION_PY[cfg.VCS]
+-                    f.write(LONG %
+-                            {"DOLLAR": "$",
+-                             "STYLE": cfg.style,
+-                             "TAG_PREFIX": cfg.tag_prefix,
+-                             "PARENTDIR_PREFIX": cfg.parentdir_prefix,
+-                             "VERSIONFILE_SOURCE": cfg.versionfile_source,
+-                             })
++                    f.write(
++                        LONG
++                        % {
++                            "DOLLAR": "$",
++                            "STYLE": cfg.style,
++                            "TAG_PREFIX": cfg.tag_prefix,
++                            "PARENTDIR_PREFIX": cfg.parentdir_prefix,
++                            "VERSIONFILE_SOURCE": cfg.versionfile_source,
++                        }
++                    )
++
+         cmds["py2exe"] = cmd_py2exe
+ 
++    # sdist farms its file list building out to egg_info
++    if "egg_info" in cmds:
++        _egg_info: Any = cmds["egg_info"]
++    else:
++        from setuptools.command.egg_info import egg_info as _egg_info
++
++    class cmd_egg_info(_egg_info):
++        def find_sources(self) -> None:
++            # egg_info.find_sources builds the manifest list and writes it
++            # in one shot
++            super().find_sources()
++
++            # Modify the filelist and normalize it
++            root = get_root()
++            cfg = get_config_from_root(root)
++            self.filelist.append("versioneer.py")
++            if cfg.versionfile_source:
++                # There are rare cases where versionfile_source might not be
++                # included by default, so we must be explicit
++                self.filelist.append(cfg.versionfile_source)
++            self.filelist.sort()
++            self.filelist.remove_duplicates()
++
++            # The write method is hidden in the manifest_maker instance that
++            # generated the filelist and was thrown away
++            # We will instead replicate their final normalization (to unicode,
++            # and POSIX-style paths)
++            from setuptools import unicode_utils
++
++            normalized = [
++                unicode_utils.filesys_decode(f).replace(os.sep, "/")
++                for f in self.filelist.files
++            ]
++
++            manifest_filename = os.path.join(self.egg_info, "SOURCES.txt")
++            with open(manifest_filename, "w") as fobj:
++                fobj.write("\n".join(normalized))
++
++    cmds["egg_info"] = cmd_egg_info
++
+     # we override different "sdist" commands for both environments
+-    if "setuptools" in sys.modules:
+-        from setuptools.command.sdist import sdist as _sdist
++    if "sdist" in cmds:
++        _sdist: Any = cmds["sdist"]
+     else:
+-        from distutils.command.sdist import sdist as _sdist
++        from setuptools.command.sdist import sdist as _sdist
+ 
+     class cmd_sdist(_sdist):
+-        def run(self):
++        def run(self) -> None:
+             versions = get_versions()
+             self._versioneer_generated_versions = versions
+             # unless we update this, the command will keep using the old
+@@ -1634,7 +2159,7 @@ def get_cmdclass():
+             self.distribution.metadata.version = versions["version"]
+             return _sdist.run(self)
+ 
+-        def make_release_tree(self, base_dir, files):
++        def make_release_tree(self, base_dir: str, files: List[str]) -> None:
+             root = get_root()
+             cfg = get_config_from_root(root)
+             _sdist.make_release_tree(self, base_dir, files)
+@@ -1643,8 +2168,10 @@ def get_cmdclass():
+             # updated value
+             target_versionfile = os.path.join(base_dir, cfg.versionfile_source)
+             print("UPDATING %s" % target_versionfile)
+-            write_to_version_file(target_versionfile,
+-                                  self._versioneer_generated_versions)
++            write_to_version_file(
++                target_versionfile, self._versioneer_generated_versions
++            )
++
+     cmds["sdist"] = cmd_sdist
+ 
+     return cmds
+@@ -1687,23 +2214,26 @@ SAMPLE_CONFIG = """
+ 
+ """
+ 
+-INIT_PY_SNIPPET = """
++OLD_SNIPPET = """
+ from ._version import get_versions
+ __version__ = get_versions()['version']
+ del get_versions
+ """
+ 
++INIT_PY_SNIPPET = """
++from . import {0}
++__version__ = {0}.get_versions()['version']
++"""
++
+ 
+-def do_setup():
+-    """Main VCS-independent setup function for installing Versioneer."""
++def do_setup() -> int:
++    """Do main VCS-independent setup function for installing Versioneer."""
+     root = get_root()
+     try:
+         cfg = get_config_from_root(root)
+-    except (EnvironmentError, configparser.NoSectionError,
+-            configparser.NoOptionError) as e:
+-        if isinstance(e, (EnvironmentError, configparser.NoSectionError)):
+-            print("Adding sample versioneer config to setup.cfg",
+-                  file=sys.stderr)
++    except (OSError, configparser.NoSectionError, configparser.NoOptionError) as e:
++        if isinstance(e, (OSError, configparser.NoSectionError)):
++            print("Adding sample versioneer config to setup.cfg", file=sys.stderr)
+             with open(os.path.join(root, "setup.cfg"), "a") as f:
+                 f.write(SAMPLE_CONFIG)
+         print(CONFIG_ERROR, file=sys.stderr)
+@@ -1712,71 +2242,49 @@ def do_setup():
+     print(" creating %s" % cfg.versionfile_source)
+     with open(cfg.versionfile_source, "w") as f:
+         LONG = LONG_VERSION_PY[cfg.VCS]
+-        f.write(LONG % {"DOLLAR": "$",
+-                        "STYLE": cfg.style,
+-                        "TAG_PREFIX": cfg.tag_prefix,
+-                        "PARENTDIR_PREFIX": cfg.parentdir_prefix,
+-                        "VERSIONFILE_SOURCE": cfg.versionfile_source,
+-                        })
+-
+-    ipy = os.path.join(os.path.dirname(cfg.versionfile_source),
+-                       "__init__.py")
++        f.write(
++            LONG
++            % {
++                "DOLLAR": "$",
++                "STYLE": cfg.style,
++                "TAG_PREFIX": cfg.tag_prefix,
++                "PARENTDIR_PREFIX": cfg.parentdir_prefix,
++                "VERSIONFILE_SOURCE": cfg.versionfile_source,
++            }
++        )
++
++    ipy = os.path.join(os.path.dirname(cfg.versionfile_source), "__init__.py")
++    maybe_ipy: Optional[str] = ipy
+     if os.path.exists(ipy):
+         try:
+             with open(ipy, "r") as f:
+                 old = f.read()
+-        except EnvironmentError:
++        except OSError:
+             old = ""
+-        if INIT_PY_SNIPPET not in old:
++        module = os.path.splitext(os.path.basename(cfg.versionfile_source))[0]
++        snippet = INIT_PY_SNIPPET.format(module)
++        if OLD_SNIPPET in old:
++            print(" replacing boilerplate in %s" % ipy)
++            with open(ipy, "w") as f:
++                f.write(old.replace(OLD_SNIPPET, snippet))
++        elif snippet not in old:
+             print(" appending to %s" % ipy)
+             with open(ipy, "a") as f:
+-                f.write(INIT_PY_SNIPPET)
++                f.write(snippet)
+         else:
+             print(" %s unmodified" % ipy)
+     else:
+         print(" %s doesn't exist, ok" % ipy)
+-        ipy = None
+-
+-    # Make sure both the top-level "versioneer.py" and versionfile_source
+-    # (PKG/_version.py, used by runtime code) are in MANIFEST.in, so
+-    # they'll be copied into source distributions. Pip won't be able to
+-    # install the package without this.
+-    manifest_in = os.path.join(root, "MANIFEST.in")
+-    simple_includes = set()
+-    try:
+-        with open(manifest_in, "r") as f:
+-            for line in f:
+-                if line.startswith("include "):
+-                    for include in line.split()[1:]:
+-                        simple_includes.add(include)
+-    except EnvironmentError:
+-        pass
+-    # That doesn't cover everything MANIFEST.in can do
+-    # (http://docs.python.org/2/distutils/sourcedist.html#commands), so
+-    # it might give some false negatives. Appending redundant 'include'
+-    # lines is safe, though.
+-    if "versioneer.py" not in simple_includes:
+-        print(" appending 'versioneer.py' to MANIFEST.in")
+-        with open(manifest_in, "a") as f:
+-            f.write("include versioneer.py\n")
+-    else:
+-        print(" 'versioneer.py' already in MANIFEST.in")
+-    if cfg.versionfile_source not in simple_includes:
+-        print(" appending versionfile_source ('%s') to MANIFEST.in" %
+-              cfg.versionfile_source)
+-        with open(manifest_in, "a") as f:
+-            f.write("include %s\n" % cfg.versionfile_source)
+-    else:
+-        print(" versionfile_source already in MANIFEST.in")
++        maybe_ipy = None
+ 
+     # Make VCS-specific changes. For git, this means creating/changing
+     # .gitattributes to mark _version.py for export-subst keyword
+     # substitution.
+-    do_vcs_install(manifest_in, cfg.versionfile_source, ipy)
++    do_vcs_install(cfg.versionfile_source, maybe_ipy)
+     return 0
+ 
+ 
+-def scan_setup_py():
++def scan_setup_py() -> int:
+     """Validate the contents of setup.py against Versioneer's expectations."""
+     found = set()
+     setters = False
+@@ -1813,10 +2321,14 @@ def scan_setup_py():
+     return errors
+ 
+ 
++def setup_command() -> NoReturn:
++    """Set up Versioneer and exit with appropriate error code."""
++    errors = do_setup()
++    errors += scan_setup_py()
++    sys.exit(1 if errors else 0)
++
++
+ if __name__ == "__main__":
+     cmd = sys.argv[1]
+     if cmd == "setup":
+-        errors = do_setup()
+-        errors += scan_setup_py()
+-        if errors:
+-            sys.exit(1)
++        setup_command()
+-- 
+2.41.0
+
-- 
2.41.0


_______________________________________________
buildroot mailing list
buildroot@buildroot.org
https://lists.buildroot.org/mailman/listinfo/buildroot


^ permalink raw reply related	[flat|nested] 54+ messages in thread

* [Buildroot] [PATCH 22/30] package/python-magic-wormhole-mailbox-server: update versioneer to 0.29
  2023-10-26  9:26 [Buildroot] [PATCH 00/30] package/python3: bump version to 3.12.0 Adam Duskett
                   ` (20 preceding siblings ...)
  2023-10-26  9:26 ` [Buildroot] [PATCH 21/30] package/python-magic-wormhole: " Adam Duskett
@ 2023-10-26  9:26 ` Adam Duskett
  2023-11-04 21:31   ` Arnout Vandecappelle via buildroot
  2023-10-26  9:26 ` [Buildroot] [PATCH 23/30] package/python-magic-wormhole-transit-relay: " Adam Duskett
                   ` (8 subsequent siblings)
  30 siblings, 1 reply; 54+ messages in thread
From: Adam Duskett @ 2023-10-26  9:26 UTC (permalink / raw)
  To: buildroot
  Cc: Adam Duskett, Andrey Smirnov, Julien Olivain, Asaf Kahlon,
	James Hilliard, Thomas Petazzoni, Mauro Condarelli

Versioneer < 0.21 is incompatible with Python 3.12.0. Use the latest version
which is 0.29 as of this commit.

Signed-off-by: Adam Duskett <adam.duskett@amarulasolutions.com>
---
 .../0002-Update-versioneer-to-0.29.patch      | 2194 +++++++++++++++++
 1 file changed, 2194 insertions(+)
 create mode 100644 package/python-magic-wormhole-mailbox-server/0002-Update-versioneer-to-0.29.patch

diff --git a/package/python-magic-wormhole-mailbox-server/0002-Update-versioneer-to-0.29.patch b/package/python-magic-wormhole-mailbox-server/0002-Update-versioneer-to-0.29.patch
new file mode 100644
index 0000000000..3152f1aae7
--- /dev/null
+++ b/package/python-magic-wormhole-mailbox-server/0002-Update-versioneer-to-0.29.patch
@@ -0,0 +1,2194 @@
+From 76b422b7f53dbc41195a184d966230106f6ddc7d Mon Sep 17 00:00:00 2001
+From: Adam Duskett <adam.duskett@amarulasolutions.com>
+Date: Tue, 24 Oct 2023 09:50:41 +0200
+Subject: [PATCH] Update versioneer to 0.29
+
+Fixes builds against Python 3.12.0
+
+Upstream: https://github.com/magic-wormhole/magic-wormhole-mailbox-server/pull/40
+
+Signed-off-by: Adam Duskett <adam.duskett@amarulasolutions.com>
+---
+ versioneer.py | 1350 ++++++++++++++++++++++++++++++++++---------------
+ 1 file changed, 931 insertions(+), 419 deletions(-)
+
+diff --git a/versioneer.py b/versioneer.py
+index 64fea1c..de97d90 100644
+--- a/versioneer.py
++++ b/versioneer.py
+@@ -1,5 +1,4 @@
+-
+-# Version: 0.18
++# Version: 0.29
+ 
+ """The Versioneer - like a rocketeer, but for versions.
+ 
+@@ -7,18 +6,14 @@ The Versioneer
+ ==============
+ 
+ * like a rocketeer, but for versions!
+-* https://github.com/warner/python-versioneer
++* https://github.com/python-versioneer/python-versioneer
+ * Brian Warner
+-* License: Public Domain
+-* Compatible With: python2.6, 2.7, 3.2, 3.3, 3.4, 3.5, 3.6, and pypy
+-* [![Latest Version]
+-(https://pypip.in/version/versioneer/badge.svg?style=flat)
+-](https://pypi.python.org/pypi/versioneer/)
+-* [![Build Status]
+-(https://travis-ci.org/warner/python-versioneer.png?branch=master)
+-](https://travis-ci.org/warner/python-versioneer)
+-
+-This is a tool for managing a recorded version number in distutils-based
++* License: Public Domain (Unlicense)
++* Compatible with: Python 3.7, 3.8, 3.9, 3.10, 3.11 and pypy3
++* [![Latest Version][pypi-image]][pypi-url]
++* [![Build Status][travis-image]][travis-url]
++
++This is a tool for managing a recorded version number in setuptools-based
+ python projects. The goal is to remove the tedious and error-prone "update
+ the embedded version string" step from your release process. Making a new
+ release should be as easy as recording a new tag in your version-control
+@@ -27,9 +22,38 @@ system, and maybe making new tarballs.
+ 
+ ## Quick Install
+ 
+-* `pip install versioneer` to somewhere to your $PATH
+-* add a `[versioneer]` section to your setup.cfg (see below)
+-* run `versioneer install` in your source tree, commit the results
++Versioneer provides two installation modes. The "classic" vendored mode installs
++a copy of versioneer into your repository. The experimental build-time dependency mode
++is intended to allow you to skip this step and simplify the process of upgrading.
++
++### Vendored mode
++
++* `pip install versioneer` to somewhere in your $PATH
++   * A [conda-forge recipe](https://github.com/conda-forge/versioneer-feedstock) is
++     available, so you can also use `conda install -c conda-forge versioneer`
++* add a `[tool.versioneer]` section to your `pyproject.toml` or a
++  `[versioneer]` section to your `setup.cfg` (see [Install](INSTALL.md))
++   * Note that you will need to add `tomli; python_version < "3.11"` to your
++     build-time dependencies if you use `pyproject.toml`
++* run `versioneer install --vendor` in your source tree, commit the results
++* verify version information with `python setup.py version`
++
++### Build-time dependency mode
++
++* `pip install versioneer` to somewhere in your $PATH
++   * A [conda-forge recipe](https://github.com/conda-forge/versioneer-feedstock) is
++     available, so you can also use `conda install -c conda-forge versioneer`
++* add a `[tool.versioneer]` section to your `pyproject.toml` or a
++  `[versioneer]` section to your `setup.cfg` (see [Install](INSTALL.md))
++* add `versioneer` (with `[toml]` extra, if configuring in `pyproject.toml`)
++  to the `requires` key of the `build-system` table in `pyproject.toml`:
++  ```toml
++  [build-system]
++  requires = ["setuptools", "versioneer[toml]"]
++  build-backend = "setuptools.build_meta"
++  ```
++* run `versioneer install --no-vendor` in your source tree, commit the results
++* verify version information with `python setup.py version`
+ 
+ ## Version Identifiers
+ 
+@@ -61,7 +85,7 @@ version 1.3). Many VCS systems can report a description that captures this,
+ for example `git describe --tags --dirty --always` reports things like
+ "0.7-1-g574ab98-dirty" to indicate that the checkout is one revision past the
+ 0.7 tag, has a unique revision id of "574ab98", and is "dirty" (it has
+-uncommitted changes.
++uncommitted changes).
+ 
+ The version identifier is used for multiple purposes:
+ 
+@@ -166,7 +190,7 @@ which may help identify what went wrong).
+ 
+ Some situations are known to cause problems for Versioneer. This details the
+ most significant ones. More can be found on Github
+-[issues page](https://github.com/warner/python-versioneer/issues).
++[issues page](https://github.com/python-versioneer/python-versioneer/issues).
+ 
+ ### Subprojects
+ 
+@@ -180,7 +204,7 @@ two common reasons why `setup.py` might not be in the root:
+   `setup.cfg`, and `tox.ini`. Projects like these produce multiple PyPI
+   distributions (and upload multiple independently-installable tarballs).
+ * Source trees whose main purpose is to contain a C library, but which also
+-  provide bindings to Python (and perhaps other langauges) in subdirectories.
++  provide bindings to Python (and perhaps other languages) in subdirectories.
+ 
+ Versioneer will look for `.git` in parent directories, and most operations
+ should get the right version string. However `pip` and `setuptools` have bugs
+@@ -194,9 +218,9 @@ work too.
+ Pip-8.1.1 is known to have this problem, but hopefully it will get fixed in
+ some later version.
+ 
+-[Bug #38](https://github.com/warner/python-versioneer/issues/38) is tracking
++[Bug #38](https://github.com/python-versioneer/python-versioneer/issues/38) is tracking
+ this issue. The discussion in
+-[PR #61](https://github.com/warner/python-versioneer/pull/61) describes the
++[PR #61](https://github.com/python-versioneer/python-versioneer/pull/61) describes the
+ issue from the Versioneer side in more detail.
+ [pip PR#3176](https://github.com/pypa/pip/pull/3176) and
+ [pip PR#3615](https://github.com/pypa/pip/pull/3615) contain work to improve
+@@ -224,31 +248,20 @@ regenerated while a different version is checked out. Many setup.py commands
+ cause egg_info to be rebuilt (including `sdist`, `wheel`, and installing into
+ a different virtualenv), so this can be surprising.
+ 
+-[Bug #83](https://github.com/warner/python-versioneer/issues/83) describes
++[Bug #83](https://github.com/python-versioneer/python-versioneer/issues/83) describes
+ this one, but upgrading to a newer version of setuptools should probably
+ resolve it.
+ 
+-### Unicode version strings
+-
+-While Versioneer works (and is continually tested) with both Python 2 and
+-Python 3, it is not entirely consistent with bytes-vs-unicode distinctions.
+-Newer releases probably generate unicode version strings on py2. It's not
+-clear that this is wrong, but it may be surprising for applications when then
+-write these strings to a network connection or include them in bytes-oriented
+-APIs like cryptographic checksums.
+-
+-[Bug #71](https://github.com/warner/python-versioneer/issues/71) investigates
+-this question.
+-
+ 
+ ## Updating Versioneer
+ 
+ To upgrade your project to a new release of Versioneer, do the following:
+ 
+ * install the new Versioneer (`pip install -U versioneer` or equivalent)
+-* edit `setup.cfg`, if necessary, to include any new configuration settings
+-  indicated by the release notes. See [UPGRADING](./UPGRADING.md) for details.
+-* re-run `versioneer install` in your source tree, to replace
++* edit `setup.cfg` and `pyproject.toml`, if necessary,
++  to include any new configuration settings indicated by the release notes.
++  See [UPGRADING](./UPGRADING.md) for details.
++* re-run `versioneer install --[no-]vendor` in your source tree, to replace
+   `SRC/_version.py`
+ * commit any changed files
+ 
+@@ -265,35 +278,70 @@ installation by editing setup.py . Alternatively, it might go the other
+ direction and include code from all supported VCS systems, reducing the
+ number of intermediate scripts.
+ 
++## Similar projects
++
++* [setuptools_scm](https://github.com/pypa/setuptools_scm/) - a non-vendored build-time
++  dependency
++* [minver](https://github.com/jbweston/miniver) - a lightweight reimplementation of
++  versioneer
++* [versioningit](https://github.com/jwodder/versioningit) - a PEP 518-based setuptools
++  plugin
+ 
+ ## License
+ 
+ To make Versioneer easier to embed, all its code is dedicated to the public
+ domain. The `_version.py` that it creates is also in the public domain.
+-Specifically, both are released under the Creative Commons "Public Domain
+-Dedication" license (CC0-1.0), as described in
+-https://creativecommons.org/publicdomain/zero/1.0/ .
++Specifically, both are released under the "Unlicense", as described in
++https://unlicense.org/.
++
++[pypi-image]: https://img.shields.io/pypi/v/versioneer.svg
++[pypi-url]: https://pypi.python.org/pypi/versioneer/
++[travis-image]:
++https://img.shields.io/travis/com/python-versioneer/python-versioneer.svg
++[travis-url]: https://travis-ci.com/github/python-versioneer/python-versioneer
+ 
+ """
++# pylint:disable=invalid-name,import-outside-toplevel,missing-function-docstring
++# pylint:disable=missing-class-docstring,too-many-branches,too-many-statements
++# pylint:disable=raise-missing-from,too-many-lines,too-many-locals,import-error
++# pylint:disable=too-few-public-methods,redefined-outer-name,consider-using-with
++# pylint:disable=attribute-defined-outside-init,too-many-arguments
+ 
+-from __future__ import print_function
+-try:
+-    import configparser
+-except ImportError:
+-    import ConfigParser as configparser
++import configparser
+ import errno
+ import json
+ import os
+ import re
+ import subprocess
+ import sys
++from pathlib import Path
++from typing import Any, Callable, cast, Dict, List, Optional, Tuple, Union
++from typing import NoReturn
++import functools
++
++have_tomllib = True
++if sys.version_info >= (3, 11):
++    import tomllib
++else:
++    try:
++        import tomli as tomllib
++    except ImportError:
++        have_tomllib = False
+ 
+ 
+ class VersioneerConfig:
+     """Container for Versioneer configuration parameters."""
+ 
++    VCS: str
++    style: str
++    tag_prefix: str
++    versionfile_source: str
++    versionfile_build: Optional[str]
++    parentdir_prefix: Optional[str]
++    verbose: Optional[bool]
++
+ 
+-def get_root():
++def get_root() -> str:
+     """Get the project root directory.
+ 
+     We require that all commands are run from the project root, i.e. the
+@@ -301,18 +349,30 @@ def get_root():
+     """
+     root = os.path.realpath(os.path.abspath(os.getcwd()))
+     setup_py = os.path.join(root, "setup.py")
++    pyproject_toml = os.path.join(root, "pyproject.toml")
+     versioneer_py = os.path.join(root, "versioneer.py")
+-    if not (os.path.exists(setup_py) or os.path.exists(versioneer_py)):
++    if not (
++        os.path.exists(setup_py)
++        or os.path.exists(pyproject_toml)
++        or os.path.exists(versioneer_py)
++    ):
+         # allow 'python path/to/setup.py COMMAND'
+         root = os.path.dirname(os.path.realpath(os.path.abspath(sys.argv[0])))
+         setup_py = os.path.join(root, "setup.py")
++        pyproject_toml = os.path.join(root, "pyproject.toml")
+         versioneer_py = os.path.join(root, "versioneer.py")
+-    if not (os.path.exists(setup_py) or os.path.exists(versioneer_py)):
+-        err = ("Versioneer was unable to run the project root directory. "
+-               "Versioneer requires setup.py to be executed from "
+-               "its immediate directory (like 'python setup.py COMMAND'), "
+-               "or in a way that lets it use sys.argv[0] to find the root "
+-               "(like 'python path/to/setup.py COMMAND').")
++    if not (
++        os.path.exists(setup_py)
++        or os.path.exists(pyproject_toml)
++        or os.path.exists(versioneer_py)
++    ):
++        err = (
++            "Versioneer was unable to run the project root directory. "
++            "Versioneer requires setup.py to be executed from "
++            "its immediate directory (like 'python setup.py COMMAND'), "
++            "or in a way that lets it use sys.argv[0] to find the root "
++            "(like 'python path/to/setup.py COMMAND')."
++        )
+         raise VersioneerBadRootError(err)
+     try:
+         # Certain runtime workflows (setup.py install/develop in a setuptools
+@@ -321,43 +381,64 @@ def get_root():
+         # module-import table will cache the first one. So we can't use
+         # os.path.dirname(__file__), as that will find whichever
+         # versioneer.py was first imported, even in later projects.
+-        me = os.path.realpath(os.path.abspath(__file__))
+-        me_dir = os.path.normcase(os.path.splitext(me)[0])
++        my_path = os.path.realpath(os.path.abspath(__file__))
++        me_dir = os.path.normcase(os.path.splitext(my_path)[0])
+         vsr_dir = os.path.normcase(os.path.splitext(versioneer_py)[0])
+-        if me_dir != vsr_dir:
+-            print("Warning: build in %s is using versioneer.py from %s"
+-                  % (os.path.dirname(me), versioneer_py))
++        if me_dir != vsr_dir and "VERSIONEER_PEP518" not in globals():
++            print(
++                "Warning: build in %s is using versioneer.py from %s"
++                % (os.path.dirname(my_path), versioneer_py)
++            )
+     except NameError:
+         pass
+     return root
+ 
+ 
+-def get_config_from_root(root):
++def get_config_from_root(root: str) -> VersioneerConfig:
+     """Read the project setup.cfg file to determine Versioneer config."""
+-    # This might raise EnvironmentError (if setup.cfg is missing), or
++    # This might raise OSError (if setup.cfg is missing), or
+     # configparser.NoSectionError (if it lacks a [versioneer] section), or
+     # configparser.NoOptionError (if it lacks "VCS="). See the docstring at
+     # the top of versioneer.py for instructions on writing your setup.cfg .
+-    setup_cfg = os.path.join(root, "setup.cfg")
+-    parser = configparser.SafeConfigParser()
+-    with open(setup_cfg, "r") as f:
+-        parser.readfp(f)
+-    VCS = parser.get("versioneer", "VCS")  # mandatory
+-
+-    def get(parser, name):
+-        if parser.has_option("versioneer", name):
+-            return parser.get("versioneer", name)
+-        return None
++    root_pth = Path(root)
++    pyproject_toml = root_pth / "pyproject.toml"
++    setup_cfg = root_pth / "setup.cfg"
++    section: Union[Dict[str, Any], configparser.SectionProxy, None] = None
++    if pyproject_toml.exists() and have_tomllib:
++        try:
++            with open(pyproject_toml, "rb") as fobj:
++                pp = tomllib.load(fobj)
++            section = pp["tool"]["versioneer"]
++        except (tomllib.TOMLDecodeError, KeyError) as e:
++            print(f"Failed to load config from {pyproject_toml}: {e}")
++            print("Try to load it from setup.cfg")
++    if not section:
++        parser = configparser.ConfigParser()
++        with open(setup_cfg) as cfg_file:
++            parser.read_file(cfg_file)
++        parser.get("versioneer", "VCS")  # raise error if missing
++
++        section = parser["versioneer"]
++
++    # `cast`` really shouldn't be used, but its simplest for the
++    # common VersioneerConfig users at the moment. We verify against
++    # `None` values elsewhere where it matters
++
+     cfg = VersioneerConfig()
+-    cfg.VCS = VCS
+-    cfg.style = get(parser, "style") or ""
+-    cfg.versionfile_source = get(parser, "versionfile_source")
+-    cfg.versionfile_build = get(parser, "versionfile_build")
+-    cfg.tag_prefix = get(parser, "tag_prefix")
+-    if cfg.tag_prefix in ("''", '""'):
++    cfg.VCS = section["VCS"]
++    cfg.style = section.get("style", "")
++    cfg.versionfile_source = cast(str, section.get("versionfile_source"))
++    cfg.versionfile_build = section.get("versionfile_build")
++    cfg.tag_prefix = cast(str, section.get("tag_prefix"))
++    if cfg.tag_prefix in ("''", '""', None):
+         cfg.tag_prefix = ""
+-    cfg.parentdir_prefix = get(parser, "parentdir_prefix")
+-    cfg.verbose = get(parser, "verbose")
++    cfg.parentdir_prefix = section.get("parentdir_prefix")
++    if isinstance(section, configparser.SectionProxy):
++        # Make sure configparser translates to bool
++        cfg.verbose = section.getboolean("verbose")
++    else:
++        cfg.verbose = section.get("verbose")
++
+     return cfg
+ 
+ 
+@@ -366,37 +447,54 @@ class NotThisMethod(Exception):
+ 
+ 
+ # these dictionaries contain VCS-specific tools
+-LONG_VERSION_PY = {}
+-HANDLERS = {}
++LONG_VERSION_PY: Dict[str, str] = {}
++HANDLERS: Dict[str, Dict[str, Callable]] = {}
+ 
+ 
+-def register_vcs_handler(vcs, method):  # decorator
+-    """Decorator to mark a method as the handler for a particular VCS."""
+-    def decorate(f):
++def register_vcs_handler(vcs: str, method: str) -> Callable:  # decorator
++    """Create decorator to mark a method as the handler of a VCS."""
++
++    def decorate(f: Callable) -> Callable:
+         """Store f in HANDLERS[vcs][method]."""
+-        if vcs not in HANDLERS:
+-            HANDLERS[vcs] = {}
+-        HANDLERS[vcs][method] = f
++        HANDLERS.setdefault(vcs, {})[method] = f
+         return f
++
+     return decorate
+ 
+ 
+-def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False,
+-                env=None):
++def run_command(
++    commands: List[str],
++    args: List[str],
++    cwd: Optional[str] = None,
++    verbose: bool = False,
++    hide_stderr: bool = False,
++    env: Optional[Dict[str, str]] = None,
++) -> Tuple[Optional[str], Optional[int]]:
+     """Call the given command(s)."""
+     assert isinstance(commands, list)
+-    p = None
+-    for c in commands:
++    process = None
++
++    popen_kwargs: Dict[str, Any] = {}
++    if sys.platform == "win32":
++        # This hides the console window if pythonw.exe is used
++        startupinfo = subprocess.STARTUPINFO()
++        startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
++        popen_kwargs["startupinfo"] = startupinfo
++
++    for command in commands:
+         try:
+-            dispcmd = str([c] + args)
++            dispcmd = str([command] + args)
+             # remember shell=False, so use git.cmd on windows, not just git
+-            p = subprocess.Popen([c] + args, cwd=cwd, env=env,
+-                                 stdout=subprocess.PIPE,
+-                                 stderr=(subprocess.PIPE if hide_stderr
+-                                         else None))
++            process = subprocess.Popen(
++                [command] + args,
++                cwd=cwd,
++                env=env,
++                stdout=subprocess.PIPE,
++                stderr=(subprocess.PIPE if hide_stderr else None),
++                **popen_kwargs,
++            )
+             break
+-        except EnvironmentError:
+-            e = sys.exc_info()[1]
++        except OSError as e:
+             if e.errno == errno.ENOENT:
+                 continue
+             if verbose:
+@@ -407,26 +505,27 @@ def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False,
+         if verbose:
+             print("unable to find command, tried %s" % (commands,))
+         return None, None
+-    stdout = p.communicate()[0].strip()
+-    if sys.version_info[0] >= 3:
+-        stdout = stdout.decode()
+-    if p.returncode != 0:
++    stdout = process.communicate()[0].strip().decode()
++    if process.returncode != 0:
+         if verbose:
+             print("unable to run %s (error)" % dispcmd)
+             print("stdout was %s" % stdout)
+-        return None, p.returncode
+-    return stdout, p.returncode
++        return None, process.returncode
++    return stdout, process.returncode
+ 
+ 
+-LONG_VERSION_PY['git'] = '''
++LONG_VERSION_PY[
++    "git"
++] = r'''
+ # This file helps to compute a version number in source trees obtained from
+ # git-archive tarball (such as those provided by githubs download-from-tag
+ # feature). Distribution tarballs (built by setup.py sdist) and build
+ # directories (produced by setup.py build) will contain a much shorter file
+ # that just contains the computed version number.
+ 
+-# This file is released into the public domain. Generated by
+-# versioneer-0.18 (https://github.com/warner/python-versioneer)
++# This file is released into the public domain.
++# Generated by versioneer-0.29
++# https://github.com/python-versioneer/python-versioneer
+ 
+ """Git implementation of _version.py."""
+ 
+@@ -435,9 +534,11 @@ import os
+ import re
+ import subprocess
+ import sys
++from typing import Any, Callable, Dict, List, Optional, Tuple
++import functools
+ 
+ 
+-def get_keywords():
++def get_keywords() -> Dict[str, str]:
+     """Get the keywords needed to look up the version information."""
+     # these strings will be replaced by git during git-archive.
+     # setup.py/versioneer.py will grep for the variable names, so they must
+@@ -453,8 +554,15 @@ def get_keywords():
+ class VersioneerConfig:
+     """Container for Versioneer configuration parameters."""
+ 
++    VCS: str
++    style: str
++    tag_prefix: str
++    parentdir_prefix: str
++    versionfile_source: str
++    verbose: bool
++
+ 
+-def get_config():
++def get_config() -> VersioneerConfig:
+     """Create, populate and return the VersioneerConfig() object."""
+     # these strings are filled in when 'setup.py versioneer' creates
+     # _version.py
+@@ -472,13 +580,13 @@ class NotThisMethod(Exception):
+     """Exception raised if a method is not valid for the current scenario."""
+ 
+ 
+-LONG_VERSION_PY = {}
+-HANDLERS = {}
++LONG_VERSION_PY: Dict[str, str] = {}
++HANDLERS: Dict[str, Dict[str, Callable]] = {}
+ 
+ 
+-def register_vcs_handler(vcs, method):  # decorator
+-    """Decorator to mark a method as the handler for a particular VCS."""
+-    def decorate(f):
++def register_vcs_handler(vcs: str, method: str) -> Callable:  # decorator
++    """Create decorator to mark a method as the handler of a VCS."""
++    def decorate(f: Callable) -> Callable:
+         """Store f in HANDLERS[vcs][method]."""
+         if vcs not in HANDLERS:
+             HANDLERS[vcs] = {}
+@@ -487,22 +595,35 @@ def register_vcs_handler(vcs, method):  # decorator
+     return decorate
+ 
+ 
+-def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False,
+-                env=None):
++def run_command(
++    commands: List[str],
++    args: List[str],
++    cwd: Optional[str] = None,
++    verbose: bool = False,
++    hide_stderr: bool = False,
++    env: Optional[Dict[str, str]] = None,
++) -> Tuple[Optional[str], Optional[int]]:
+     """Call the given command(s)."""
+     assert isinstance(commands, list)
+-    p = None
+-    for c in commands:
++    process = None
++
++    popen_kwargs: Dict[str, Any] = {}
++    if sys.platform == "win32":
++        # This hides the console window if pythonw.exe is used
++        startupinfo = subprocess.STARTUPINFO()
++        startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
++        popen_kwargs["startupinfo"] = startupinfo
++
++    for command in commands:
+         try:
+-            dispcmd = str([c] + args)
++            dispcmd = str([command] + args)
+             # remember shell=False, so use git.cmd on windows, not just git
+-            p = subprocess.Popen([c] + args, cwd=cwd, env=env,
+-                                 stdout=subprocess.PIPE,
+-                                 stderr=(subprocess.PIPE if hide_stderr
+-                                         else None))
++            process = subprocess.Popen([command] + args, cwd=cwd, env=env,
++                                       stdout=subprocess.PIPE,
++                                       stderr=(subprocess.PIPE if hide_stderr
++                                               else None), **popen_kwargs)
+             break
+-        except EnvironmentError:
+-            e = sys.exc_info()[1]
++        except OSError as e:
+             if e.errno == errno.ENOENT:
+                 continue
+             if verbose:
+@@ -513,18 +634,20 @@ def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False,
+         if verbose:
+             print("unable to find command, tried %%s" %% (commands,))
+         return None, None
+-    stdout = p.communicate()[0].strip()
+-    if sys.version_info[0] >= 3:
+-        stdout = stdout.decode()
+-    if p.returncode != 0:
++    stdout = process.communicate()[0].strip().decode()
++    if process.returncode != 0:
+         if verbose:
+             print("unable to run %%s (error)" %% dispcmd)
+             print("stdout was %%s" %% stdout)
+-        return None, p.returncode
+-    return stdout, p.returncode
++        return None, process.returncode
++    return stdout, process.returncode
+ 
+ 
+-def versions_from_parentdir(parentdir_prefix, root, verbose):
++def versions_from_parentdir(
++    parentdir_prefix: str,
++    root: str,
++    verbose: bool,
++) -> Dict[str, Any]:
+     """Try to determine the version from the parent directory name.
+ 
+     Source tarballs conventionally unpack into a directory that includes both
+@@ -533,15 +656,14 @@ def versions_from_parentdir(parentdir_prefix, root, verbose):
+     """
+     rootdirs = []
+ 
+-    for i in range(3):
++    for _ in range(3):
+         dirname = os.path.basename(root)
+         if dirname.startswith(parentdir_prefix):
+             return {"version": dirname[len(parentdir_prefix):],
+                     "full-revisionid": None,
+                     "dirty": False, "error": None, "date": None}
+-        else:
+-            rootdirs.append(root)
+-            root = os.path.dirname(root)  # up a level
++        rootdirs.append(root)
++        root = os.path.dirname(root)  # up a level
+ 
+     if verbose:
+         print("Tried directories %%s but none started with prefix %%s" %%
+@@ -550,41 +672,48 @@ def versions_from_parentdir(parentdir_prefix, root, verbose):
+ 
+ 
+ @register_vcs_handler("git", "get_keywords")
+-def git_get_keywords(versionfile_abs):
++def git_get_keywords(versionfile_abs: str) -> Dict[str, str]:
+     """Extract version information from the given file."""
+     # the code embedded in _version.py can just fetch the value of these
+     # keywords. When used from setup.py, we don't want to import _version.py,
+     # so we do it with a regexp instead. This function is not used from
+     # _version.py.
+-    keywords = {}
++    keywords: Dict[str, str] = {}
+     try:
+-        f = open(versionfile_abs, "r")
+-        for line in f.readlines():
+-            if line.strip().startswith("git_refnames ="):
+-                mo = re.search(r'=\s*"(.*)"', line)
+-                if mo:
+-                    keywords["refnames"] = mo.group(1)
+-            if line.strip().startswith("git_full ="):
+-                mo = re.search(r'=\s*"(.*)"', line)
+-                if mo:
+-                    keywords["full"] = mo.group(1)
+-            if line.strip().startswith("git_date ="):
+-                mo = re.search(r'=\s*"(.*)"', line)
+-                if mo:
+-                    keywords["date"] = mo.group(1)
+-        f.close()
+-    except EnvironmentError:
++        with open(versionfile_abs, "r") as fobj:
++            for line in fobj:
++                if line.strip().startswith("git_refnames ="):
++                    mo = re.search(r'=\s*"(.*)"', line)
++                    if mo:
++                        keywords["refnames"] = mo.group(1)
++                if line.strip().startswith("git_full ="):
++                    mo = re.search(r'=\s*"(.*)"', line)
++                    if mo:
++                        keywords["full"] = mo.group(1)
++                if line.strip().startswith("git_date ="):
++                    mo = re.search(r'=\s*"(.*)"', line)
++                    if mo:
++                        keywords["date"] = mo.group(1)
++    except OSError:
+         pass
+     return keywords
+ 
+ 
+ @register_vcs_handler("git", "keywords")
+-def git_versions_from_keywords(keywords, tag_prefix, verbose):
++def git_versions_from_keywords(
++    keywords: Dict[str, str],
++    tag_prefix: str,
++    verbose: bool,
++) -> Dict[str, Any]:
+     """Get version information from git keywords."""
+-    if not keywords:
+-        raise NotThisMethod("no keywords at all, weird")
++    if "refnames" not in keywords:
++        raise NotThisMethod("Short version file found")
+     date = keywords.get("date")
+     if date is not None:
++        # Use only the last line.  Previous lines may contain GPG signature
++        # information.
++        date = date.splitlines()[-1]
++
+         # git-2.2.0 added "%%cI", which expands to an ISO-8601 -compliant
+         # datestamp. However we prefer "%%ci" (which expands to an "ISO-8601
+         # -like" string, which we must then edit to make compliant), because
+@@ -597,11 +726,11 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose):
+         if verbose:
+             print("keywords are unexpanded, not using")
+         raise NotThisMethod("unexpanded keywords, not a git-archive tarball")
+-    refs = set([r.strip() for r in refnames.strip("()").split(",")])
++    refs = {r.strip() for r in refnames.strip("()").split(",")}
+     # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of
+     # just "foo-1.0". If we see a "tag: " prefix, prefer those.
+     TAG = "tag: "
+-    tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)])
++    tags = {r[len(TAG):] for r in refs if r.startswith(TAG)}
+     if not tags:
+         # Either we're using git < 1.8.3, or there really are no tags. We use
+         # a heuristic: assume all version tags have a digit. The old git %%d
+@@ -610,7 +739,7 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose):
+         # between branches and tags. By ignoring refnames without digits, we
+         # filter out many common branch names like "release" and
+         # "stabilization", as well as "HEAD" and "master".
+-        tags = set([r for r in refs if re.search(r'\d', r)])
++        tags = {r for r in refs if re.search(r'\d', r)}
+         if verbose:
+             print("discarding '%%s', no digits" %% ",".join(refs - tags))
+     if verbose:
+@@ -619,6 +748,11 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose):
+         # sorting will prefer e.g. "2.0" over "2.0rc1"
+         if ref.startswith(tag_prefix):
+             r = ref[len(tag_prefix):]
++            # Filter out refs that exactly match prefix or that don't start
++            # with a number once the prefix is stripped (mostly a concern
++            # when prefix is '')
++            if not re.match(r'\d', r):
++                continue
+             if verbose:
+                 print("picking %%s" %% r)
+             return {"version": r,
+@@ -634,7 +768,12 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose):
+ 
+ 
+ @register_vcs_handler("git", "pieces_from_vcs")
+-def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
++def git_pieces_from_vcs(
++    tag_prefix: str,
++    root: str,
++    verbose: bool,
++    runner: Callable = run_command
++) -> Dict[str, Any]:
+     """Get version from 'git describe' in the root of the source tree.
+ 
+     This only gets called if the git-archive 'subst' keywords were *not*
+@@ -645,8 +784,15 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
+     if sys.platform == "win32":
+         GITS = ["git.cmd", "git.exe"]
+ 
+-    out, rc = run_command(GITS, ["rev-parse", "--git-dir"], cwd=root,
+-                          hide_stderr=True)
++    # GIT_DIR can interfere with correct operation of Versioneer.
++    # It may be intended to be passed to the Versioneer-versioned project,
++    # but that should not change where we get our version from.
++    env = os.environ.copy()
++    env.pop("GIT_DIR", None)
++    runner = functools.partial(runner, env=env)
++
++    _, rc = runner(GITS, ["rev-parse", "--git-dir"], cwd=root,
++                   hide_stderr=not verbose)
+     if rc != 0:
+         if verbose:
+             print("Directory %%s not under git control" %% root)
+@@ -654,24 +800,57 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
+ 
+     # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty]
+     # if there isn't one, this yields HEX[-dirty] (no NUM)
+-    describe_out, rc = run_command(GITS, ["describe", "--tags", "--dirty",
+-                                          "--always", "--long",
+-                                          "--match", "%%s*" %% tag_prefix],
+-                                   cwd=root)
++    describe_out, rc = runner(GITS, [
++        "describe", "--tags", "--dirty", "--always", "--long",
++        "--match", f"{tag_prefix}[[:digit:]]*"
++    ], cwd=root)
+     # --long was added in git-1.5.5
+     if describe_out is None:
+         raise NotThisMethod("'git describe' failed")
+     describe_out = describe_out.strip()
+-    full_out, rc = run_command(GITS, ["rev-parse", "HEAD"], cwd=root)
++    full_out, rc = runner(GITS, ["rev-parse", "HEAD"], cwd=root)
+     if full_out is None:
+         raise NotThisMethod("'git rev-parse' failed")
+     full_out = full_out.strip()
+ 
+-    pieces = {}
++    pieces: Dict[str, Any] = {}
+     pieces["long"] = full_out
+     pieces["short"] = full_out[:7]  # maybe improved later
+     pieces["error"] = None
+ 
++    branch_name, rc = runner(GITS, ["rev-parse", "--abbrev-ref", "HEAD"],
++                             cwd=root)
++    # --abbrev-ref was added in git-1.6.3
++    if rc != 0 or branch_name is None:
++        raise NotThisMethod("'git rev-parse --abbrev-ref' returned error")
++    branch_name = branch_name.strip()
++
++    if branch_name == "HEAD":
++        # If we aren't exactly on a branch, pick a branch which represents
++        # the current commit. If all else fails, we are on a branchless
++        # commit.
++        branches, rc = runner(GITS, ["branch", "--contains"], cwd=root)
++        # --contains was added in git-1.5.4
++        if rc != 0 or branches is None:
++            raise NotThisMethod("'git branch --contains' returned error")
++        branches = branches.split("\n")
++
++        # Remove the first line if we're running detached
++        if "(" in branches[0]:
++            branches.pop(0)
++
++        # Strip off the leading "* " from the list of branches.
++        branches = [branch[2:] for branch in branches]
++        if "master" in branches:
++            branch_name = "master"
++        elif not branches:
++            branch_name = None
++        else:
++            # Pick the first branch that is returned. Good or bad.
++            branch_name = branches[0]
++
++    pieces["branch"] = branch_name
++
+     # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty]
+     # TAG might have hyphens.
+     git_describe = describe_out
+@@ -688,7 +867,7 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
+         # TAG-NUM-gHEX
+         mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe)
+         if not mo:
+-            # unparseable. Maybe git-describe is misbehaving?
++            # unparsable. Maybe git-describe is misbehaving?
+             pieces["error"] = ("unable to parse git-describe output: '%%s'"
+                                %% describe_out)
+             return pieces
+@@ -713,26 +892,27 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
+     else:
+         # HEX: no tags
+         pieces["closest-tag"] = None
+-        count_out, rc = run_command(GITS, ["rev-list", "HEAD", "--count"],
+-                                    cwd=root)
+-        pieces["distance"] = int(count_out)  # total number of commits
++        out, rc = runner(GITS, ["rev-list", "HEAD", "--left-right"], cwd=root)
++        pieces["distance"] = len(out.split())  # total number of commits
+ 
+     # commit date: see ISO-8601 comment in git_versions_from_keywords()
+-    date = run_command(GITS, ["show", "-s", "--format=%%ci", "HEAD"],
+-                       cwd=root)[0].strip()
++    date = runner(GITS, ["show", "-s", "--format=%%ci", "HEAD"], cwd=root)[0].strip()
++    # Use only the last line.  Previous lines may contain GPG signature
++    # information.
++    date = date.splitlines()[-1]
+     pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1)
+ 
+     return pieces
+ 
+ 
+-def plus_or_dot(pieces):
++def plus_or_dot(pieces: Dict[str, Any]) -> str:
+     """Return a + if we don't already have one, else return a ."""
+     if "+" in pieces.get("closest-tag", ""):
+         return "."
+     return "+"
+ 
+ 
+-def render_pep440(pieces):
++def render_pep440(pieces: Dict[str, Any]) -> str:
+     """Build up version string, with post-release "local version identifier".
+ 
+     Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you
+@@ -757,23 +937,71 @@ def render_pep440(pieces):
+     return rendered
+ 
+ 
+-def render_pep440_pre(pieces):
+-    """TAG[.post.devDISTANCE] -- No -dirty.
++def render_pep440_branch(pieces: Dict[str, Any]) -> str:
++    """TAG[[.dev0]+DISTANCE.gHEX[.dirty]] .
++
++    The ".dev0" means not master branch. Note that .dev0 sorts backwards
++    (a feature branch will appear "older" than the master branch).
+ 
+     Exceptions:
+-    1: no tags. 0.post.devDISTANCE
++    1: no tags. 0[.dev0]+untagged.DISTANCE.gHEX[.dirty]
+     """
+     if pieces["closest-tag"]:
+         rendered = pieces["closest-tag"]
++        if pieces["distance"] or pieces["dirty"]:
++            if pieces["branch"] != "master":
++                rendered += ".dev0"
++            rendered += plus_or_dot(pieces)
++            rendered += "%%d.g%%s" %% (pieces["distance"], pieces["short"])
++            if pieces["dirty"]:
++                rendered += ".dirty"
++    else:
++        # exception #1
++        rendered = "0"
++        if pieces["branch"] != "master":
++            rendered += ".dev0"
++        rendered += "+untagged.%%d.g%%s" %% (pieces["distance"],
++                                          pieces["short"])
++        if pieces["dirty"]:
++            rendered += ".dirty"
++    return rendered
++
++
++def pep440_split_post(ver: str) -> Tuple[str, Optional[int]]:
++    """Split pep440 version string at the post-release segment.
++
++    Returns the release segments before the post-release and the
++    post-release version number (or -1 if no post-release segment is present).
++    """
++    vc = str.split(ver, ".post")
++    return vc[0], int(vc[1] or 0) if len(vc) == 2 else None
++
++
++def render_pep440_pre(pieces: Dict[str, Any]) -> str:
++    """TAG[.postN.devDISTANCE] -- No -dirty.
++
++    Exceptions:
++    1: no tags. 0.post0.devDISTANCE
++    """
++    if pieces["closest-tag"]:
+         if pieces["distance"]:
+-            rendered += ".post.dev%%d" %% pieces["distance"]
++            # update the post release segment
++            tag_version, post_version = pep440_split_post(pieces["closest-tag"])
++            rendered = tag_version
++            if post_version is not None:
++                rendered += ".post%%d.dev%%d" %% (post_version + 1, pieces["distance"])
++            else:
++                rendered += ".post0.dev%%d" %% (pieces["distance"])
++        else:
++            # no commits, use the tag as the version
++            rendered = pieces["closest-tag"]
+     else:
+         # exception #1
+-        rendered = "0.post.dev%%d" %% pieces["distance"]
++        rendered = "0.post0.dev%%d" %% pieces["distance"]
+     return rendered
+ 
+ 
+-def render_pep440_post(pieces):
++def render_pep440_post(pieces: Dict[str, Any]) -> str:
+     """TAG[.postDISTANCE[.dev0]+gHEX] .
+ 
+     The ".dev0" means dirty. Note that .dev0 sorts backwards
+@@ -800,12 +1028,41 @@ def render_pep440_post(pieces):
+     return rendered
+ 
+ 
+-def render_pep440_old(pieces):
++def render_pep440_post_branch(pieces: Dict[str, Any]) -> str:
++    """TAG[.postDISTANCE[.dev0]+gHEX[.dirty]] .
++
++    The ".dev0" means not master branch.
++
++    Exceptions:
++    1: no tags. 0.postDISTANCE[.dev0]+gHEX[.dirty]
++    """
++    if pieces["closest-tag"]:
++        rendered = pieces["closest-tag"]
++        if pieces["distance"] or pieces["dirty"]:
++            rendered += ".post%%d" %% pieces["distance"]
++            if pieces["branch"] != "master":
++                rendered += ".dev0"
++            rendered += plus_or_dot(pieces)
++            rendered += "g%%s" %% pieces["short"]
++            if pieces["dirty"]:
++                rendered += ".dirty"
++    else:
++        # exception #1
++        rendered = "0.post%%d" %% pieces["distance"]
++        if pieces["branch"] != "master":
++            rendered += ".dev0"
++        rendered += "+g%%s" %% pieces["short"]
++        if pieces["dirty"]:
++            rendered += ".dirty"
++    return rendered
++
++
++def render_pep440_old(pieces: Dict[str, Any]) -> str:
+     """TAG[.postDISTANCE[.dev0]] .
+ 
+     The ".dev0" means dirty.
+ 
+-    Eexceptions:
++    Exceptions:
+     1: no tags. 0.postDISTANCE[.dev0]
+     """
+     if pieces["closest-tag"]:
+@@ -822,7 +1079,7 @@ def render_pep440_old(pieces):
+     return rendered
+ 
+ 
+-def render_git_describe(pieces):
++def render_git_describe(pieces: Dict[str, Any]) -> str:
+     """TAG[-DISTANCE-gHEX][-dirty].
+ 
+     Like 'git describe --tags --dirty --always'.
+@@ -842,7 +1099,7 @@ def render_git_describe(pieces):
+     return rendered
+ 
+ 
+-def render_git_describe_long(pieces):
++def render_git_describe_long(pieces: Dict[str, Any]) -> str:
+     """TAG-DISTANCE-gHEX[-dirty].
+ 
+     Like 'git describe --tags --dirty --always -long'.
+@@ -862,7 +1119,7 @@ def render_git_describe_long(pieces):
+     return rendered
+ 
+ 
+-def render(pieces, style):
++def render(pieces: Dict[str, Any], style: str) -> Dict[str, Any]:
+     """Render the given version pieces into the requested style."""
+     if pieces["error"]:
+         return {"version": "unknown",
+@@ -876,10 +1133,14 @@ def render(pieces, style):
+ 
+     if style == "pep440":
+         rendered = render_pep440(pieces)
++    elif style == "pep440-branch":
++        rendered = render_pep440_branch(pieces)
+     elif style == "pep440-pre":
+         rendered = render_pep440_pre(pieces)
+     elif style == "pep440-post":
+         rendered = render_pep440_post(pieces)
++    elif style == "pep440-post-branch":
++        rendered = render_pep440_post_branch(pieces)
+     elif style == "pep440-old":
+         rendered = render_pep440_old(pieces)
+     elif style == "git-describe":
+@@ -894,7 +1155,7 @@ def render(pieces, style):
+             "date": pieces.get("date")}
+ 
+ 
+-def get_versions():
++def get_versions() -> Dict[str, Any]:
+     """Get version information or return default if unable to do so."""
+     # I am in _version.py, which lives at ROOT/VERSIONFILE_SOURCE. If we have
+     # __file__, we can work backwards from there to the root. Some
+@@ -915,7 +1176,7 @@ def get_versions():
+         # versionfile_source is the relative path from the top of the source
+         # tree (where the .git directory might live) to this file. Invert
+         # this to find the root from __file__.
+-        for i in cfg.versionfile_source.split('/'):
++        for _ in cfg.versionfile_source.split('/'):
+             root = os.path.dirname(root)
+     except NameError:
+         return {"version": "0+unknown", "full-revisionid": None,
+@@ -942,41 +1203,48 @@ def get_versions():
+ 
+ 
+ @register_vcs_handler("git", "get_keywords")
+-def git_get_keywords(versionfile_abs):
++def git_get_keywords(versionfile_abs: str) -> Dict[str, str]:
+     """Extract version information from the given file."""
+     # the code embedded in _version.py can just fetch the value of these
+     # keywords. When used from setup.py, we don't want to import _version.py,
+     # so we do it with a regexp instead. This function is not used from
+     # _version.py.
+-    keywords = {}
++    keywords: Dict[str, str] = {}
+     try:
+-        f = open(versionfile_abs, "r")
+-        for line in f.readlines():
+-            if line.strip().startswith("git_refnames ="):
+-                mo = re.search(r'=\s*"(.*)"', line)
+-                if mo:
+-                    keywords["refnames"] = mo.group(1)
+-            if line.strip().startswith("git_full ="):
+-                mo = re.search(r'=\s*"(.*)"', line)
+-                if mo:
+-                    keywords["full"] = mo.group(1)
+-            if line.strip().startswith("git_date ="):
+-                mo = re.search(r'=\s*"(.*)"', line)
+-                if mo:
+-                    keywords["date"] = mo.group(1)
+-        f.close()
+-    except EnvironmentError:
++        with open(versionfile_abs, "r") as fobj:
++            for line in fobj:
++                if line.strip().startswith("git_refnames ="):
++                    mo = re.search(r'=\s*"(.*)"', line)
++                    if mo:
++                        keywords["refnames"] = mo.group(1)
++                if line.strip().startswith("git_full ="):
++                    mo = re.search(r'=\s*"(.*)"', line)
++                    if mo:
++                        keywords["full"] = mo.group(1)
++                if line.strip().startswith("git_date ="):
++                    mo = re.search(r'=\s*"(.*)"', line)
++                    if mo:
++                        keywords["date"] = mo.group(1)
++    except OSError:
+         pass
+     return keywords
+ 
+ 
+ @register_vcs_handler("git", "keywords")
+-def git_versions_from_keywords(keywords, tag_prefix, verbose):
++def git_versions_from_keywords(
++    keywords: Dict[str, str],
++    tag_prefix: str,
++    verbose: bool,
++) -> Dict[str, Any]:
+     """Get version information from git keywords."""
+-    if not keywords:
+-        raise NotThisMethod("no keywords at all, weird")
++    if "refnames" not in keywords:
++        raise NotThisMethod("Short version file found")
+     date = keywords.get("date")
+     if date is not None:
++        # Use only the last line.  Previous lines may contain GPG signature
++        # information.
++        date = date.splitlines()[-1]
++
+         # git-2.2.0 added "%cI", which expands to an ISO-8601 -compliant
+         # datestamp. However we prefer "%ci" (which expands to an "ISO-8601
+         # -like" string, which we must then edit to make compliant), because
+@@ -989,11 +1257,11 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose):
+         if verbose:
+             print("keywords are unexpanded, not using")
+         raise NotThisMethod("unexpanded keywords, not a git-archive tarball")
+-    refs = set([r.strip() for r in refnames.strip("()").split(",")])
++    refs = {r.strip() for r in refnames.strip("()").split(",")}
+     # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of
+     # just "foo-1.0". If we see a "tag: " prefix, prefer those.
+     TAG = "tag: "
+-    tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)])
++    tags = {r[len(TAG) :] for r in refs if r.startswith(TAG)}
+     if not tags:
+         # Either we're using git < 1.8.3, or there really are no tags. We use
+         # a heuristic: assume all version tags have a digit. The old git %d
+@@ -1002,7 +1270,7 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose):
+         # between branches and tags. By ignoring refnames without digits, we
+         # filter out many common branch names like "release" and
+         # "stabilization", as well as "HEAD" and "master".
+-        tags = set([r for r in refs if re.search(r'\d', r)])
++        tags = {r for r in refs if re.search(r"\d", r)}
+         if verbose:
+             print("discarding '%s', no digits" % ",".join(refs - tags))
+     if verbose:
+@@ -1010,23 +1278,37 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose):
+     for ref in sorted(tags):
+         # sorting will prefer e.g. "2.0" over "2.0rc1"
+         if ref.startswith(tag_prefix):
+-            r = ref[len(tag_prefix):]
++            r = ref[len(tag_prefix) :]
++            # Filter out refs that exactly match prefix or that don't start
++            # with a number once the prefix is stripped (mostly a concern
++            # when prefix is '')
++            if not re.match(r"\d", r):
++                continue
+             if verbose:
+                 print("picking %s" % r)
+-            return {"version": r,
+-                    "full-revisionid": keywords["full"].strip(),
+-                    "dirty": False, "error": None,
+-                    "date": date}
++            return {
++                "version": r,
++                "full-revisionid": keywords["full"].strip(),
++                "dirty": False,
++                "error": None,
++                "date": date,
++            }
+     # no suitable tags, so version is "0+unknown", but full hex is still there
+     if verbose:
+         print("no suitable tags, using unknown + full revision id")
+-    return {"version": "0+unknown",
+-            "full-revisionid": keywords["full"].strip(),
+-            "dirty": False, "error": "no suitable tags", "date": None}
++    return {
++        "version": "0+unknown",
++        "full-revisionid": keywords["full"].strip(),
++        "dirty": False,
++        "error": "no suitable tags",
++        "date": None,
++    }
+ 
+ 
+ @register_vcs_handler("git", "pieces_from_vcs")
+-def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
++def git_pieces_from_vcs(
++    tag_prefix: str, root: str, verbose: bool, runner: Callable = run_command
++) -> Dict[str, Any]:
+     """Get version from 'git describe' in the root of the source tree.
+ 
+     This only gets called if the git-archive 'subst' keywords were *not*
+@@ -1037,8 +1319,14 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
+     if sys.platform == "win32":
+         GITS = ["git.cmd", "git.exe"]
+ 
+-    out, rc = run_command(GITS, ["rev-parse", "--git-dir"], cwd=root,
+-                          hide_stderr=True)
++    # GIT_DIR can interfere with correct operation of Versioneer.
++    # It may be intended to be passed to the Versioneer-versioned project,
++    # but that should not change where we get our version from.
++    env = os.environ.copy()
++    env.pop("GIT_DIR", None)
++    runner = functools.partial(runner, env=env)
++
++    _, rc = runner(GITS, ["rev-parse", "--git-dir"], cwd=root, hide_stderr=not verbose)
+     if rc != 0:
+         if verbose:
+             print("Directory %s not under git control" % root)
+@@ -1046,24 +1334,65 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
+ 
+     # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty]
+     # if there isn't one, this yields HEX[-dirty] (no NUM)
+-    describe_out, rc = run_command(GITS, ["describe", "--tags", "--dirty",
+-                                          "--always", "--long",
+-                                          "--match", "%s*" % tag_prefix],
+-                                   cwd=root)
++    describe_out, rc = runner(
++        GITS,
++        [
++            "describe",
++            "--tags",
++            "--dirty",
++            "--always",
++            "--long",
++            "--match",
++            f"{tag_prefix}[[:digit:]]*",
++        ],
++        cwd=root,
++    )
+     # --long was added in git-1.5.5
+     if describe_out is None:
+         raise NotThisMethod("'git describe' failed")
+     describe_out = describe_out.strip()
+-    full_out, rc = run_command(GITS, ["rev-parse", "HEAD"], cwd=root)
++    full_out, rc = runner(GITS, ["rev-parse", "HEAD"], cwd=root)
+     if full_out is None:
+         raise NotThisMethod("'git rev-parse' failed")
+     full_out = full_out.strip()
+ 
+-    pieces = {}
++    pieces: Dict[str, Any] = {}
+     pieces["long"] = full_out
+     pieces["short"] = full_out[:7]  # maybe improved later
+     pieces["error"] = None
+ 
++    branch_name, rc = runner(GITS, ["rev-parse", "--abbrev-ref", "HEAD"], cwd=root)
++    # --abbrev-ref was added in git-1.6.3
++    if rc != 0 or branch_name is None:
++        raise NotThisMethod("'git rev-parse --abbrev-ref' returned error")
++    branch_name = branch_name.strip()
++
++    if branch_name == "HEAD":
++        # If we aren't exactly on a branch, pick a branch which represents
++        # the current commit. If all else fails, we are on a branchless
++        # commit.
++        branches, rc = runner(GITS, ["branch", "--contains"], cwd=root)
++        # --contains was added in git-1.5.4
++        if rc != 0 or branches is None:
++            raise NotThisMethod("'git branch --contains' returned error")
++        branches = branches.split("\n")
++
++        # Remove the first line if we're running detached
++        if "(" in branches[0]:
++            branches.pop(0)
++
++        # Strip off the leading "* " from the list of branches.
++        branches = [branch[2:] for branch in branches]
++        if "master" in branches:
++            branch_name = "master"
++        elif not branches:
++            branch_name = None
++        else:
++            # Pick the first branch that is returned. Good or bad.
++            branch_name = branches[0]
++
++    pieces["branch"] = branch_name
++
+     # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty]
+     # TAG might have hyphens.
+     git_describe = describe_out
+@@ -1072,17 +1401,16 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
+     dirty = git_describe.endswith("-dirty")
+     pieces["dirty"] = dirty
+     if dirty:
+-        git_describe = git_describe[:git_describe.rindex("-dirty")]
++        git_describe = git_describe[: git_describe.rindex("-dirty")]
+ 
+     # now we have TAG-NUM-gHEX or HEX
+ 
+     if "-" in git_describe:
+         # TAG-NUM-gHEX
+-        mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe)
++        mo = re.search(r"^(.+)-(\d+)-g([0-9a-f]+)$", git_describe)
+         if not mo:
+-            # unparseable. Maybe git-describe is misbehaving?
+-            pieces["error"] = ("unable to parse git-describe output: '%s'"
+-                               % describe_out)
++            # unparsable. Maybe git-describe is misbehaving?
++            pieces["error"] = "unable to parse git-describe output: '%s'" % describe_out
+             return pieces
+ 
+         # tag
+@@ -1091,10 +1419,12 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
+             if verbose:
+                 fmt = "tag '%s' doesn't start with prefix '%s'"
+                 print(fmt % (full_tag, tag_prefix))
+-            pieces["error"] = ("tag '%s' doesn't start with prefix '%s'"
+-                               % (full_tag, tag_prefix))
++            pieces["error"] = "tag '%s' doesn't start with prefix '%s'" % (
++                full_tag,
++                tag_prefix,
++            )
+             return pieces
+-        pieces["closest-tag"] = full_tag[len(tag_prefix):]
++        pieces["closest-tag"] = full_tag[len(tag_prefix) :]
+ 
+         # distance: number of commits since tag
+         pieces["distance"] = int(mo.group(2))
+@@ -1105,19 +1435,20 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
+     else:
+         # HEX: no tags
+         pieces["closest-tag"] = None
+-        count_out, rc = run_command(GITS, ["rev-list", "HEAD", "--count"],
+-                                    cwd=root)
+-        pieces["distance"] = int(count_out)  # total number of commits
++        out, rc = runner(GITS, ["rev-list", "HEAD", "--left-right"], cwd=root)
++        pieces["distance"] = len(out.split())  # total number of commits
+ 
+     # commit date: see ISO-8601 comment in git_versions_from_keywords()
+-    date = run_command(GITS, ["show", "-s", "--format=%ci", "HEAD"],
+-                       cwd=root)[0].strip()
++    date = runner(GITS, ["show", "-s", "--format=%ci", "HEAD"], cwd=root)[0].strip()
++    # Use only the last line.  Previous lines may contain GPG signature
++    # information.
++    date = date.splitlines()[-1]
+     pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1)
+ 
+     return pieces
+ 
+ 
+-def do_vcs_install(manifest_in, versionfile_source, ipy):
++def do_vcs_install(versionfile_source: str, ipy: Optional[str]) -> None:
+     """Git-specific installation logic for Versioneer.
+ 
+     For Git, this means creating/changing .gitattributes to mark _version.py
+@@ -1126,36 +1457,40 @@ def do_vcs_install(manifest_in, versionfile_source, ipy):
+     GITS = ["git"]
+     if sys.platform == "win32":
+         GITS = ["git.cmd", "git.exe"]
+-    files = [manifest_in, versionfile_source]
++    files = [versionfile_source]
+     if ipy:
+         files.append(ipy)
+-    try:
+-        me = __file__
+-        if me.endswith(".pyc") or me.endswith(".pyo"):
+-            me = os.path.splitext(me)[0] + ".py"
+-        versioneer_file = os.path.relpath(me)
+-    except NameError:
+-        versioneer_file = "versioneer.py"
+-    files.append(versioneer_file)
++    if "VERSIONEER_PEP518" not in globals():
++        try:
++            my_path = __file__
++            if my_path.endswith((".pyc", ".pyo")):
++                my_path = os.path.splitext(my_path)[0] + ".py"
++            versioneer_file = os.path.relpath(my_path)
++        except NameError:
++            versioneer_file = "versioneer.py"
++        files.append(versioneer_file)
+     present = False
+     try:
+-        f = open(".gitattributes", "r")
+-        for line in f.readlines():
+-            if line.strip().startswith(versionfile_source):
+-                if "export-subst" in line.strip().split()[1:]:
+-                    present = True
+-        f.close()
+-    except EnvironmentError:
++        with open(".gitattributes", "r") as fobj:
++            for line in fobj:
++                if line.strip().startswith(versionfile_source):
++                    if "export-subst" in line.strip().split()[1:]:
++                        present = True
++                        break
++    except OSError:
+         pass
+     if not present:
+-        f = open(".gitattributes", "a+")
+-        f.write("%s export-subst\n" % versionfile_source)
+-        f.close()
++        with open(".gitattributes", "a+") as fobj:
++            fobj.write(f"{versionfile_source} export-subst\n")
+         files.append(".gitattributes")
+     run_command(GITS, ["add", "--"] + files)
+ 
+ 
+-def versions_from_parentdir(parentdir_prefix, root, verbose):
++def versions_from_parentdir(
++    parentdir_prefix: str,
++    root: str,
++    verbose: bool,
++) -> Dict[str, Any]:
+     """Try to determine the version from the parent directory name.
+ 
+     Source tarballs conventionally unpack into a directory that includes both
+@@ -1164,24 +1499,29 @@ def versions_from_parentdir(parentdir_prefix, root, verbose):
+     """
+     rootdirs = []
+ 
+-    for i in range(3):
++    for _ in range(3):
+         dirname = os.path.basename(root)
+         if dirname.startswith(parentdir_prefix):
+-            return {"version": dirname[len(parentdir_prefix):],
+-                    "full-revisionid": None,
+-                    "dirty": False, "error": None, "date": None}
+-        else:
+-            rootdirs.append(root)
+-            root = os.path.dirname(root)  # up a level
++            return {
++                "version": dirname[len(parentdir_prefix) :],
++                "full-revisionid": None,
++                "dirty": False,
++                "error": None,
++                "date": None,
++            }
++        rootdirs.append(root)
++        root = os.path.dirname(root)  # up a level
+ 
+     if verbose:
+-        print("Tried directories %s but none started with prefix %s" %
+-              (str(rootdirs), parentdir_prefix))
++        print(
++            "Tried directories %s but none started with prefix %s"
++            % (str(rootdirs), parentdir_prefix)
++        )
+     raise NotThisMethod("rootdir doesn't start with parentdir_prefix")
+ 
+ 
+ SHORT_VERSION_PY = """
+-# This file was generated by 'versioneer.py' (0.18) from
++# This file was generated by 'versioneer.py' (0.29) from
+ # revision-control system data, or from the parent directory name of an
+ # unpacked source archive. Distribution tarballs contain a pre-generated copy
+ # of this file.
+@@ -1198,42 +1538,42 @@ def get_versions():
+ """
+ 
+ 
+-def versions_from_file(filename):
++def versions_from_file(filename: str) -> Dict[str, Any]:
+     """Try to determine the version from _version.py if present."""
+     try:
+         with open(filename) as f:
+             contents = f.read()
+-    except EnvironmentError:
++    except OSError:
+         raise NotThisMethod("unable to read _version.py")
+-    mo = re.search(r"version_json = '''\n(.*)'''  # END VERSION_JSON",
+-                   contents, re.M | re.S)
++    mo = re.search(
++        r"version_json = '''\n(.*)'''  # END VERSION_JSON", contents, re.M | re.S
++    )
+     if not mo:
+-        mo = re.search(r"version_json = '''\r\n(.*)'''  # END VERSION_JSON",
+-                       contents, re.M | re.S)
++        mo = re.search(
++            r"version_json = '''\r\n(.*)'''  # END VERSION_JSON", contents, re.M | re.S
++        )
+     if not mo:
+         raise NotThisMethod("no version_json in _version.py")
+     return json.loads(mo.group(1))
+ 
+ 
+-def write_to_version_file(filename, versions):
++def write_to_version_file(filename: str, versions: Dict[str, Any]) -> None:
+     """Write the given version number to the given _version.py file."""
+-    os.unlink(filename)
+-    contents = json.dumps(versions, sort_keys=True,
+-                          indent=1, separators=(",", ": "))
++    contents = json.dumps(versions, sort_keys=True, indent=1, separators=(",", ": "))
+     with open(filename, "w") as f:
+         f.write(SHORT_VERSION_PY % contents)
+ 
+     print("set %s to '%s'" % (filename, versions["version"]))
+ 
+ 
+-def plus_or_dot(pieces):
++def plus_or_dot(pieces: Dict[str, Any]) -> str:
+     """Return a + if we don't already have one, else return a ."""
+     if "+" in pieces.get("closest-tag", ""):
+         return "."
+     return "+"
+ 
+ 
+-def render_pep440(pieces):
++def render_pep440(pieces: Dict[str, Any]) -> str:
+     """Build up version string, with post-release "local version identifier".
+ 
+     Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you
+@@ -1251,30 +1591,76 @@ def render_pep440(pieces):
+                 rendered += ".dirty"
+     else:
+         # exception #1
+-        rendered = "0+untagged.%d.g%s" % (pieces["distance"],
+-                                          pieces["short"])
++        rendered = "0+untagged.%d.g%s" % (pieces["distance"], pieces["short"])
+         if pieces["dirty"]:
+             rendered += ".dirty"
+     return rendered
+ 
+ 
+-def render_pep440_pre(pieces):
+-    """TAG[.post.devDISTANCE] -- No -dirty.
++def render_pep440_branch(pieces: Dict[str, Any]) -> str:
++    """TAG[[.dev0]+DISTANCE.gHEX[.dirty]] .
++
++    The ".dev0" means not master branch. Note that .dev0 sorts backwards
++    (a feature branch will appear "older" than the master branch).
+ 
+     Exceptions:
+-    1: no tags. 0.post.devDISTANCE
++    1: no tags. 0[.dev0]+untagged.DISTANCE.gHEX[.dirty]
+     """
+     if pieces["closest-tag"]:
+         rendered = pieces["closest-tag"]
++        if pieces["distance"] or pieces["dirty"]:
++            if pieces["branch"] != "master":
++                rendered += ".dev0"
++            rendered += plus_or_dot(pieces)
++            rendered += "%d.g%s" % (pieces["distance"], pieces["short"])
++            if pieces["dirty"]:
++                rendered += ".dirty"
++    else:
++        # exception #1
++        rendered = "0"
++        if pieces["branch"] != "master":
++            rendered += ".dev0"
++        rendered += "+untagged.%d.g%s" % (pieces["distance"], pieces["short"])
++        if pieces["dirty"]:
++            rendered += ".dirty"
++    return rendered
++
++
++def pep440_split_post(ver: str) -> Tuple[str, Optional[int]]:
++    """Split pep440 version string at the post-release segment.
++
++    Returns the release segments before the post-release and the
++    post-release version number (or -1 if no post-release segment is present).
++    """
++    vc = str.split(ver, ".post")
++    return vc[0], int(vc[1] or 0) if len(vc) == 2 else None
++
++
++def render_pep440_pre(pieces: Dict[str, Any]) -> str:
++    """TAG[.postN.devDISTANCE] -- No -dirty.
++
++    Exceptions:
++    1: no tags. 0.post0.devDISTANCE
++    """
++    if pieces["closest-tag"]:
+         if pieces["distance"]:
+-            rendered += ".post.dev%d" % pieces["distance"]
++            # update the post release segment
++            tag_version, post_version = pep440_split_post(pieces["closest-tag"])
++            rendered = tag_version
++            if post_version is not None:
++                rendered += ".post%d.dev%d" % (post_version + 1, pieces["distance"])
++            else:
++                rendered += ".post0.dev%d" % (pieces["distance"])
++        else:
++            # no commits, use the tag as the version
++            rendered = pieces["closest-tag"]
+     else:
+         # exception #1
+-        rendered = "0.post.dev%d" % pieces["distance"]
++        rendered = "0.post0.dev%d" % pieces["distance"]
+     return rendered
+ 
+ 
+-def render_pep440_post(pieces):
++def render_pep440_post(pieces: Dict[str, Any]) -> str:
+     """TAG[.postDISTANCE[.dev0]+gHEX] .
+ 
+     The ".dev0" means dirty. Note that .dev0 sorts backwards
+@@ -1301,12 +1687,41 @@ def render_pep440_post(pieces):
+     return rendered
+ 
+ 
+-def render_pep440_old(pieces):
++def render_pep440_post_branch(pieces: Dict[str, Any]) -> str:
++    """TAG[.postDISTANCE[.dev0]+gHEX[.dirty]] .
++
++    The ".dev0" means not master branch.
++
++    Exceptions:
++    1: no tags. 0.postDISTANCE[.dev0]+gHEX[.dirty]
++    """
++    if pieces["closest-tag"]:
++        rendered = pieces["closest-tag"]
++        if pieces["distance"] or pieces["dirty"]:
++            rendered += ".post%d" % pieces["distance"]
++            if pieces["branch"] != "master":
++                rendered += ".dev0"
++            rendered += plus_or_dot(pieces)
++            rendered += "g%s" % pieces["short"]
++            if pieces["dirty"]:
++                rendered += ".dirty"
++    else:
++        # exception #1
++        rendered = "0.post%d" % pieces["distance"]
++        if pieces["branch"] != "master":
++            rendered += ".dev0"
++        rendered += "+g%s" % pieces["short"]
++        if pieces["dirty"]:
++            rendered += ".dirty"
++    return rendered
++
++
++def render_pep440_old(pieces: Dict[str, Any]) -> str:
+     """TAG[.postDISTANCE[.dev0]] .
+ 
+     The ".dev0" means dirty.
+ 
+-    Eexceptions:
++    Exceptions:
+     1: no tags. 0.postDISTANCE[.dev0]
+     """
+     if pieces["closest-tag"]:
+@@ -1323,7 +1738,7 @@ def render_pep440_old(pieces):
+     return rendered
+ 
+ 
+-def render_git_describe(pieces):
++def render_git_describe(pieces: Dict[str, Any]) -> str:
+     """TAG[-DISTANCE-gHEX][-dirty].
+ 
+     Like 'git describe --tags --dirty --always'.
+@@ -1343,7 +1758,7 @@ def render_git_describe(pieces):
+     return rendered
+ 
+ 
+-def render_git_describe_long(pieces):
++def render_git_describe_long(pieces: Dict[str, Any]) -> str:
+     """TAG-DISTANCE-gHEX[-dirty].
+ 
+     Like 'git describe --tags --dirty --always -long'.
+@@ -1363,24 +1778,30 @@ def render_git_describe_long(pieces):
+     return rendered
+ 
+ 
+-def render(pieces, style):
++def render(pieces: Dict[str, Any], style: str) -> Dict[str, Any]:
+     """Render the given version pieces into the requested style."""
+     if pieces["error"]:
+-        return {"version": "unknown",
+-                "full-revisionid": pieces.get("long"),
+-                "dirty": None,
+-                "error": pieces["error"],
+-                "date": None}
++        return {
++            "version": "unknown",
++            "full-revisionid": pieces.get("long"),
++            "dirty": None,
++            "error": pieces["error"],
++            "date": None,
++        }
+ 
+     if not style or style == "default":
+         style = "pep440"  # the default
+ 
+     if style == "pep440":
+         rendered = render_pep440(pieces)
++    elif style == "pep440-branch":
++        rendered = render_pep440_branch(pieces)
+     elif style == "pep440-pre":
+         rendered = render_pep440_pre(pieces)
+     elif style == "pep440-post":
+         rendered = render_pep440_post(pieces)
++    elif style == "pep440-post-branch":
++        rendered = render_pep440_post_branch(pieces)
+     elif style == "pep440-old":
+         rendered = render_pep440_old(pieces)
+     elif style == "git-describe":
+@@ -1390,16 +1811,20 @@ def render(pieces, style):
+     else:
+         raise ValueError("unknown style '%s'" % style)
+ 
+-    return {"version": rendered, "full-revisionid": pieces["long"],
+-            "dirty": pieces["dirty"], "error": None,
+-            "date": pieces.get("date")}
++    return {
++        "version": rendered,
++        "full-revisionid": pieces["long"],
++        "dirty": pieces["dirty"],
++        "error": None,
++        "date": pieces.get("date"),
++    }
+ 
+ 
+ class VersioneerBadRootError(Exception):
+     """The project root directory is unknown or missing key files."""
+ 
+ 
+-def get_versions(verbose=False):
++def get_versions(verbose: bool = False) -> Dict[str, Any]:
+     """Get the project version from whatever source is available.
+ 
+     Returns dict with two keys: 'version' and 'full'.
+@@ -1414,9 +1839,10 @@ def get_versions(verbose=False):
+     assert cfg.VCS is not None, "please set [versioneer]VCS= in setup.cfg"
+     handlers = HANDLERS.get(cfg.VCS)
+     assert handlers, "unrecognized VCS '%s'" % cfg.VCS
+-    verbose = verbose or cfg.verbose
+-    assert cfg.versionfile_source is not None, \
+-        "please set versioneer.versionfile_source"
++    verbose = verbose or bool(cfg.verbose)  # `bool()` used to avoid `None`
++    assert (
++        cfg.versionfile_source is not None
++    ), "please set versioneer.versionfile_source"
+     assert cfg.tag_prefix is not None, "please set versioneer.tag_prefix"
+ 
+     versionfile_abs = os.path.join(root, cfg.versionfile_source)
+@@ -1470,18 +1896,26 @@ def get_versions(verbose=False):
+     if verbose:
+         print("unable to compute version")
+ 
+-    return {"version": "0+unknown", "full-revisionid": None,
+-            "dirty": None, "error": "unable to compute version",
+-            "date": None}
++    return {
++        "version": "0+unknown",
++        "full-revisionid": None,
++        "dirty": None,
++        "error": "unable to compute version",
++        "date": None,
++    }
+ 
+ 
+-def get_version():
++def get_version() -> str:
+     """Get the short version string for this project."""
+     return get_versions()["version"]
+ 
+ 
+-def get_cmdclass():
+-    """Get the custom setuptools/distutils subclasses used by Versioneer."""
++def get_cmdclass(cmdclass: Optional[Dict[str, Any]] = None):
++    """Get the custom setuptools subclasses used by Versioneer.
++
++    If the package uses a different cmdclass (e.g. one from numpy), it
++    should be provide as an argument.
++    """
+     if "versioneer" in sys.modules:
+         del sys.modules["versioneer"]
+         # this fixes the "python setup.py develop" case (also 'install' and
+@@ -1495,25 +1929,25 @@ def get_cmdclass():
+         # parent is protected against the child's "import versioneer". By
+         # removing ourselves from sys.modules here, before the child build
+         # happens, we protect the child from the parent's versioneer too.
+-        # Also see https://github.com/warner/python-versioneer/issues/52
++        # Also see https://github.com/python-versioneer/python-versioneer/issues/52
+ 
+-    cmds = {}
++    cmds = {} if cmdclass is None else cmdclass.copy()
+ 
+-    # we add "version" to both distutils and setuptools
+-    from distutils.core import Command
++    # we add "version" to setuptools
++    from setuptools import Command
+ 
+     class cmd_version(Command):
+         description = "report generated version string"
+-        user_options = []
+-        boolean_options = []
++        user_options: List[Tuple[str, str, str]] = []
++        boolean_options: List[str] = []
+ 
+-        def initialize_options(self):
++        def initialize_options(self) -> None:
+             pass
+ 
+-        def finalize_options(self):
++        def finalize_options(self) -> None:
+             pass
+ 
+-        def run(self):
++        def run(self) -> None:
+             vers = get_versions(verbose=True)
+             print("Version: %s" % vers["version"])
+             print(" full-revisionid: %s" % vers.get("full-revisionid"))
+@@ -1521,9 +1955,10 @@ def get_cmdclass():
+             print(" date: %s" % vers.get("date"))
+             if vers["error"]:
+                 print(" error: %s" % vers["error"])
++
+     cmds["version"] = cmd_version
+ 
+-    # we override "build_py" in both distutils and setuptools
++    # we override "build_py" in setuptools
+     #
+     # most invocation pathways end up running build_py:
+     #  distutils/build -> build_py
+@@ -1538,29 +1973,71 @@ def get_cmdclass():
+     #   then does setup.py bdist_wheel, or sometimes setup.py install
+     #  setup.py egg_info -> ?
+ 
++    # pip install -e . and setuptool/editable_wheel will invoke build_py
++    # but the build_py command is not expected to copy any files.
++
+     # we override different "build_py" commands for both environments
+-    if "setuptools" in sys.modules:
+-        from setuptools.command.build_py import build_py as _build_py
++    if "build_py" in cmds:
++        _build_py: Any = cmds["build_py"]
+     else:
+-        from distutils.command.build_py import build_py as _build_py
++        from setuptools.command.build_py import build_py as _build_py
+ 
+     class cmd_build_py(_build_py):
+-        def run(self):
++        def run(self) -> None:
+             root = get_root()
+             cfg = get_config_from_root(root)
+             versions = get_versions()
+             _build_py.run(self)
++            if getattr(self, "editable_mode", False):
++                # During editable installs `.py` and data files are
++                # not copied to build_lib
++                return
+             # now locate _version.py in the new build/ directory and replace
+             # it with an updated value
+             if cfg.versionfile_build:
+-                target_versionfile = os.path.join(self.build_lib,
+-                                                  cfg.versionfile_build)
++                target_versionfile = os.path.join(self.build_lib, cfg.versionfile_build)
+                 print("UPDATING %s" % target_versionfile)
+                 write_to_version_file(target_versionfile, versions)
++
+     cmds["build_py"] = cmd_build_py
+ 
++    if "build_ext" in cmds:
++        _build_ext: Any = cmds["build_ext"]
++    else:
++        from setuptools.command.build_ext import build_ext as _build_ext
++
++    class cmd_build_ext(_build_ext):
++        def run(self) -> None:
++            root = get_root()
++            cfg = get_config_from_root(root)
++            versions = get_versions()
++            _build_ext.run(self)
++            if self.inplace:
++                # build_ext --inplace will only build extensions in
++                # build/lib<..> dir with no _version.py to write to.
++                # As in place builds will already have a _version.py
++                # in the module dir, we do not need to write one.
++                return
++            # now locate _version.py in the new build/ directory and replace
++            # it with an updated value
++            if not cfg.versionfile_build:
++                return
++            target_versionfile = os.path.join(self.build_lib, cfg.versionfile_build)
++            if not os.path.exists(target_versionfile):
++                print(
++                    f"Warning: {target_versionfile} does not exist, skipping "
++                    "version update. This can happen if you are running build_ext "
++                    "without first running build_py."
++                )
++                return
++            print("UPDATING %s" % target_versionfile)
++            write_to_version_file(target_versionfile, versions)
++
++    cmds["build_ext"] = cmd_build_ext
++
+     if "cx_Freeze" in sys.modules:  # cx_freeze enabled?
+-        from cx_Freeze.dist import build_exe as _build_exe
++        from cx_Freeze.dist import build_exe as _build_exe  # type: ignore
++
+         # nczeczulin reports that py2exe won't like the pep440-style string
+         # as FILEVERSION, but it can be used for PRODUCTVERSION, e.g.
+         # setup(console=[{
+@@ -1569,7 +2046,7 @@ def get_cmdclass():
+         #   ...
+ 
+         class cmd_build_exe(_build_exe):
+-            def run(self):
++            def run(self) -> None:
+                 root = get_root()
+                 cfg = get_config_from_root(root)
+                 versions = get_versions()
+@@ -1581,24 +2058,28 @@ def get_cmdclass():
+                 os.unlink(target_versionfile)
+                 with open(cfg.versionfile_source, "w") as f:
+                     LONG = LONG_VERSION_PY[cfg.VCS]
+-                    f.write(LONG %
+-                            {"DOLLAR": "$",
+-                             "STYLE": cfg.style,
+-                             "TAG_PREFIX": cfg.tag_prefix,
+-                             "PARENTDIR_PREFIX": cfg.parentdir_prefix,
+-                             "VERSIONFILE_SOURCE": cfg.versionfile_source,
+-                             })
++                    f.write(
++                        LONG
++                        % {
++                            "DOLLAR": "$",
++                            "STYLE": cfg.style,
++                            "TAG_PREFIX": cfg.tag_prefix,
++                            "PARENTDIR_PREFIX": cfg.parentdir_prefix,
++                            "VERSIONFILE_SOURCE": cfg.versionfile_source,
++                        }
++                    )
++
+         cmds["build_exe"] = cmd_build_exe
+         del cmds["build_py"]
+ 
+-    if 'py2exe' in sys.modules:  # py2exe enabled?
++    if "py2exe" in sys.modules:  # py2exe enabled?
+         try:
+-            from py2exe.distutils_buildexe import py2exe as _py2exe  # py3
++            from py2exe.setuptools_buildexe import py2exe as _py2exe  # type: ignore
+         except ImportError:
+-            from py2exe.build_exe import py2exe as _py2exe  # py2
++            from py2exe.distutils_buildexe import py2exe as _py2exe  # type: ignore
+ 
+         class cmd_py2exe(_py2exe):
+-            def run(self):
++            def run(self) -> None:
+                 root = get_root()
+                 cfg = get_config_from_root(root)
+                 versions = get_versions()
+@@ -1610,23 +2091,67 @@ def get_cmdclass():
+                 os.unlink(target_versionfile)
+                 with open(cfg.versionfile_source, "w") as f:
+                     LONG = LONG_VERSION_PY[cfg.VCS]
+-                    f.write(LONG %
+-                            {"DOLLAR": "$",
+-                             "STYLE": cfg.style,
+-                             "TAG_PREFIX": cfg.tag_prefix,
+-                             "PARENTDIR_PREFIX": cfg.parentdir_prefix,
+-                             "VERSIONFILE_SOURCE": cfg.versionfile_source,
+-                             })
++                    f.write(
++                        LONG
++                        % {
++                            "DOLLAR": "$",
++                            "STYLE": cfg.style,
++                            "TAG_PREFIX": cfg.tag_prefix,
++                            "PARENTDIR_PREFIX": cfg.parentdir_prefix,
++                            "VERSIONFILE_SOURCE": cfg.versionfile_source,
++                        }
++                    )
++
+         cmds["py2exe"] = cmd_py2exe
+ 
++    # sdist farms its file list building out to egg_info
++    if "egg_info" in cmds:
++        _egg_info: Any = cmds["egg_info"]
++    else:
++        from setuptools.command.egg_info import egg_info as _egg_info
++
++    class cmd_egg_info(_egg_info):
++        def find_sources(self) -> None:
++            # egg_info.find_sources builds the manifest list and writes it
++            # in one shot
++            super().find_sources()
++
++            # Modify the filelist and normalize it
++            root = get_root()
++            cfg = get_config_from_root(root)
++            self.filelist.append("versioneer.py")
++            if cfg.versionfile_source:
++                # There are rare cases where versionfile_source might not be
++                # included by default, so we must be explicit
++                self.filelist.append(cfg.versionfile_source)
++            self.filelist.sort()
++            self.filelist.remove_duplicates()
++
++            # The write method is hidden in the manifest_maker instance that
++            # generated the filelist and was thrown away
++            # We will instead replicate their final normalization (to unicode,
++            # and POSIX-style paths)
++            from setuptools import unicode_utils
++
++            normalized = [
++                unicode_utils.filesys_decode(f).replace(os.sep, "/")
++                for f in self.filelist.files
++            ]
++
++            manifest_filename = os.path.join(self.egg_info, "SOURCES.txt")
++            with open(manifest_filename, "w") as fobj:
++                fobj.write("\n".join(normalized))
++
++    cmds["egg_info"] = cmd_egg_info
++
+     # we override different "sdist" commands for both environments
+-    if "setuptools" in sys.modules:
+-        from setuptools.command.sdist import sdist as _sdist
++    if "sdist" in cmds:
++        _sdist: Any = cmds["sdist"]
+     else:
+-        from distutils.command.sdist import sdist as _sdist
++        from setuptools.command.sdist import sdist as _sdist
+ 
+     class cmd_sdist(_sdist):
+-        def run(self):
++        def run(self) -> None:
+             versions = get_versions()
+             self._versioneer_generated_versions = versions
+             # unless we update this, the command will keep using the old
+@@ -1634,7 +2159,7 @@ def get_cmdclass():
+             self.distribution.metadata.version = versions["version"]
+             return _sdist.run(self)
+ 
+-        def make_release_tree(self, base_dir, files):
++        def make_release_tree(self, base_dir: str, files: List[str]) -> None:
+             root = get_root()
+             cfg = get_config_from_root(root)
+             _sdist.make_release_tree(self, base_dir, files)
+@@ -1643,8 +2168,10 @@ def get_cmdclass():
+             # updated value
+             target_versionfile = os.path.join(base_dir, cfg.versionfile_source)
+             print("UPDATING %s" % target_versionfile)
+-            write_to_version_file(target_versionfile,
+-                                  self._versioneer_generated_versions)
++            write_to_version_file(
++                target_versionfile, self._versioneer_generated_versions
++            )
++
+     cmds["sdist"] = cmd_sdist
+ 
+     return cmds
+@@ -1687,23 +2214,26 @@ SAMPLE_CONFIG = """
+ 
+ """
+ 
+-INIT_PY_SNIPPET = """
++OLD_SNIPPET = """
+ from ._version import get_versions
+ __version__ = get_versions()['version']
+ del get_versions
+ """
+ 
++INIT_PY_SNIPPET = """
++from . import {0}
++__version__ = {0}.get_versions()['version']
++"""
++
+ 
+-def do_setup():
+-    """Main VCS-independent setup function for installing Versioneer."""
++def do_setup() -> int:
++    """Do main VCS-independent setup function for installing Versioneer."""
+     root = get_root()
+     try:
+         cfg = get_config_from_root(root)
+-    except (EnvironmentError, configparser.NoSectionError,
+-            configparser.NoOptionError) as e:
+-        if isinstance(e, (EnvironmentError, configparser.NoSectionError)):
+-            print("Adding sample versioneer config to setup.cfg",
+-                  file=sys.stderr)
++    except (OSError, configparser.NoSectionError, configparser.NoOptionError) as e:
++        if isinstance(e, (OSError, configparser.NoSectionError)):
++            print("Adding sample versioneer config to setup.cfg", file=sys.stderr)
+             with open(os.path.join(root, "setup.cfg"), "a") as f:
+                 f.write(SAMPLE_CONFIG)
+         print(CONFIG_ERROR, file=sys.stderr)
+@@ -1712,71 +2242,49 @@ def do_setup():
+     print(" creating %s" % cfg.versionfile_source)
+     with open(cfg.versionfile_source, "w") as f:
+         LONG = LONG_VERSION_PY[cfg.VCS]
+-        f.write(LONG % {"DOLLAR": "$",
+-                        "STYLE": cfg.style,
+-                        "TAG_PREFIX": cfg.tag_prefix,
+-                        "PARENTDIR_PREFIX": cfg.parentdir_prefix,
+-                        "VERSIONFILE_SOURCE": cfg.versionfile_source,
+-                        })
+-
+-    ipy = os.path.join(os.path.dirname(cfg.versionfile_source),
+-                       "__init__.py")
++        f.write(
++            LONG
++            % {
++                "DOLLAR": "$",
++                "STYLE": cfg.style,
++                "TAG_PREFIX": cfg.tag_prefix,
++                "PARENTDIR_PREFIX": cfg.parentdir_prefix,
++                "VERSIONFILE_SOURCE": cfg.versionfile_source,
++            }
++        )
++
++    ipy = os.path.join(os.path.dirname(cfg.versionfile_source), "__init__.py")
++    maybe_ipy: Optional[str] = ipy
+     if os.path.exists(ipy):
+         try:
+             with open(ipy, "r") as f:
+                 old = f.read()
+-        except EnvironmentError:
++        except OSError:
+             old = ""
+-        if INIT_PY_SNIPPET not in old:
++        module = os.path.splitext(os.path.basename(cfg.versionfile_source))[0]
++        snippet = INIT_PY_SNIPPET.format(module)
++        if OLD_SNIPPET in old:
++            print(" replacing boilerplate in %s" % ipy)
++            with open(ipy, "w") as f:
++                f.write(old.replace(OLD_SNIPPET, snippet))
++        elif snippet not in old:
+             print(" appending to %s" % ipy)
+             with open(ipy, "a") as f:
+-                f.write(INIT_PY_SNIPPET)
++                f.write(snippet)
+         else:
+             print(" %s unmodified" % ipy)
+     else:
+         print(" %s doesn't exist, ok" % ipy)
+-        ipy = None
+-
+-    # Make sure both the top-level "versioneer.py" and versionfile_source
+-    # (PKG/_version.py, used by runtime code) are in MANIFEST.in, so
+-    # they'll be copied into source distributions. Pip won't be able to
+-    # install the package without this.
+-    manifest_in = os.path.join(root, "MANIFEST.in")
+-    simple_includes = set()
+-    try:
+-        with open(manifest_in, "r") as f:
+-            for line in f:
+-                if line.startswith("include "):
+-                    for include in line.split()[1:]:
+-                        simple_includes.add(include)
+-    except EnvironmentError:
+-        pass
+-    # That doesn't cover everything MANIFEST.in can do
+-    # (http://docs.python.org/2/distutils/sourcedist.html#commands), so
+-    # it might give some false negatives. Appending redundant 'include'
+-    # lines is safe, though.
+-    if "versioneer.py" not in simple_includes:
+-        print(" appending 'versioneer.py' to MANIFEST.in")
+-        with open(manifest_in, "a") as f:
+-            f.write("include versioneer.py\n")
+-    else:
+-        print(" 'versioneer.py' already in MANIFEST.in")
+-    if cfg.versionfile_source not in simple_includes:
+-        print(" appending versionfile_source ('%s') to MANIFEST.in" %
+-              cfg.versionfile_source)
+-        with open(manifest_in, "a") as f:
+-            f.write("include %s\n" % cfg.versionfile_source)
+-    else:
+-        print(" versionfile_source already in MANIFEST.in")
++        maybe_ipy = None
+ 
+     # Make VCS-specific changes. For git, this means creating/changing
+     # .gitattributes to mark _version.py for export-subst keyword
+     # substitution.
+-    do_vcs_install(manifest_in, cfg.versionfile_source, ipy)
++    do_vcs_install(cfg.versionfile_source, maybe_ipy)
+     return 0
+ 
+ 
+-def scan_setup_py():
++def scan_setup_py() -> int:
+     """Validate the contents of setup.py against Versioneer's expectations."""
+     found = set()
+     setters = False
+@@ -1813,10 +2321,14 @@ def scan_setup_py():
+     return errors
+ 
+ 
++def setup_command() -> NoReturn:
++    """Set up Versioneer and exit with appropriate error code."""
++    errors = do_setup()
++    errors += scan_setup_py()
++    sys.exit(1 if errors else 0)
++
++
+ if __name__ == "__main__":
+     cmd = sys.argv[1]
+     if cmd == "setup":
+-        errors = do_setup()
+-        errors += scan_setup_py()
+-        if errors:
+-            sys.exit(1)
++        setup_command()
+-- 
+2.41.0
+
-- 
2.41.0


_______________________________________________
buildroot mailing list
buildroot@buildroot.org
https://lists.buildroot.org/mailman/listinfo/buildroot


^ permalink raw reply related	[flat|nested] 54+ messages in thread

* [Buildroot] [PATCH 23/30] package/python-magic-wormhole-transit-relay: update versioneer to 0.29
  2023-10-26  9:26 [Buildroot] [PATCH 00/30] package/python3: bump version to 3.12.0 Adam Duskett
                   ` (21 preceding siblings ...)
  2023-10-26  9:26 ` [Buildroot] [PATCH 22/30] package/python-magic-wormhole-mailbox-server: " Adam Duskett
@ 2023-10-26  9:26 ` Adam Duskett
  2023-11-04 21:31   ` Arnout Vandecappelle via buildroot
  2023-10-26  9:26 ` [Buildroot] [PATCH 24/30] package/python-spake2: " Adam Duskett
                   ` (7 subsequent siblings)
  30 siblings, 1 reply; 54+ messages in thread
From: Adam Duskett @ 2023-10-26  9:26 UTC (permalink / raw)
  To: buildroot
  Cc: Adam Duskett, Andrey Smirnov, Julien Olivain, Asaf Kahlon,
	James Hilliard, Thomas Petazzoni, Mauro Condarelli

Versioneer < 0.21 is incompatible with Python 3.12.0. Use the latest version
which is 0.29 as of this commit.

Signed-off-by: Adam Duskett <adam.duskett@amarulasolutions.com>
---
 .../0001-Update-versioneer-to-0.29.patch      | 2194 +++++++++++++++++
 1 file changed, 2194 insertions(+)
 create mode 100644 package/python-magic-wormhole-transit-relay/0001-Update-versioneer-to-0.29.patch

diff --git a/package/python-magic-wormhole-transit-relay/0001-Update-versioneer-to-0.29.patch b/package/python-magic-wormhole-transit-relay/0001-Update-versioneer-to-0.29.patch
new file mode 100644
index 0000000000..51cecd138a
--- /dev/null
+++ b/package/python-magic-wormhole-transit-relay/0001-Update-versioneer-to-0.29.patch
@@ -0,0 +1,2194 @@
+From f00f54ecbd9bea970795da4f1f6091828a011bcd Mon Sep 17 00:00:00 2001
+From: Adam Duskett <adam.duskett@amarulasolutions.com>
+Date: Tue, 24 Oct 2023 09:52:45 +0200
+Subject: [PATCH] Update versioneer to 0.29
+
+Fixes builds against Python 3.12.0
+
+Upstream: https://github.com/magic-wormhole/magic-wormhole-transit-relay/pull/34
+
+Signed-off-by: Adam Duskett <adam.duskett@amarulasolutions.com>
+---
+ versioneer.py | 1350 ++++++++++++++++++++++++++++++++++---------------
+ 1 file changed, 931 insertions(+), 419 deletions(-)
+
+diff --git a/versioneer.py b/versioneer.py
+index 64fea1c..de97d90 100644
+--- a/versioneer.py
++++ b/versioneer.py
+@@ -1,5 +1,4 @@
+-
+-# Version: 0.18
++# Version: 0.29
+ 
+ """The Versioneer - like a rocketeer, but for versions.
+ 
+@@ -7,18 +6,14 @@ The Versioneer
+ ==============
+ 
+ * like a rocketeer, but for versions!
+-* https://github.com/warner/python-versioneer
++* https://github.com/python-versioneer/python-versioneer
+ * Brian Warner
+-* License: Public Domain
+-* Compatible With: python2.6, 2.7, 3.2, 3.3, 3.4, 3.5, 3.6, and pypy
+-* [![Latest Version]
+-(https://pypip.in/version/versioneer/badge.svg?style=flat)
+-](https://pypi.python.org/pypi/versioneer/)
+-* [![Build Status]
+-(https://travis-ci.org/warner/python-versioneer.png?branch=master)
+-](https://travis-ci.org/warner/python-versioneer)
+-
+-This is a tool for managing a recorded version number in distutils-based
++* License: Public Domain (Unlicense)
++* Compatible with: Python 3.7, 3.8, 3.9, 3.10, 3.11 and pypy3
++* [![Latest Version][pypi-image]][pypi-url]
++* [![Build Status][travis-image]][travis-url]
++
++This is a tool for managing a recorded version number in setuptools-based
+ python projects. The goal is to remove the tedious and error-prone "update
+ the embedded version string" step from your release process. Making a new
+ release should be as easy as recording a new tag in your version-control
+@@ -27,9 +22,38 @@ system, and maybe making new tarballs.
+ 
+ ## Quick Install
+ 
+-* `pip install versioneer` to somewhere to your $PATH
+-* add a `[versioneer]` section to your setup.cfg (see below)
+-* run `versioneer install` in your source tree, commit the results
++Versioneer provides two installation modes. The "classic" vendored mode installs
++a copy of versioneer into your repository. The experimental build-time dependency mode
++is intended to allow you to skip this step and simplify the process of upgrading.
++
++### Vendored mode
++
++* `pip install versioneer` to somewhere in your $PATH
++   * A [conda-forge recipe](https://github.com/conda-forge/versioneer-feedstock) is
++     available, so you can also use `conda install -c conda-forge versioneer`
++* add a `[tool.versioneer]` section to your `pyproject.toml` or a
++  `[versioneer]` section to your `setup.cfg` (see [Install](INSTALL.md))
++   * Note that you will need to add `tomli; python_version < "3.11"` to your
++     build-time dependencies if you use `pyproject.toml`
++* run `versioneer install --vendor` in your source tree, commit the results
++* verify version information with `python setup.py version`
++
++### Build-time dependency mode
++
++* `pip install versioneer` to somewhere in your $PATH
++   * A [conda-forge recipe](https://github.com/conda-forge/versioneer-feedstock) is
++     available, so you can also use `conda install -c conda-forge versioneer`
++* add a `[tool.versioneer]` section to your `pyproject.toml` or a
++  `[versioneer]` section to your `setup.cfg` (see [Install](INSTALL.md))
++* add `versioneer` (with `[toml]` extra, if configuring in `pyproject.toml`)
++  to the `requires` key of the `build-system` table in `pyproject.toml`:
++  ```toml
++  [build-system]
++  requires = ["setuptools", "versioneer[toml]"]
++  build-backend = "setuptools.build_meta"
++  ```
++* run `versioneer install --no-vendor` in your source tree, commit the results
++* verify version information with `python setup.py version`
+ 
+ ## Version Identifiers
+ 
+@@ -61,7 +85,7 @@ version 1.3). Many VCS systems can report a description that captures this,
+ for example `git describe --tags --dirty --always` reports things like
+ "0.7-1-g574ab98-dirty" to indicate that the checkout is one revision past the
+ 0.7 tag, has a unique revision id of "574ab98", and is "dirty" (it has
+-uncommitted changes.
++uncommitted changes).
+ 
+ The version identifier is used for multiple purposes:
+ 
+@@ -166,7 +190,7 @@ which may help identify what went wrong).
+ 
+ Some situations are known to cause problems for Versioneer. This details the
+ most significant ones. More can be found on Github
+-[issues page](https://github.com/warner/python-versioneer/issues).
++[issues page](https://github.com/python-versioneer/python-versioneer/issues).
+ 
+ ### Subprojects
+ 
+@@ -180,7 +204,7 @@ two common reasons why `setup.py` might not be in the root:
+   `setup.cfg`, and `tox.ini`. Projects like these produce multiple PyPI
+   distributions (and upload multiple independently-installable tarballs).
+ * Source trees whose main purpose is to contain a C library, but which also
+-  provide bindings to Python (and perhaps other langauges) in subdirectories.
++  provide bindings to Python (and perhaps other languages) in subdirectories.
+ 
+ Versioneer will look for `.git` in parent directories, and most operations
+ should get the right version string. However `pip` and `setuptools` have bugs
+@@ -194,9 +218,9 @@ work too.
+ Pip-8.1.1 is known to have this problem, but hopefully it will get fixed in
+ some later version.
+ 
+-[Bug #38](https://github.com/warner/python-versioneer/issues/38) is tracking
++[Bug #38](https://github.com/python-versioneer/python-versioneer/issues/38) is tracking
+ this issue. The discussion in
+-[PR #61](https://github.com/warner/python-versioneer/pull/61) describes the
++[PR #61](https://github.com/python-versioneer/python-versioneer/pull/61) describes the
+ issue from the Versioneer side in more detail.
+ [pip PR#3176](https://github.com/pypa/pip/pull/3176) and
+ [pip PR#3615](https://github.com/pypa/pip/pull/3615) contain work to improve
+@@ -224,31 +248,20 @@ regenerated while a different version is checked out. Many setup.py commands
+ cause egg_info to be rebuilt (including `sdist`, `wheel`, and installing into
+ a different virtualenv), so this can be surprising.
+ 
+-[Bug #83](https://github.com/warner/python-versioneer/issues/83) describes
++[Bug #83](https://github.com/python-versioneer/python-versioneer/issues/83) describes
+ this one, but upgrading to a newer version of setuptools should probably
+ resolve it.
+ 
+-### Unicode version strings
+-
+-While Versioneer works (and is continually tested) with both Python 2 and
+-Python 3, it is not entirely consistent with bytes-vs-unicode distinctions.
+-Newer releases probably generate unicode version strings on py2. It's not
+-clear that this is wrong, but it may be surprising for applications when then
+-write these strings to a network connection or include them in bytes-oriented
+-APIs like cryptographic checksums.
+-
+-[Bug #71](https://github.com/warner/python-versioneer/issues/71) investigates
+-this question.
+-
+ 
+ ## Updating Versioneer
+ 
+ To upgrade your project to a new release of Versioneer, do the following:
+ 
+ * install the new Versioneer (`pip install -U versioneer` or equivalent)
+-* edit `setup.cfg`, if necessary, to include any new configuration settings
+-  indicated by the release notes. See [UPGRADING](./UPGRADING.md) for details.
+-* re-run `versioneer install` in your source tree, to replace
++* edit `setup.cfg` and `pyproject.toml`, if necessary,
++  to include any new configuration settings indicated by the release notes.
++  See [UPGRADING](./UPGRADING.md) for details.
++* re-run `versioneer install --[no-]vendor` in your source tree, to replace
+   `SRC/_version.py`
+ * commit any changed files
+ 
+@@ -265,35 +278,70 @@ installation by editing setup.py . Alternatively, it might go the other
+ direction and include code from all supported VCS systems, reducing the
+ number of intermediate scripts.
+ 
++## Similar projects
++
++* [setuptools_scm](https://github.com/pypa/setuptools_scm/) - a non-vendored build-time
++  dependency
++* [minver](https://github.com/jbweston/miniver) - a lightweight reimplementation of
++  versioneer
++* [versioningit](https://github.com/jwodder/versioningit) - a PEP 518-based setuptools
++  plugin
+ 
+ ## License
+ 
+ To make Versioneer easier to embed, all its code is dedicated to the public
+ domain. The `_version.py` that it creates is also in the public domain.
+-Specifically, both are released under the Creative Commons "Public Domain
+-Dedication" license (CC0-1.0), as described in
+-https://creativecommons.org/publicdomain/zero/1.0/ .
++Specifically, both are released under the "Unlicense", as described in
++https://unlicense.org/.
++
++[pypi-image]: https://img.shields.io/pypi/v/versioneer.svg
++[pypi-url]: https://pypi.python.org/pypi/versioneer/
++[travis-image]:
++https://img.shields.io/travis/com/python-versioneer/python-versioneer.svg
++[travis-url]: https://travis-ci.com/github/python-versioneer/python-versioneer
+ 
+ """
++# pylint:disable=invalid-name,import-outside-toplevel,missing-function-docstring
++# pylint:disable=missing-class-docstring,too-many-branches,too-many-statements
++# pylint:disable=raise-missing-from,too-many-lines,too-many-locals,import-error
++# pylint:disable=too-few-public-methods,redefined-outer-name,consider-using-with
++# pylint:disable=attribute-defined-outside-init,too-many-arguments
+ 
+-from __future__ import print_function
+-try:
+-    import configparser
+-except ImportError:
+-    import ConfigParser as configparser
++import configparser
+ import errno
+ import json
+ import os
+ import re
+ import subprocess
+ import sys
++from pathlib import Path
++from typing import Any, Callable, cast, Dict, List, Optional, Tuple, Union
++from typing import NoReturn
++import functools
++
++have_tomllib = True
++if sys.version_info >= (3, 11):
++    import tomllib
++else:
++    try:
++        import tomli as tomllib
++    except ImportError:
++        have_tomllib = False
+ 
+ 
+ class VersioneerConfig:
+     """Container for Versioneer configuration parameters."""
+ 
++    VCS: str
++    style: str
++    tag_prefix: str
++    versionfile_source: str
++    versionfile_build: Optional[str]
++    parentdir_prefix: Optional[str]
++    verbose: Optional[bool]
++
+ 
+-def get_root():
++def get_root() -> str:
+     """Get the project root directory.
+ 
+     We require that all commands are run from the project root, i.e. the
+@@ -301,18 +349,30 @@ def get_root():
+     """
+     root = os.path.realpath(os.path.abspath(os.getcwd()))
+     setup_py = os.path.join(root, "setup.py")
++    pyproject_toml = os.path.join(root, "pyproject.toml")
+     versioneer_py = os.path.join(root, "versioneer.py")
+-    if not (os.path.exists(setup_py) or os.path.exists(versioneer_py)):
++    if not (
++        os.path.exists(setup_py)
++        or os.path.exists(pyproject_toml)
++        or os.path.exists(versioneer_py)
++    ):
+         # allow 'python path/to/setup.py COMMAND'
+         root = os.path.dirname(os.path.realpath(os.path.abspath(sys.argv[0])))
+         setup_py = os.path.join(root, "setup.py")
++        pyproject_toml = os.path.join(root, "pyproject.toml")
+         versioneer_py = os.path.join(root, "versioneer.py")
+-    if not (os.path.exists(setup_py) or os.path.exists(versioneer_py)):
+-        err = ("Versioneer was unable to run the project root directory. "
+-               "Versioneer requires setup.py to be executed from "
+-               "its immediate directory (like 'python setup.py COMMAND'), "
+-               "or in a way that lets it use sys.argv[0] to find the root "
+-               "(like 'python path/to/setup.py COMMAND').")
++    if not (
++        os.path.exists(setup_py)
++        or os.path.exists(pyproject_toml)
++        or os.path.exists(versioneer_py)
++    ):
++        err = (
++            "Versioneer was unable to run the project root directory. "
++            "Versioneer requires setup.py to be executed from "
++            "its immediate directory (like 'python setup.py COMMAND'), "
++            "or in a way that lets it use sys.argv[0] to find the root "
++            "(like 'python path/to/setup.py COMMAND')."
++        )
+         raise VersioneerBadRootError(err)
+     try:
+         # Certain runtime workflows (setup.py install/develop in a setuptools
+@@ -321,43 +381,64 @@ def get_root():
+         # module-import table will cache the first one. So we can't use
+         # os.path.dirname(__file__), as that will find whichever
+         # versioneer.py was first imported, even in later projects.
+-        me = os.path.realpath(os.path.abspath(__file__))
+-        me_dir = os.path.normcase(os.path.splitext(me)[0])
++        my_path = os.path.realpath(os.path.abspath(__file__))
++        me_dir = os.path.normcase(os.path.splitext(my_path)[0])
+         vsr_dir = os.path.normcase(os.path.splitext(versioneer_py)[0])
+-        if me_dir != vsr_dir:
+-            print("Warning: build in %s is using versioneer.py from %s"
+-                  % (os.path.dirname(me), versioneer_py))
++        if me_dir != vsr_dir and "VERSIONEER_PEP518" not in globals():
++            print(
++                "Warning: build in %s is using versioneer.py from %s"
++                % (os.path.dirname(my_path), versioneer_py)
++            )
+     except NameError:
+         pass
+     return root
+ 
+ 
+-def get_config_from_root(root):
++def get_config_from_root(root: str) -> VersioneerConfig:
+     """Read the project setup.cfg file to determine Versioneer config."""
+-    # This might raise EnvironmentError (if setup.cfg is missing), or
++    # This might raise OSError (if setup.cfg is missing), or
+     # configparser.NoSectionError (if it lacks a [versioneer] section), or
+     # configparser.NoOptionError (if it lacks "VCS="). See the docstring at
+     # the top of versioneer.py for instructions on writing your setup.cfg .
+-    setup_cfg = os.path.join(root, "setup.cfg")
+-    parser = configparser.SafeConfigParser()
+-    with open(setup_cfg, "r") as f:
+-        parser.readfp(f)
+-    VCS = parser.get("versioneer", "VCS")  # mandatory
+-
+-    def get(parser, name):
+-        if parser.has_option("versioneer", name):
+-            return parser.get("versioneer", name)
+-        return None
++    root_pth = Path(root)
++    pyproject_toml = root_pth / "pyproject.toml"
++    setup_cfg = root_pth / "setup.cfg"
++    section: Union[Dict[str, Any], configparser.SectionProxy, None] = None
++    if pyproject_toml.exists() and have_tomllib:
++        try:
++            with open(pyproject_toml, "rb") as fobj:
++                pp = tomllib.load(fobj)
++            section = pp["tool"]["versioneer"]
++        except (tomllib.TOMLDecodeError, KeyError) as e:
++            print(f"Failed to load config from {pyproject_toml}: {e}")
++            print("Try to load it from setup.cfg")
++    if not section:
++        parser = configparser.ConfigParser()
++        with open(setup_cfg) as cfg_file:
++            parser.read_file(cfg_file)
++        parser.get("versioneer", "VCS")  # raise error if missing
++
++        section = parser["versioneer"]
++
++    # `cast`` really shouldn't be used, but its simplest for the
++    # common VersioneerConfig users at the moment. We verify against
++    # `None` values elsewhere where it matters
++
+     cfg = VersioneerConfig()
+-    cfg.VCS = VCS
+-    cfg.style = get(parser, "style") or ""
+-    cfg.versionfile_source = get(parser, "versionfile_source")
+-    cfg.versionfile_build = get(parser, "versionfile_build")
+-    cfg.tag_prefix = get(parser, "tag_prefix")
+-    if cfg.tag_prefix in ("''", '""'):
++    cfg.VCS = section["VCS"]
++    cfg.style = section.get("style", "")
++    cfg.versionfile_source = cast(str, section.get("versionfile_source"))
++    cfg.versionfile_build = section.get("versionfile_build")
++    cfg.tag_prefix = cast(str, section.get("tag_prefix"))
++    if cfg.tag_prefix in ("''", '""', None):
+         cfg.tag_prefix = ""
+-    cfg.parentdir_prefix = get(parser, "parentdir_prefix")
+-    cfg.verbose = get(parser, "verbose")
++    cfg.parentdir_prefix = section.get("parentdir_prefix")
++    if isinstance(section, configparser.SectionProxy):
++        # Make sure configparser translates to bool
++        cfg.verbose = section.getboolean("verbose")
++    else:
++        cfg.verbose = section.get("verbose")
++
+     return cfg
+ 
+ 
+@@ -366,37 +447,54 @@ class NotThisMethod(Exception):
+ 
+ 
+ # these dictionaries contain VCS-specific tools
+-LONG_VERSION_PY = {}
+-HANDLERS = {}
++LONG_VERSION_PY: Dict[str, str] = {}
++HANDLERS: Dict[str, Dict[str, Callable]] = {}
+ 
+ 
+-def register_vcs_handler(vcs, method):  # decorator
+-    """Decorator to mark a method as the handler for a particular VCS."""
+-    def decorate(f):
++def register_vcs_handler(vcs: str, method: str) -> Callable:  # decorator
++    """Create decorator to mark a method as the handler of a VCS."""
++
++    def decorate(f: Callable) -> Callable:
+         """Store f in HANDLERS[vcs][method]."""
+-        if vcs not in HANDLERS:
+-            HANDLERS[vcs] = {}
+-        HANDLERS[vcs][method] = f
++        HANDLERS.setdefault(vcs, {})[method] = f
+         return f
++
+     return decorate
+ 
+ 
+-def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False,
+-                env=None):
++def run_command(
++    commands: List[str],
++    args: List[str],
++    cwd: Optional[str] = None,
++    verbose: bool = False,
++    hide_stderr: bool = False,
++    env: Optional[Dict[str, str]] = None,
++) -> Tuple[Optional[str], Optional[int]]:
+     """Call the given command(s)."""
+     assert isinstance(commands, list)
+-    p = None
+-    for c in commands:
++    process = None
++
++    popen_kwargs: Dict[str, Any] = {}
++    if sys.platform == "win32":
++        # This hides the console window if pythonw.exe is used
++        startupinfo = subprocess.STARTUPINFO()
++        startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
++        popen_kwargs["startupinfo"] = startupinfo
++
++    for command in commands:
+         try:
+-            dispcmd = str([c] + args)
++            dispcmd = str([command] + args)
+             # remember shell=False, so use git.cmd on windows, not just git
+-            p = subprocess.Popen([c] + args, cwd=cwd, env=env,
+-                                 stdout=subprocess.PIPE,
+-                                 stderr=(subprocess.PIPE if hide_stderr
+-                                         else None))
++            process = subprocess.Popen(
++                [command] + args,
++                cwd=cwd,
++                env=env,
++                stdout=subprocess.PIPE,
++                stderr=(subprocess.PIPE if hide_stderr else None),
++                **popen_kwargs,
++            )
+             break
+-        except EnvironmentError:
+-            e = sys.exc_info()[1]
++        except OSError as e:
+             if e.errno == errno.ENOENT:
+                 continue
+             if verbose:
+@@ -407,26 +505,27 @@ def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False,
+         if verbose:
+             print("unable to find command, tried %s" % (commands,))
+         return None, None
+-    stdout = p.communicate()[0].strip()
+-    if sys.version_info[0] >= 3:
+-        stdout = stdout.decode()
+-    if p.returncode != 0:
++    stdout = process.communicate()[0].strip().decode()
++    if process.returncode != 0:
+         if verbose:
+             print("unable to run %s (error)" % dispcmd)
+             print("stdout was %s" % stdout)
+-        return None, p.returncode
+-    return stdout, p.returncode
++        return None, process.returncode
++    return stdout, process.returncode
+ 
+ 
+-LONG_VERSION_PY['git'] = '''
++LONG_VERSION_PY[
++    "git"
++] = r'''
+ # This file helps to compute a version number in source trees obtained from
+ # git-archive tarball (such as those provided by githubs download-from-tag
+ # feature). Distribution tarballs (built by setup.py sdist) and build
+ # directories (produced by setup.py build) will contain a much shorter file
+ # that just contains the computed version number.
+ 
+-# This file is released into the public domain. Generated by
+-# versioneer-0.18 (https://github.com/warner/python-versioneer)
++# This file is released into the public domain.
++# Generated by versioneer-0.29
++# https://github.com/python-versioneer/python-versioneer
+ 
+ """Git implementation of _version.py."""
+ 
+@@ -435,9 +534,11 @@ import os
+ import re
+ import subprocess
+ import sys
++from typing import Any, Callable, Dict, List, Optional, Tuple
++import functools
+ 
+ 
+-def get_keywords():
++def get_keywords() -> Dict[str, str]:
+     """Get the keywords needed to look up the version information."""
+     # these strings will be replaced by git during git-archive.
+     # setup.py/versioneer.py will grep for the variable names, so they must
+@@ -453,8 +554,15 @@ def get_keywords():
+ class VersioneerConfig:
+     """Container for Versioneer configuration parameters."""
+ 
++    VCS: str
++    style: str
++    tag_prefix: str
++    parentdir_prefix: str
++    versionfile_source: str
++    verbose: bool
++
+ 
+-def get_config():
++def get_config() -> VersioneerConfig:
+     """Create, populate and return the VersioneerConfig() object."""
+     # these strings are filled in when 'setup.py versioneer' creates
+     # _version.py
+@@ -472,13 +580,13 @@ class NotThisMethod(Exception):
+     """Exception raised if a method is not valid for the current scenario."""
+ 
+ 
+-LONG_VERSION_PY = {}
+-HANDLERS = {}
++LONG_VERSION_PY: Dict[str, str] = {}
++HANDLERS: Dict[str, Dict[str, Callable]] = {}
+ 
+ 
+-def register_vcs_handler(vcs, method):  # decorator
+-    """Decorator to mark a method as the handler for a particular VCS."""
+-    def decorate(f):
++def register_vcs_handler(vcs: str, method: str) -> Callable:  # decorator
++    """Create decorator to mark a method as the handler of a VCS."""
++    def decorate(f: Callable) -> Callable:
+         """Store f in HANDLERS[vcs][method]."""
+         if vcs not in HANDLERS:
+             HANDLERS[vcs] = {}
+@@ -487,22 +595,35 @@ def register_vcs_handler(vcs, method):  # decorator
+     return decorate
+ 
+ 
+-def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False,
+-                env=None):
++def run_command(
++    commands: List[str],
++    args: List[str],
++    cwd: Optional[str] = None,
++    verbose: bool = False,
++    hide_stderr: bool = False,
++    env: Optional[Dict[str, str]] = None,
++) -> Tuple[Optional[str], Optional[int]]:
+     """Call the given command(s)."""
+     assert isinstance(commands, list)
+-    p = None
+-    for c in commands:
++    process = None
++
++    popen_kwargs: Dict[str, Any] = {}
++    if sys.platform == "win32":
++        # This hides the console window if pythonw.exe is used
++        startupinfo = subprocess.STARTUPINFO()
++        startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
++        popen_kwargs["startupinfo"] = startupinfo
++
++    for command in commands:
+         try:
+-            dispcmd = str([c] + args)
++            dispcmd = str([command] + args)
+             # remember shell=False, so use git.cmd on windows, not just git
+-            p = subprocess.Popen([c] + args, cwd=cwd, env=env,
+-                                 stdout=subprocess.PIPE,
+-                                 stderr=(subprocess.PIPE if hide_stderr
+-                                         else None))
++            process = subprocess.Popen([command] + args, cwd=cwd, env=env,
++                                       stdout=subprocess.PIPE,
++                                       stderr=(subprocess.PIPE if hide_stderr
++                                               else None), **popen_kwargs)
+             break
+-        except EnvironmentError:
+-            e = sys.exc_info()[1]
++        except OSError as e:
+             if e.errno == errno.ENOENT:
+                 continue
+             if verbose:
+@@ -513,18 +634,20 @@ def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False,
+         if verbose:
+             print("unable to find command, tried %%s" %% (commands,))
+         return None, None
+-    stdout = p.communicate()[0].strip()
+-    if sys.version_info[0] >= 3:
+-        stdout = stdout.decode()
+-    if p.returncode != 0:
++    stdout = process.communicate()[0].strip().decode()
++    if process.returncode != 0:
+         if verbose:
+             print("unable to run %%s (error)" %% dispcmd)
+             print("stdout was %%s" %% stdout)
+-        return None, p.returncode
+-    return stdout, p.returncode
++        return None, process.returncode
++    return stdout, process.returncode
+ 
+ 
+-def versions_from_parentdir(parentdir_prefix, root, verbose):
++def versions_from_parentdir(
++    parentdir_prefix: str,
++    root: str,
++    verbose: bool,
++) -> Dict[str, Any]:
+     """Try to determine the version from the parent directory name.
+ 
+     Source tarballs conventionally unpack into a directory that includes both
+@@ -533,15 +656,14 @@ def versions_from_parentdir(parentdir_prefix, root, verbose):
+     """
+     rootdirs = []
+ 
+-    for i in range(3):
++    for _ in range(3):
+         dirname = os.path.basename(root)
+         if dirname.startswith(parentdir_prefix):
+             return {"version": dirname[len(parentdir_prefix):],
+                     "full-revisionid": None,
+                     "dirty": False, "error": None, "date": None}
+-        else:
+-            rootdirs.append(root)
+-            root = os.path.dirname(root)  # up a level
++        rootdirs.append(root)
++        root = os.path.dirname(root)  # up a level
+ 
+     if verbose:
+         print("Tried directories %%s but none started with prefix %%s" %%
+@@ -550,41 +672,48 @@ def versions_from_parentdir(parentdir_prefix, root, verbose):
+ 
+ 
+ @register_vcs_handler("git", "get_keywords")
+-def git_get_keywords(versionfile_abs):
++def git_get_keywords(versionfile_abs: str) -> Dict[str, str]:
+     """Extract version information from the given file."""
+     # the code embedded in _version.py can just fetch the value of these
+     # keywords. When used from setup.py, we don't want to import _version.py,
+     # so we do it with a regexp instead. This function is not used from
+     # _version.py.
+-    keywords = {}
++    keywords: Dict[str, str] = {}
+     try:
+-        f = open(versionfile_abs, "r")
+-        for line in f.readlines():
+-            if line.strip().startswith("git_refnames ="):
+-                mo = re.search(r'=\s*"(.*)"', line)
+-                if mo:
+-                    keywords["refnames"] = mo.group(1)
+-            if line.strip().startswith("git_full ="):
+-                mo = re.search(r'=\s*"(.*)"', line)
+-                if mo:
+-                    keywords["full"] = mo.group(1)
+-            if line.strip().startswith("git_date ="):
+-                mo = re.search(r'=\s*"(.*)"', line)
+-                if mo:
+-                    keywords["date"] = mo.group(1)
+-        f.close()
+-    except EnvironmentError:
++        with open(versionfile_abs, "r") as fobj:
++            for line in fobj:
++                if line.strip().startswith("git_refnames ="):
++                    mo = re.search(r'=\s*"(.*)"', line)
++                    if mo:
++                        keywords["refnames"] = mo.group(1)
++                if line.strip().startswith("git_full ="):
++                    mo = re.search(r'=\s*"(.*)"', line)
++                    if mo:
++                        keywords["full"] = mo.group(1)
++                if line.strip().startswith("git_date ="):
++                    mo = re.search(r'=\s*"(.*)"', line)
++                    if mo:
++                        keywords["date"] = mo.group(1)
++    except OSError:
+         pass
+     return keywords
+ 
+ 
+ @register_vcs_handler("git", "keywords")
+-def git_versions_from_keywords(keywords, tag_prefix, verbose):
++def git_versions_from_keywords(
++    keywords: Dict[str, str],
++    tag_prefix: str,
++    verbose: bool,
++) -> Dict[str, Any]:
+     """Get version information from git keywords."""
+-    if not keywords:
+-        raise NotThisMethod("no keywords at all, weird")
++    if "refnames" not in keywords:
++        raise NotThisMethod("Short version file found")
+     date = keywords.get("date")
+     if date is not None:
++        # Use only the last line.  Previous lines may contain GPG signature
++        # information.
++        date = date.splitlines()[-1]
++
+         # git-2.2.0 added "%%cI", which expands to an ISO-8601 -compliant
+         # datestamp. However we prefer "%%ci" (which expands to an "ISO-8601
+         # -like" string, which we must then edit to make compliant), because
+@@ -597,11 +726,11 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose):
+         if verbose:
+             print("keywords are unexpanded, not using")
+         raise NotThisMethod("unexpanded keywords, not a git-archive tarball")
+-    refs = set([r.strip() for r in refnames.strip("()").split(",")])
++    refs = {r.strip() for r in refnames.strip("()").split(",")}
+     # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of
+     # just "foo-1.0". If we see a "tag: " prefix, prefer those.
+     TAG = "tag: "
+-    tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)])
++    tags = {r[len(TAG):] for r in refs if r.startswith(TAG)}
+     if not tags:
+         # Either we're using git < 1.8.3, or there really are no tags. We use
+         # a heuristic: assume all version tags have a digit. The old git %%d
+@@ -610,7 +739,7 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose):
+         # between branches and tags. By ignoring refnames without digits, we
+         # filter out many common branch names like "release" and
+         # "stabilization", as well as "HEAD" and "master".
+-        tags = set([r for r in refs if re.search(r'\d', r)])
++        tags = {r for r in refs if re.search(r'\d', r)}
+         if verbose:
+             print("discarding '%%s', no digits" %% ",".join(refs - tags))
+     if verbose:
+@@ -619,6 +748,11 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose):
+         # sorting will prefer e.g. "2.0" over "2.0rc1"
+         if ref.startswith(tag_prefix):
+             r = ref[len(tag_prefix):]
++            # Filter out refs that exactly match prefix or that don't start
++            # with a number once the prefix is stripped (mostly a concern
++            # when prefix is '')
++            if not re.match(r'\d', r):
++                continue
+             if verbose:
+                 print("picking %%s" %% r)
+             return {"version": r,
+@@ -634,7 +768,12 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose):
+ 
+ 
+ @register_vcs_handler("git", "pieces_from_vcs")
+-def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
++def git_pieces_from_vcs(
++    tag_prefix: str,
++    root: str,
++    verbose: bool,
++    runner: Callable = run_command
++) -> Dict[str, Any]:
+     """Get version from 'git describe' in the root of the source tree.
+ 
+     This only gets called if the git-archive 'subst' keywords were *not*
+@@ -645,8 +784,15 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
+     if sys.platform == "win32":
+         GITS = ["git.cmd", "git.exe"]
+ 
+-    out, rc = run_command(GITS, ["rev-parse", "--git-dir"], cwd=root,
+-                          hide_stderr=True)
++    # GIT_DIR can interfere with correct operation of Versioneer.
++    # It may be intended to be passed to the Versioneer-versioned project,
++    # but that should not change where we get our version from.
++    env = os.environ.copy()
++    env.pop("GIT_DIR", None)
++    runner = functools.partial(runner, env=env)
++
++    _, rc = runner(GITS, ["rev-parse", "--git-dir"], cwd=root,
++                   hide_stderr=not verbose)
+     if rc != 0:
+         if verbose:
+             print("Directory %%s not under git control" %% root)
+@@ -654,24 +800,57 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
+ 
+     # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty]
+     # if there isn't one, this yields HEX[-dirty] (no NUM)
+-    describe_out, rc = run_command(GITS, ["describe", "--tags", "--dirty",
+-                                          "--always", "--long",
+-                                          "--match", "%%s*" %% tag_prefix],
+-                                   cwd=root)
++    describe_out, rc = runner(GITS, [
++        "describe", "--tags", "--dirty", "--always", "--long",
++        "--match", f"{tag_prefix}[[:digit:]]*"
++    ], cwd=root)
+     # --long was added in git-1.5.5
+     if describe_out is None:
+         raise NotThisMethod("'git describe' failed")
+     describe_out = describe_out.strip()
+-    full_out, rc = run_command(GITS, ["rev-parse", "HEAD"], cwd=root)
++    full_out, rc = runner(GITS, ["rev-parse", "HEAD"], cwd=root)
+     if full_out is None:
+         raise NotThisMethod("'git rev-parse' failed")
+     full_out = full_out.strip()
+ 
+-    pieces = {}
++    pieces: Dict[str, Any] = {}
+     pieces["long"] = full_out
+     pieces["short"] = full_out[:7]  # maybe improved later
+     pieces["error"] = None
+ 
++    branch_name, rc = runner(GITS, ["rev-parse", "--abbrev-ref", "HEAD"],
++                             cwd=root)
++    # --abbrev-ref was added in git-1.6.3
++    if rc != 0 or branch_name is None:
++        raise NotThisMethod("'git rev-parse --abbrev-ref' returned error")
++    branch_name = branch_name.strip()
++
++    if branch_name == "HEAD":
++        # If we aren't exactly on a branch, pick a branch which represents
++        # the current commit. If all else fails, we are on a branchless
++        # commit.
++        branches, rc = runner(GITS, ["branch", "--contains"], cwd=root)
++        # --contains was added in git-1.5.4
++        if rc != 0 or branches is None:
++            raise NotThisMethod("'git branch --contains' returned error")
++        branches = branches.split("\n")
++
++        # Remove the first line if we're running detached
++        if "(" in branches[0]:
++            branches.pop(0)
++
++        # Strip off the leading "* " from the list of branches.
++        branches = [branch[2:] for branch in branches]
++        if "master" in branches:
++            branch_name = "master"
++        elif not branches:
++            branch_name = None
++        else:
++            # Pick the first branch that is returned. Good or bad.
++            branch_name = branches[0]
++
++    pieces["branch"] = branch_name
++
+     # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty]
+     # TAG might have hyphens.
+     git_describe = describe_out
+@@ -688,7 +867,7 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
+         # TAG-NUM-gHEX
+         mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe)
+         if not mo:
+-            # unparseable. Maybe git-describe is misbehaving?
++            # unparsable. Maybe git-describe is misbehaving?
+             pieces["error"] = ("unable to parse git-describe output: '%%s'"
+                                %% describe_out)
+             return pieces
+@@ -713,26 +892,27 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
+     else:
+         # HEX: no tags
+         pieces["closest-tag"] = None
+-        count_out, rc = run_command(GITS, ["rev-list", "HEAD", "--count"],
+-                                    cwd=root)
+-        pieces["distance"] = int(count_out)  # total number of commits
++        out, rc = runner(GITS, ["rev-list", "HEAD", "--left-right"], cwd=root)
++        pieces["distance"] = len(out.split())  # total number of commits
+ 
+     # commit date: see ISO-8601 comment in git_versions_from_keywords()
+-    date = run_command(GITS, ["show", "-s", "--format=%%ci", "HEAD"],
+-                       cwd=root)[0].strip()
++    date = runner(GITS, ["show", "-s", "--format=%%ci", "HEAD"], cwd=root)[0].strip()
++    # Use only the last line.  Previous lines may contain GPG signature
++    # information.
++    date = date.splitlines()[-1]
+     pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1)
+ 
+     return pieces
+ 
+ 
+-def plus_or_dot(pieces):
++def plus_or_dot(pieces: Dict[str, Any]) -> str:
+     """Return a + if we don't already have one, else return a ."""
+     if "+" in pieces.get("closest-tag", ""):
+         return "."
+     return "+"
+ 
+ 
+-def render_pep440(pieces):
++def render_pep440(pieces: Dict[str, Any]) -> str:
+     """Build up version string, with post-release "local version identifier".
+ 
+     Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you
+@@ -757,23 +937,71 @@ def render_pep440(pieces):
+     return rendered
+ 
+ 
+-def render_pep440_pre(pieces):
+-    """TAG[.post.devDISTANCE] -- No -dirty.
++def render_pep440_branch(pieces: Dict[str, Any]) -> str:
++    """TAG[[.dev0]+DISTANCE.gHEX[.dirty]] .
++
++    The ".dev0" means not master branch. Note that .dev0 sorts backwards
++    (a feature branch will appear "older" than the master branch).
+ 
+     Exceptions:
+-    1: no tags. 0.post.devDISTANCE
++    1: no tags. 0[.dev0]+untagged.DISTANCE.gHEX[.dirty]
+     """
+     if pieces["closest-tag"]:
+         rendered = pieces["closest-tag"]
++        if pieces["distance"] or pieces["dirty"]:
++            if pieces["branch"] != "master":
++                rendered += ".dev0"
++            rendered += plus_or_dot(pieces)
++            rendered += "%%d.g%%s" %% (pieces["distance"], pieces["short"])
++            if pieces["dirty"]:
++                rendered += ".dirty"
++    else:
++        # exception #1
++        rendered = "0"
++        if pieces["branch"] != "master":
++            rendered += ".dev0"
++        rendered += "+untagged.%%d.g%%s" %% (pieces["distance"],
++                                          pieces["short"])
++        if pieces["dirty"]:
++            rendered += ".dirty"
++    return rendered
++
++
++def pep440_split_post(ver: str) -> Tuple[str, Optional[int]]:
++    """Split pep440 version string at the post-release segment.
++
++    Returns the release segments before the post-release and the
++    post-release version number (or -1 if no post-release segment is present).
++    """
++    vc = str.split(ver, ".post")
++    return vc[0], int(vc[1] or 0) if len(vc) == 2 else None
++
++
++def render_pep440_pre(pieces: Dict[str, Any]) -> str:
++    """TAG[.postN.devDISTANCE] -- No -dirty.
++
++    Exceptions:
++    1: no tags. 0.post0.devDISTANCE
++    """
++    if pieces["closest-tag"]:
+         if pieces["distance"]:
+-            rendered += ".post.dev%%d" %% pieces["distance"]
++            # update the post release segment
++            tag_version, post_version = pep440_split_post(pieces["closest-tag"])
++            rendered = tag_version
++            if post_version is not None:
++                rendered += ".post%%d.dev%%d" %% (post_version + 1, pieces["distance"])
++            else:
++                rendered += ".post0.dev%%d" %% (pieces["distance"])
++        else:
++            # no commits, use the tag as the version
++            rendered = pieces["closest-tag"]
+     else:
+         # exception #1
+-        rendered = "0.post.dev%%d" %% pieces["distance"]
++        rendered = "0.post0.dev%%d" %% pieces["distance"]
+     return rendered
+ 
+ 
+-def render_pep440_post(pieces):
++def render_pep440_post(pieces: Dict[str, Any]) -> str:
+     """TAG[.postDISTANCE[.dev0]+gHEX] .
+ 
+     The ".dev0" means dirty. Note that .dev0 sorts backwards
+@@ -800,12 +1028,41 @@ def render_pep440_post(pieces):
+     return rendered
+ 
+ 
+-def render_pep440_old(pieces):
++def render_pep440_post_branch(pieces: Dict[str, Any]) -> str:
++    """TAG[.postDISTANCE[.dev0]+gHEX[.dirty]] .
++
++    The ".dev0" means not master branch.
++
++    Exceptions:
++    1: no tags. 0.postDISTANCE[.dev0]+gHEX[.dirty]
++    """
++    if pieces["closest-tag"]:
++        rendered = pieces["closest-tag"]
++        if pieces["distance"] or pieces["dirty"]:
++            rendered += ".post%%d" %% pieces["distance"]
++            if pieces["branch"] != "master":
++                rendered += ".dev0"
++            rendered += plus_or_dot(pieces)
++            rendered += "g%%s" %% pieces["short"]
++            if pieces["dirty"]:
++                rendered += ".dirty"
++    else:
++        # exception #1
++        rendered = "0.post%%d" %% pieces["distance"]
++        if pieces["branch"] != "master":
++            rendered += ".dev0"
++        rendered += "+g%%s" %% pieces["short"]
++        if pieces["dirty"]:
++            rendered += ".dirty"
++    return rendered
++
++
++def render_pep440_old(pieces: Dict[str, Any]) -> str:
+     """TAG[.postDISTANCE[.dev0]] .
+ 
+     The ".dev0" means dirty.
+ 
+-    Eexceptions:
++    Exceptions:
+     1: no tags. 0.postDISTANCE[.dev0]
+     """
+     if pieces["closest-tag"]:
+@@ -822,7 +1079,7 @@ def render_pep440_old(pieces):
+     return rendered
+ 
+ 
+-def render_git_describe(pieces):
++def render_git_describe(pieces: Dict[str, Any]) -> str:
+     """TAG[-DISTANCE-gHEX][-dirty].
+ 
+     Like 'git describe --tags --dirty --always'.
+@@ -842,7 +1099,7 @@ def render_git_describe(pieces):
+     return rendered
+ 
+ 
+-def render_git_describe_long(pieces):
++def render_git_describe_long(pieces: Dict[str, Any]) -> str:
+     """TAG-DISTANCE-gHEX[-dirty].
+ 
+     Like 'git describe --tags --dirty --always -long'.
+@@ -862,7 +1119,7 @@ def render_git_describe_long(pieces):
+     return rendered
+ 
+ 
+-def render(pieces, style):
++def render(pieces: Dict[str, Any], style: str) -> Dict[str, Any]:
+     """Render the given version pieces into the requested style."""
+     if pieces["error"]:
+         return {"version": "unknown",
+@@ -876,10 +1133,14 @@ def render(pieces, style):
+ 
+     if style == "pep440":
+         rendered = render_pep440(pieces)
++    elif style == "pep440-branch":
++        rendered = render_pep440_branch(pieces)
+     elif style == "pep440-pre":
+         rendered = render_pep440_pre(pieces)
+     elif style == "pep440-post":
+         rendered = render_pep440_post(pieces)
++    elif style == "pep440-post-branch":
++        rendered = render_pep440_post_branch(pieces)
+     elif style == "pep440-old":
+         rendered = render_pep440_old(pieces)
+     elif style == "git-describe":
+@@ -894,7 +1155,7 @@ def render(pieces, style):
+             "date": pieces.get("date")}
+ 
+ 
+-def get_versions():
++def get_versions() -> Dict[str, Any]:
+     """Get version information or return default if unable to do so."""
+     # I am in _version.py, which lives at ROOT/VERSIONFILE_SOURCE. If we have
+     # __file__, we can work backwards from there to the root. Some
+@@ -915,7 +1176,7 @@ def get_versions():
+         # versionfile_source is the relative path from the top of the source
+         # tree (where the .git directory might live) to this file. Invert
+         # this to find the root from __file__.
+-        for i in cfg.versionfile_source.split('/'):
++        for _ in cfg.versionfile_source.split('/'):
+             root = os.path.dirname(root)
+     except NameError:
+         return {"version": "0+unknown", "full-revisionid": None,
+@@ -942,41 +1203,48 @@ def get_versions():
+ 
+ 
+ @register_vcs_handler("git", "get_keywords")
+-def git_get_keywords(versionfile_abs):
++def git_get_keywords(versionfile_abs: str) -> Dict[str, str]:
+     """Extract version information from the given file."""
+     # the code embedded in _version.py can just fetch the value of these
+     # keywords. When used from setup.py, we don't want to import _version.py,
+     # so we do it with a regexp instead. This function is not used from
+     # _version.py.
+-    keywords = {}
++    keywords: Dict[str, str] = {}
+     try:
+-        f = open(versionfile_abs, "r")
+-        for line in f.readlines():
+-            if line.strip().startswith("git_refnames ="):
+-                mo = re.search(r'=\s*"(.*)"', line)
+-                if mo:
+-                    keywords["refnames"] = mo.group(1)
+-            if line.strip().startswith("git_full ="):
+-                mo = re.search(r'=\s*"(.*)"', line)
+-                if mo:
+-                    keywords["full"] = mo.group(1)
+-            if line.strip().startswith("git_date ="):
+-                mo = re.search(r'=\s*"(.*)"', line)
+-                if mo:
+-                    keywords["date"] = mo.group(1)
+-        f.close()
+-    except EnvironmentError:
++        with open(versionfile_abs, "r") as fobj:
++            for line in fobj:
++                if line.strip().startswith("git_refnames ="):
++                    mo = re.search(r'=\s*"(.*)"', line)
++                    if mo:
++                        keywords["refnames"] = mo.group(1)
++                if line.strip().startswith("git_full ="):
++                    mo = re.search(r'=\s*"(.*)"', line)
++                    if mo:
++                        keywords["full"] = mo.group(1)
++                if line.strip().startswith("git_date ="):
++                    mo = re.search(r'=\s*"(.*)"', line)
++                    if mo:
++                        keywords["date"] = mo.group(1)
++    except OSError:
+         pass
+     return keywords
+ 
+ 
+ @register_vcs_handler("git", "keywords")
+-def git_versions_from_keywords(keywords, tag_prefix, verbose):
++def git_versions_from_keywords(
++    keywords: Dict[str, str],
++    tag_prefix: str,
++    verbose: bool,
++) -> Dict[str, Any]:
+     """Get version information from git keywords."""
+-    if not keywords:
+-        raise NotThisMethod("no keywords at all, weird")
++    if "refnames" not in keywords:
++        raise NotThisMethod("Short version file found")
+     date = keywords.get("date")
+     if date is not None:
++        # Use only the last line.  Previous lines may contain GPG signature
++        # information.
++        date = date.splitlines()[-1]
++
+         # git-2.2.0 added "%cI", which expands to an ISO-8601 -compliant
+         # datestamp. However we prefer "%ci" (which expands to an "ISO-8601
+         # -like" string, which we must then edit to make compliant), because
+@@ -989,11 +1257,11 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose):
+         if verbose:
+             print("keywords are unexpanded, not using")
+         raise NotThisMethod("unexpanded keywords, not a git-archive tarball")
+-    refs = set([r.strip() for r in refnames.strip("()").split(",")])
++    refs = {r.strip() for r in refnames.strip("()").split(",")}
+     # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of
+     # just "foo-1.0". If we see a "tag: " prefix, prefer those.
+     TAG = "tag: "
+-    tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)])
++    tags = {r[len(TAG) :] for r in refs if r.startswith(TAG)}
+     if not tags:
+         # Either we're using git < 1.8.3, or there really are no tags. We use
+         # a heuristic: assume all version tags have a digit. The old git %d
+@@ -1002,7 +1270,7 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose):
+         # between branches and tags. By ignoring refnames without digits, we
+         # filter out many common branch names like "release" and
+         # "stabilization", as well as "HEAD" and "master".
+-        tags = set([r for r in refs if re.search(r'\d', r)])
++        tags = {r for r in refs if re.search(r"\d", r)}
+         if verbose:
+             print("discarding '%s', no digits" % ",".join(refs - tags))
+     if verbose:
+@@ -1010,23 +1278,37 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose):
+     for ref in sorted(tags):
+         # sorting will prefer e.g. "2.0" over "2.0rc1"
+         if ref.startswith(tag_prefix):
+-            r = ref[len(tag_prefix):]
++            r = ref[len(tag_prefix) :]
++            # Filter out refs that exactly match prefix or that don't start
++            # with a number once the prefix is stripped (mostly a concern
++            # when prefix is '')
++            if not re.match(r"\d", r):
++                continue
+             if verbose:
+                 print("picking %s" % r)
+-            return {"version": r,
+-                    "full-revisionid": keywords["full"].strip(),
+-                    "dirty": False, "error": None,
+-                    "date": date}
++            return {
++                "version": r,
++                "full-revisionid": keywords["full"].strip(),
++                "dirty": False,
++                "error": None,
++                "date": date,
++            }
+     # no suitable tags, so version is "0+unknown", but full hex is still there
+     if verbose:
+         print("no suitable tags, using unknown + full revision id")
+-    return {"version": "0+unknown",
+-            "full-revisionid": keywords["full"].strip(),
+-            "dirty": False, "error": "no suitable tags", "date": None}
++    return {
++        "version": "0+unknown",
++        "full-revisionid": keywords["full"].strip(),
++        "dirty": False,
++        "error": "no suitable tags",
++        "date": None,
++    }
+ 
+ 
+ @register_vcs_handler("git", "pieces_from_vcs")
+-def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
++def git_pieces_from_vcs(
++    tag_prefix: str, root: str, verbose: bool, runner: Callable = run_command
++) -> Dict[str, Any]:
+     """Get version from 'git describe' in the root of the source tree.
+ 
+     This only gets called if the git-archive 'subst' keywords were *not*
+@@ -1037,8 +1319,14 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
+     if sys.platform == "win32":
+         GITS = ["git.cmd", "git.exe"]
+ 
+-    out, rc = run_command(GITS, ["rev-parse", "--git-dir"], cwd=root,
+-                          hide_stderr=True)
++    # GIT_DIR can interfere with correct operation of Versioneer.
++    # It may be intended to be passed to the Versioneer-versioned project,
++    # but that should not change where we get our version from.
++    env = os.environ.copy()
++    env.pop("GIT_DIR", None)
++    runner = functools.partial(runner, env=env)
++
++    _, rc = runner(GITS, ["rev-parse", "--git-dir"], cwd=root, hide_stderr=not verbose)
+     if rc != 0:
+         if verbose:
+             print("Directory %s not under git control" % root)
+@@ -1046,24 +1334,65 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
+ 
+     # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty]
+     # if there isn't one, this yields HEX[-dirty] (no NUM)
+-    describe_out, rc = run_command(GITS, ["describe", "--tags", "--dirty",
+-                                          "--always", "--long",
+-                                          "--match", "%s*" % tag_prefix],
+-                                   cwd=root)
++    describe_out, rc = runner(
++        GITS,
++        [
++            "describe",
++            "--tags",
++            "--dirty",
++            "--always",
++            "--long",
++            "--match",
++            f"{tag_prefix}[[:digit:]]*",
++        ],
++        cwd=root,
++    )
+     # --long was added in git-1.5.5
+     if describe_out is None:
+         raise NotThisMethod("'git describe' failed")
+     describe_out = describe_out.strip()
+-    full_out, rc = run_command(GITS, ["rev-parse", "HEAD"], cwd=root)
++    full_out, rc = runner(GITS, ["rev-parse", "HEAD"], cwd=root)
+     if full_out is None:
+         raise NotThisMethod("'git rev-parse' failed")
+     full_out = full_out.strip()
+ 
+-    pieces = {}
++    pieces: Dict[str, Any] = {}
+     pieces["long"] = full_out
+     pieces["short"] = full_out[:7]  # maybe improved later
+     pieces["error"] = None
+ 
++    branch_name, rc = runner(GITS, ["rev-parse", "--abbrev-ref", "HEAD"], cwd=root)
++    # --abbrev-ref was added in git-1.6.3
++    if rc != 0 or branch_name is None:
++        raise NotThisMethod("'git rev-parse --abbrev-ref' returned error")
++    branch_name = branch_name.strip()
++
++    if branch_name == "HEAD":
++        # If we aren't exactly on a branch, pick a branch which represents
++        # the current commit. If all else fails, we are on a branchless
++        # commit.
++        branches, rc = runner(GITS, ["branch", "--contains"], cwd=root)
++        # --contains was added in git-1.5.4
++        if rc != 0 or branches is None:
++            raise NotThisMethod("'git branch --contains' returned error")
++        branches = branches.split("\n")
++
++        # Remove the first line if we're running detached
++        if "(" in branches[0]:
++            branches.pop(0)
++
++        # Strip off the leading "* " from the list of branches.
++        branches = [branch[2:] for branch in branches]
++        if "master" in branches:
++            branch_name = "master"
++        elif not branches:
++            branch_name = None
++        else:
++            # Pick the first branch that is returned. Good or bad.
++            branch_name = branches[0]
++
++    pieces["branch"] = branch_name
++
+     # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty]
+     # TAG might have hyphens.
+     git_describe = describe_out
+@@ -1072,17 +1401,16 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
+     dirty = git_describe.endswith("-dirty")
+     pieces["dirty"] = dirty
+     if dirty:
+-        git_describe = git_describe[:git_describe.rindex("-dirty")]
++        git_describe = git_describe[: git_describe.rindex("-dirty")]
+ 
+     # now we have TAG-NUM-gHEX or HEX
+ 
+     if "-" in git_describe:
+         # TAG-NUM-gHEX
+-        mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe)
++        mo = re.search(r"^(.+)-(\d+)-g([0-9a-f]+)$", git_describe)
+         if not mo:
+-            # unparseable. Maybe git-describe is misbehaving?
+-            pieces["error"] = ("unable to parse git-describe output: '%s'"
+-                               % describe_out)
++            # unparsable. Maybe git-describe is misbehaving?
++            pieces["error"] = "unable to parse git-describe output: '%s'" % describe_out
+             return pieces
+ 
+         # tag
+@@ -1091,10 +1419,12 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
+             if verbose:
+                 fmt = "tag '%s' doesn't start with prefix '%s'"
+                 print(fmt % (full_tag, tag_prefix))
+-            pieces["error"] = ("tag '%s' doesn't start with prefix '%s'"
+-                               % (full_tag, tag_prefix))
++            pieces["error"] = "tag '%s' doesn't start with prefix '%s'" % (
++                full_tag,
++                tag_prefix,
++            )
+             return pieces
+-        pieces["closest-tag"] = full_tag[len(tag_prefix):]
++        pieces["closest-tag"] = full_tag[len(tag_prefix) :]
+ 
+         # distance: number of commits since tag
+         pieces["distance"] = int(mo.group(2))
+@@ -1105,19 +1435,20 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
+     else:
+         # HEX: no tags
+         pieces["closest-tag"] = None
+-        count_out, rc = run_command(GITS, ["rev-list", "HEAD", "--count"],
+-                                    cwd=root)
+-        pieces["distance"] = int(count_out)  # total number of commits
++        out, rc = runner(GITS, ["rev-list", "HEAD", "--left-right"], cwd=root)
++        pieces["distance"] = len(out.split())  # total number of commits
+ 
+     # commit date: see ISO-8601 comment in git_versions_from_keywords()
+-    date = run_command(GITS, ["show", "-s", "--format=%ci", "HEAD"],
+-                       cwd=root)[0].strip()
++    date = runner(GITS, ["show", "-s", "--format=%ci", "HEAD"], cwd=root)[0].strip()
++    # Use only the last line.  Previous lines may contain GPG signature
++    # information.
++    date = date.splitlines()[-1]
+     pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1)
+ 
+     return pieces
+ 
+ 
+-def do_vcs_install(manifest_in, versionfile_source, ipy):
++def do_vcs_install(versionfile_source: str, ipy: Optional[str]) -> None:
+     """Git-specific installation logic for Versioneer.
+ 
+     For Git, this means creating/changing .gitattributes to mark _version.py
+@@ -1126,36 +1457,40 @@ def do_vcs_install(manifest_in, versionfile_source, ipy):
+     GITS = ["git"]
+     if sys.platform == "win32":
+         GITS = ["git.cmd", "git.exe"]
+-    files = [manifest_in, versionfile_source]
++    files = [versionfile_source]
+     if ipy:
+         files.append(ipy)
+-    try:
+-        me = __file__
+-        if me.endswith(".pyc") or me.endswith(".pyo"):
+-            me = os.path.splitext(me)[0] + ".py"
+-        versioneer_file = os.path.relpath(me)
+-    except NameError:
+-        versioneer_file = "versioneer.py"
+-    files.append(versioneer_file)
++    if "VERSIONEER_PEP518" not in globals():
++        try:
++            my_path = __file__
++            if my_path.endswith((".pyc", ".pyo")):
++                my_path = os.path.splitext(my_path)[0] + ".py"
++            versioneer_file = os.path.relpath(my_path)
++        except NameError:
++            versioneer_file = "versioneer.py"
++        files.append(versioneer_file)
+     present = False
+     try:
+-        f = open(".gitattributes", "r")
+-        for line in f.readlines():
+-            if line.strip().startswith(versionfile_source):
+-                if "export-subst" in line.strip().split()[1:]:
+-                    present = True
+-        f.close()
+-    except EnvironmentError:
++        with open(".gitattributes", "r") as fobj:
++            for line in fobj:
++                if line.strip().startswith(versionfile_source):
++                    if "export-subst" in line.strip().split()[1:]:
++                        present = True
++                        break
++    except OSError:
+         pass
+     if not present:
+-        f = open(".gitattributes", "a+")
+-        f.write("%s export-subst\n" % versionfile_source)
+-        f.close()
++        with open(".gitattributes", "a+") as fobj:
++            fobj.write(f"{versionfile_source} export-subst\n")
+         files.append(".gitattributes")
+     run_command(GITS, ["add", "--"] + files)
+ 
+ 
+-def versions_from_parentdir(parentdir_prefix, root, verbose):
++def versions_from_parentdir(
++    parentdir_prefix: str,
++    root: str,
++    verbose: bool,
++) -> Dict[str, Any]:
+     """Try to determine the version from the parent directory name.
+ 
+     Source tarballs conventionally unpack into a directory that includes both
+@@ -1164,24 +1499,29 @@ def versions_from_parentdir(parentdir_prefix, root, verbose):
+     """
+     rootdirs = []
+ 
+-    for i in range(3):
++    for _ in range(3):
+         dirname = os.path.basename(root)
+         if dirname.startswith(parentdir_prefix):
+-            return {"version": dirname[len(parentdir_prefix):],
+-                    "full-revisionid": None,
+-                    "dirty": False, "error": None, "date": None}
+-        else:
+-            rootdirs.append(root)
+-            root = os.path.dirname(root)  # up a level
++            return {
++                "version": dirname[len(parentdir_prefix) :],
++                "full-revisionid": None,
++                "dirty": False,
++                "error": None,
++                "date": None,
++            }
++        rootdirs.append(root)
++        root = os.path.dirname(root)  # up a level
+ 
+     if verbose:
+-        print("Tried directories %s but none started with prefix %s" %
+-              (str(rootdirs), parentdir_prefix))
++        print(
++            "Tried directories %s but none started with prefix %s"
++            % (str(rootdirs), parentdir_prefix)
++        )
+     raise NotThisMethod("rootdir doesn't start with parentdir_prefix")
+ 
+ 
+ SHORT_VERSION_PY = """
+-# This file was generated by 'versioneer.py' (0.18) from
++# This file was generated by 'versioneer.py' (0.29) from
+ # revision-control system data, or from the parent directory name of an
+ # unpacked source archive. Distribution tarballs contain a pre-generated copy
+ # of this file.
+@@ -1198,42 +1538,42 @@ def get_versions():
+ """
+ 
+ 
+-def versions_from_file(filename):
++def versions_from_file(filename: str) -> Dict[str, Any]:
+     """Try to determine the version from _version.py if present."""
+     try:
+         with open(filename) as f:
+             contents = f.read()
+-    except EnvironmentError:
++    except OSError:
+         raise NotThisMethod("unable to read _version.py")
+-    mo = re.search(r"version_json = '''\n(.*)'''  # END VERSION_JSON",
+-                   contents, re.M | re.S)
++    mo = re.search(
++        r"version_json = '''\n(.*)'''  # END VERSION_JSON", contents, re.M | re.S
++    )
+     if not mo:
+-        mo = re.search(r"version_json = '''\r\n(.*)'''  # END VERSION_JSON",
+-                       contents, re.M | re.S)
++        mo = re.search(
++            r"version_json = '''\r\n(.*)'''  # END VERSION_JSON", contents, re.M | re.S
++        )
+     if not mo:
+         raise NotThisMethod("no version_json in _version.py")
+     return json.loads(mo.group(1))
+ 
+ 
+-def write_to_version_file(filename, versions):
++def write_to_version_file(filename: str, versions: Dict[str, Any]) -> None:
+     """Write the given version number to the given _version.py file."""
+-    os.unlink(filename)
+-    contents = json.dumps(versions, sort_keys=True,
+-                          indent=1, separators=(",", ": "))
++    contents = json.dumps(versions, sort_keys=True, indent=1, separators=(",", ": "))
+     with open(filename, "w") as f:
+         f.write(SHORT_VERSION_PY % contents)
+ 
+     print("set %s to '%s'" % (filename, versions["version"]))
+ 
+ 
+-def plus_or_dot(pieces):
++def plus_or_dot(pieces: Dict[str, Any]) -> str:
+     """Return a + if we don't already have one, else return a ."""
+     if "+" in pieces.get("closest-tag", ""):
+         return "."
+     return "+"
+ 
+ 
+-def render_pep440(pieces):
++def render_pep440(pieces: Dict[str, Any]) -> str:
+     """Build up version string, with post-release "local version identifier".
+ 
+     Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you
+@@ -1251,30 +1591,76 @@ def render_pep440(pieces):
+                 rendered += ".dirty"
+     else:
+         # exception #1
+-        rendered = "0+untagged.%d.g%s" % (pieces["distance"],
+-                                          pieces["short"])
++        rendered = "0+untagged.%d.g%s" % (pieces["distance"], pieces["short"])
+         if pieces["dirty"]:
+             rendered += ".dirty"
+     return rendered
+ 
+ 
+-def render_pep440_pre(pieces):
+-    """TAG[.post.devDISTANCE] -- No -dirty.
++def render_pep440_branch(pieces: Dict[str, Any]) -> str:
++    """TAG[[.dev0]+DISTANCE.gHEX[.dirty]] .
++
++    The ".dev0" means not master branch. Note that .dev0 sorts backwards
++    (a feature branch will appear "older" than the master branch).
+ 
+     Exceptions:
+-    1: no tags. 0.post.devDISTANCE
++    1: no tags. 0[.dev0]+untagged.DISTANCE.gHEX[.dirty]
+     """
+     if pieces["closest-tag"]:
+         rendered = pieces["closest-tag"]
++        if pieces["distance"] or pieces["dirty"]:
++            if pieces["branch"] != "master":
++                rendered += ".dev0"
++            rendered += plus_or_dot(pieces)
++            rendered += "%d.g%s" % (pieces["distance"], pieces["short"])
++            if pieces["dirty"]:
++                rendered += ".dirty"
++    else:
++        # exception #1
++        rendered = "0"
++        if pieces["branch"] != "master":
++            rendered += ".dev0"
++        rendered += "+untagged.%d.g%s" % (pieces["distance"], pieces["short"])
++        if pieces["dirty"]:
++            rendered += ".dirty"
++    return rendered
++
++
++def pep440_split_post(ver: str) -> Tuple[str, Optional[int]]:
++    """Split pep440 version string at the post-release segment.
++
++    Returns the release segments before the post-release and the
++    post-release version number (or -1 if no post-release segment is present).
++    """
++    vc = str.split(ver, ".post")
++    return vc[0], int(vc[1] or 0) if len(vc) == 2 else None
++
++
++def render_pep440_pre(pieces: Dict[str, Any]) -> str:
++    """TAG[.postN.devDISTANCE] -- No -dirty.
++
++    Exceptions:
++    1: no tags. 0.post0.devDISTANCE
++    """
++    if pieces["closest-tag"]:
+         if pieces["distance"]:
+-            rendered += ".post.dev%d" % pieces["distance"]
++            # update the post release segment
++            tag_version, post_version = pep440_split_post(pieces["closest-tag"])
++            rendered = tag_version
++            if post_version is not None:
++                rendered += ".post%d.dev%d" % (post_version + 1, pieces["distance"])
++            else:
++                rendered += ".post0.dev%d" % (pieces["distance"])
++        else:
++            # no commits, use the tag as the version
++            rendered = pieces["closest-tag"]
+     else:
+         # exception #1
+-        rendered = "0.post.dev%d" % pieces["distance"]
++        rendered = "0.post0.dev%d" % pieces["distance"]
+     return rendered
+ 
+ 
+-def render_pep440_post(pieces):
++def render_pep440_post(pieces: Dict[str, Any]) -> str:
+     """TAG[.postDISTANCE[.dev0]+gHEX] .
+ 
+     The ".dev0" means dirty. Note that .dev0 sorts backwards
+@@ -1301,12 +1687,41 @@ def render_pep440_post(pieces):
+     return rendered
+ 
+ 
+-def render_pep440_old(pieces):
++def render_pep440_post_branch(pieces: Dict[str, Any]) -> str:
++    """TAG[.postDISTANCE[.dev0]+gHEX[.dirty]] .
++
++    The ".dev0" means not master branch.
++
++    Exceptions:
++    1: no tags. 0.postDISTANCE[.dev0]+gHEX[.dirty]
++    """
++    if pieces["closest-tag"]:
++        rendered = pieces["closest-tag"]
++        if pieces["distance"] or pieces["dirty"]:
++            rendered += ".post%d" % pieces["distance"]
++            if pieces["branch"] != "master":
++                rendered += ".dev0"
++            rendered += plus_or_dot(pieces)
++            rendered += "g%s" % pieces["short"]
++            if pieces["dirty"]:
++                rendered += ".dirty"
++    else:
++        # exception #1
++        rendered = "0.post%d" % pieces["distance"]
++        if pieces["branch"] != "master":
++            rendered += ".dev0"
++        rendered += "+g%s" % pieces["short"]
++        if pieces["dirty"]:
++            rendered += ".dirty"
++    return rendered
++
++
++def render_pep440_old(pieces: Dict[str, Any]) -> str:
+     """TAG[.postDISTANCE[.dev0]] .
+ 
+     The ".dev0" means dirty.
+ 
+-    Eexceptions:
++    Exceptions:
+     1: no tags. 0.postDISTANCE[.dev0]
+     """
+     if pieces["closest-tag"]:
+@@ -1323,7 +1738,7 @@ def render_pep440_old(pieces):
+     return rendered
+ 
+ 
+-def render_git_describe(pieces):
++def render_git_describe(pieces: Dict[str, Any]) -> str:
+     """TAG[-DISTANCE-gHEX][-dirty].
+ 
+     Like 'git describe --tags --dirty --always'.
+@@ -1343,7 +1758,7 @@ def render_git_describe(pieces):
+     return rendered
+ 
+ 
+-def render_git_describe_long(pieces):
++def render_git_describe_long(pieces: Dict[str, Any]) -> str:
+     """TAG-DISTANCE-gHEX[-dirty].
+ 
+     Like 'git describe --tags --dirty --always -long'.
+@@ -1363,24 +1778,30 @@ def render_git_describe_long(pieces):
+     return rendered
+ 
+ 
+-def render(pieces, style):
++def render(pieces: Dict[str, Any], style: str) -> Dict[str, Any]:
+     """Render the given version pieces into the requested style."""
+     if pieces["error"]:
+-        return {"version": "unknown",
+-                "full-revisionid": pieces.get("long"),
+-                "dirty": None,
+-                "error": pieces["error"],
+-                "date": None}
++        return {
++            "version": "unknown",
++            "full-revisionid": pieces.get("long"),
++            "dirty": None,
++            "error": pieces["error"],
++            "date": None,
++        }
+ 
+     if not style or style == "default":
+         style = "pep440"  # the default
+ 
+     if style == "pep440":
+         rendered = render_pep440(pieces)
++    elif style == "pep440-branch":
++        rendered = render_pep440_branch(pieces)
+     elif style == "pep440-pre":
+         rendered = render_pep440_pre(pieces)
+     elif style == "pep440-post":
+         rendered = render_pep440_post(pieces)
++    elif style == "pep440-post-branch":
++        rendered = render_pep440_post_branch(pieces)
+     elif style == "pep440-old":
+         rendered = render_pep440_old(pieces)
+     elif style == "git-describe":
+@@ -1390,16 +1811,20 @@ def render(pieces, style):
+     else:
+         raise ValueError("unknown style '%s'" % style)
+ 
+-    return {"version": rendered, "full-revisionid": pieces["long"],
+-            "dirty": pieces["dirty"], "error": None,
+-            "date": pieces.get("date")}
++    return {
++        "version": rendered,
++        "full-revisionid": pieces["long"],
++        "dirty": pieces["dirty"],
++        "error": None,
++        "date": pieces.get("date"),
++    }
+ 
+ 
+ class VersioneerBadRootError(Exception):
+     """The project root directory is unknown or missing key files."""
+ 
+ 
+-def get_versions(verbose=False):
++def get_versions(verbose: bool = False) -> Dict[str, Any]:
+     """Get the project version from whatever source is available.
+ 
+     Returns dict with two keys: 'version' and 'full'.
+@@ -1414,9 +1839,10 @@ def get_versions(verbose=False):
+     assert cfg.VCS is not None, "please set [versioneer]VCS= in setup.cfg"
+     handlers = HANDLERS.get(cfg.VCS)
+     assert handlers, "unrecognized VCS '%s'" % cfg.VCS
+-    verbose = verbose or cfg.verbose
+-    assert cfg.versionfile_source is not None, \
+-        "please set versioneer.versionfile_source"
++    verbose = verbose or bool(cfg.verbose)  # `bool()` used to avoid `None`
++    assert (
++        cfg.versionfile_source is not None
++    ), "please set versioneer.versionfile_source"
+     assert cfg.tag_prefix is not None, "please set versioneer.tag_prefix"
+ 
+     versionfile_abs = os.path.join(root, cfg.versionfile_source)
+@@ -1470,18 +1896,26 @@ def get_versions(verbose=False):
+     if verbose:
+         print("unable to compute version")
+ 
+-    return {"version": "0+unknown", "full-revisionid": None,
+-            "dirty": None, "error": "unable to compute version",
+-            "date": None}
++    return {
++        "version": "0+unknown",
++        "full-revisionid": None,
++        "dirty": None,
++        "error": "unable to compute version",
++        "date": None,
++    }
+ 
+ 
+-def get_version():
++def get_version() -> str:
+     """Get the short version string for this project."""
+     return get_versions()["version"]
+ 
+ 
+-def get_cmdclass():
+-    """Get the custom setuptools/distutils subclasses used by Versioneer."""
++def get_cmdclass(cmdclass: Optional[Dict[str, Any]] = None):
++    """Get the custom setuptools subclasses used by Versioneer.
++
++    If the package uses a different cmdclass (e.g. one from numpy), it
++    should be provide as an argument.
++    """
+     if "versioneer" in sys.modules:
+         del sys.modules["versioneer"]
+         # this fixes the "python setup.py develop" case (also 'install' and
+@@ -1495,25 +1929,25 @@ def get_cmdclass():
+         # parent is protected against the child's "import versioneer". By
+         # removing ourselves from sys.modules here, before the child build
+         # happens, we protect the child from the parent's versioneer too.
+-        # Also see https://github.com/warner/python-versioneer/issues/52
++        # Also see https://github.com/python-versioneer/python-versioneer/issues/52
+ 
+-    cmds = {}
++    cmds = {} if cmdclass is None else cmdclass.copy()
+ 
+-    # we add "version" to both distutils and setuptools
+-    from distutils.core import Command
++    # we add "version" to setuptools
++    from setuptools import Command
+ 
+     class cmd_version(Command):
+         description = "report generated version string"
+-        user_options = []
+-        boolean_options = []
++        user_options: List[Tuple[str, str, str]] = []
++        boolean_options: List[str] = []
+ 
+-        def initialize_options(self):
++        def initialize_options(self) -> None:
+             pass
+ 
+-        def finalize_options(self):
++        def finalize_options(self) -> None:
+             pass
+ 
+-        def run(self):
++        def run(self) -> None:
+             vers = get_versions(verbose=True)
+             print("Version: %s" % vers["version"])
+             print(" full-revisionid: %s" % vers.get("full-revisionid"))
+@@ -1521,9 +1955,10 @@ def get_cmdclass():
+             print(" date: %s" % vers.get("date"))
+             if vers["error"]:
+                 print(" error: %s" % vers["error"])
++
+     cmds["version"] = cmd_version
+ 
+-    # we override "build_py" in both distutils and setuptools
++    # we override "build_py" in setuptools
+     #
+     # most invocation pathways end up running build_py:
+     #  distutils/build -> build_py
+@@ -1538,29 +1973,71 @@ def get_cmdclass():
+     #   then does setup.py bdist_wheel, or sometimes setup.py install
+     #  setup.py egg_info -> ?
+ 
++    # pip install -e . and setuptool/editable_wheel will invoke build_py
++    # but the build_py command is not expected to copy any files.
++
+     # we override different "build_py" commands for both environments
+-    if "setuptools" in sys.modules:
+-        from setuptools.command.build_py import build_py as _build_py
++    if "build_py" in cmds:
++        _build_py: Any = cmds["build_py"]
+     else:
+-        from distutils.command.build_py import build_py as _build_py
++        from setuptools.command.build_py import build_py as _build_py
+ 
+     class cmd_build_py(_build_py):
+-        def run(self):
++        def run(self) -> None:
+             root = get_root()
+             cfg = get_config_from_root(root)
+             versions = get_versions()
+             _build_py.run(self)
++            if getattr(self, "editable_mode", False):
++                # During editable installs `.py` and data files are
++                # not copied to build_lib
++                return
+             # now locate _version.py in the new build/ directory and replace
+             # it with an updated value
+             if cfg.versionfile_build:
+-                target_versionfile = os.path.join(self.build_lib,
+-                                                  cfg.versionfile_build)
++                target_versionfile = os.path.join(self.build_lib, cfg.versionfile_build)
+                 print("UPDATING %s" % target_versionfile)
+                 write_to_version_file(target_versionfile, versions)
++
+     cmds["build_py"] = cmd_build_py
+ 
++    if "build_ext" in cmds:
++        _build_ext: Any = cmds["build_ext"]
++    else:
++        from setuptools.command.build_ext import build_ext as _build_ext
++
++    class cmd_build_ext(_build_ext):
++        def run(self) -> None:
++            root = get_root()
++            cfg = get_config_from_root(root)
++            versions = get_versions()
++            _build_ext.run(self)
++            if self.inplace:
++                # build_ext --inplace will only build extensions in
++                # build/lib<..> dir with no _version.py to write to.
++                # As in place builds will already have a _version.py
++                # in the module dir, we do not need to write one.
++                return
++            # now locate _version.py in the new build/ directory and replace
++            # it with an updated value
++            if not cfg.versionfile_build:
++                return
++            target_versionfile = os.path.join(self.build_lib, cfg.versionfile_build)
++            if not os.path.exists(target_versionfile):
++                print(
++                    f"Warning: {target_versionfile} does not exist, skipping "
++                    "version update. This can happen if you are running build_ext "
++                    "without first running build_py."
++                )
++                return
++            print("UPDATING %s" % target_versionfile)
++            write_to_version_file(target_versionfile, versions)
++
++    cmds["build_ext"] = cmd_build_ext
++
+     if "cx_Freeze" in sys.modules:  # cx_freeze enabled?
+-        from cx_Freeze.dist import build_exe as _build_exe
++        from cx_Freeze.dist import build_exe as _build_exe  # type: ignore
++
+         # nczeczulin reports that py2exe won't like the pep440-style string
+         # as FILEVERSION, but it can be used for PRODUCTVERSION, e.g.
+         # setup(console=[{
+@@ -1569,7 +2046,7 @@ def get_cmdclass():
+         #   ...
+ 
+         class cmd_build_exe(_build_exe):
+-            def run(self):
++            def run(self) -> None:
+                 root = get_root()
+                 cfg = get_config_from_root(root)
+                 versions = get_versions()
+@@ -1581,24 +2058,28 @@ def get_cmdclass():
+                 os.unlink(target_versionfile)
+                 with open(cfg.versionfile_source, "w") as f:
+                     LONG = LONG_VERSION_PY[cfg.VCS]
+-                    f.write(LONG %
+-                            {"DOLLAR": "$",
+-                             "STYLE": cfg.style,
+-                             "TAG_PREFIX": cfg.tag_prefix,
+-                             "PARENTDIR_PREFIX": cfg.parentdir_prefix,
+-                             "VERSIONFILE_SOURCE": cfg.versionfile_source,
+-                             })
++                    f.write(
++                        LONG
++                        % {
++                            "DOLLAR": "$",
++                            "STYLE": cfg.style,
++                            "TAG_PREFIX": cfg.tag_prefix,
++                            "PARENTDIR_PREFIX": cfg.parentdir_prefix,
++                            "VERSIONFILE_SOURCE": cfg.versionfile_source,
++                        }
++                    )
++
+         cmds["build_exe"] = cmd_build_exe
+         del cmds["build_py"]
+ 
+-    if 'py2exe' in sys.modules:  # py2exe enabled?
++    if "py2exe" in sys.modules:  # py2exe enabled?
+         try:
+-            from py2exe.distutils_buildexe import py2exe as _py2exe  # py3
++            from py2exe.setuptools_buildexe import py2exe as _py2exe  # type: ignore
+         except ImportError:
+-            from py2exe.build_exe import py2exe as _py2exe  # py2
++            from py2exe.distutils_buildexe import py2exe as _py2exe  # type: ignore
+ 
+         class cmd_py2exe(_py2exe):
+-            def run(self):
++            def run(self) -> None:
+                 root = get_root()
+                 cfg = get_config_from_root(root)
+                 versions = get_versions()
+@@ -1610,23 +2091,67 @@ def get_cmdclass():
+                 os.unlink(target_versionfile)
+                 with open(cfg.versionfile_source, "w") as f:
+                     LONG = LONG_VERSION_PY[cfg.VCS]
+-                    f.write(LONG %
+-                            {"DOLLAR": "$",
+-                             "STYLE": cfg.style,
+-                             "TAG_PREFIX": cfg.tag_prefix,
+-                             "PARENTDIR_PREFIX": cfg.parentdir_prefix,
+-                             "VERSIONFILE_SOURCE": cfg.versionfile_source,
+-                             })
++                    f.write(
++                        LONG
++                        % {
++                            "DOLLAR": "$",
++                            "STYLE": cfg.style,
++                            "TAG_PREFIX": cfg.tag_prefix,
++                            "PARENTDIR_PREFIX": cfg.parentdir_prefix,
++                            "VERSIONFILE_SOURCE": cfg.versionfile_source,
++                        }
++                    )
++
+         cmds["py2exe"] = cmd_py2exe
+ 
++    # sdist farms its file list building out to egg_info
++    if "egg_info" in cmds:
++        _egg_info: Any = cmds["egg_info"]
++    else:
++        from setuptools.command.egg_info import egg_info as _egg_info
++
++    class cmd_egg_info(_egg_info):
++        def find_sources(self) -> None:
++            # egg_info.find_sources builds the manifest list and writes it
++            # in one shot
++            super().find_sources()
++
++            # Modify the filelist and normalize it
++            root = get_root()
++            cfg = get_config_from_root(root)
++            self.filelist.append("versioneer.py")
++            if cfg.versionfile_source:
++                # There are rare cases where versionfile_source might not be
++                # included by default, so we must be explicit
++                self.filelist.append(cfg.versionfile_source)
++            self.filelist.sort()
++            self.filelist.remove_duplicates()
++
++            # The write method is hidden in the manifest_maker instance that
++            # generated the filelist and was thrown away
++            # We will instead replicate their final normalization (to unicode,
++            # and POSIX-style paths)
++            from setuptools import unicode_utils
++
++            normalized = [
++                unicode_utils.filesys_decode(f).replace(os.sep, "/")
++                for f in self.filelist.files
++            ]
++
++            manifest_filename = os.path.join(self.egg_info, "SOURCES.txt")
++            with open(manifest_filename, "w") as fobj:
++                fobj.write("\n".join(normalized))
++
++    cmds["egg_info"] = cmd_egg_info
++
+     # we override different "sdist" commands for both environments
+-    if "setuptools" in sys.modules:
+-        from setuptools.command.sdist import sdist as _sdist
++    if "sdist" in cmds:
++        _sdist: Any = cmds["sdist"]
+     else:
+-        from distutils.command.sdist import sdist as _sdist
++        from setuptools.command.sdist import sdist as _sdist
+ 
+     class cmd_sdist(_sdist):
+-        def run(self):
++        def run(self) -> None:
+             versions = get_versions()
+             self._versioneer_generated_versions = versions
+             # unless we update this, the command will keep using the old
+@@ -1634,7 +2159,7 @@ def get_cmdclass():
+             self.distribution.metadata.version = versions["version"]
+             return _sdist.run(self)
+ 
+-        def make_release_tree(self, base_dir, files):
++        def make_release_tree(self, base_dir: str, files: List[str]) -> None:
+             root = get_root()
+             cfg = get_config_from_root(root)
+             _sdist.make_release_tree(self, base_dir, files)
+@@ -1643,8 +2168,10 @@ def get_cmdclass():
+             # updated value
+             target_versionfile = os.path.join(base_dir, cfg.versionfile_source)
+             print("UPDATING %s" % target_versionfile)
+-            write_to_version_file(target_versionfile,
+-                                  self._versioneer_generated_versions)
++            write_to_version_file(
++                target_versionfile, self._versioneer_generated_versions
++            )
++
+     cmds["sdist"] = cmd_sdist
+ 
+     return cmds
+@@ -1687,23 +2214,26 @@ SAMPLE_CONFIG = """
+ 
+ """
+ 
+-INIT_PY_SNIPPET = """
++OLD_SNIPPET = """
+ from ._version import get_versions
+ __version__ = get_versions()['version']
+ del get_versions
+ """
+ 
++INIT_PY_SNIPPET = """
++from . import {0}
++__version__ = {0}.get_versions()['version']
++"""
++
+ 
+-def do_setup():
+-    """Main VCS-independent setup function for installing Versioneer."""
++def do_setup() -> int:
++    """Do main VCS-independent setup function for installing Versioneer."""
+     root = get_root()
+     try:
+         cfg = get_config_from_root(root)
+-    except (EnvironmentError, configparser.NoSectionError,
+-            configparser.NoOptionError) as e:
+-        if isinstance(e, (EnvironmentError, configparser.NoSectionError)):
+-            print("Adding sample versioneer config to setup.cfg",
+-                  file=sys.stderr)
++    except (OSError, configparser.NoSectionError, configparser.NoOptionError) as e:
++        if isinstance(e, (OSError, configparser.NoSectionError)):
++            print("Adding sample versioneer config to setup.cfg", file=sys.stderr)
+             with open(os.path.join(root, "setup.cfg"), "a") as f:
+                 f.write(SAMPLE_CONFIG)
+         print(CONFIG_ERROR, file=sys.stderr)
+@@ -1712,71 +2242,49 @@ def do_setup():
+     print(" creating %s" % cfg.versionfile_source)
+     with open(cfg.versionfile_source, "w") as f:
+         LONG = LONG_VERSION_PY[cfg.VCS]
+-        f.write(LONG % {"DOLLAR": "$",
+-                        "STYLE": cfg.style,
+-                        "TAG_PREFIX": cfg.tag_prefix,
+-                        "PARENTDIR_PREFIX": cfg.parentdir_prefix,
+-                        "VERSIONFILE_SOURCE": cfg.versionfile_source,
+-                        })
+-
+-    ipy = os.path.join(os.path.dirname(cfg.versionfile_source),
+-                       "__init__.py")
++        f.write(
++            LONG
++            % {
++                "DOLLAR": "$",
++                "STYLE": cfg.style,
++                "TAG_PREFIX": cfg.tag_prefix,
++                "PARENTDIR_PREFIX": cfg.parentdir_prefix,
++                "VERSIONFILE_SOURCE": cfg.versionfile_source,
++            }
++        )
++
++    ipy = os.path.join(os.path.dirname(cfg.versionfile_source), "__init__.py")
++    maybe_ipy: Optional[str] = ipy
+     if os.path.exists(ipy):
+         try:
+             with open(ipy, "r") as f:
+                 old = f.read()
+-        except EnvironmentError:
++        except OSError:
+             old = ""
+-        if INIT_PY_SNIPPET not in old:
++        module = os.path.splitext(os.path.basename(cfg.versionfile_source))[0]
++        snippet = INIT_PY_SNIPPET.format(module)
++        if OLD_SNIPPET in old:
++            print(" replacing boilerplate in %s" % ipy)
++            with open(ipy, "w") as f:
++                f.write(old.replace(OLD_SNIPPET, snippet))
++        elif snippet not in old:
+             print(" appending to %s" % ipy)
+             with open(ipy, "a") as f:
+-                f.write(INIT_PY_SNIPPET)
++                f.write(snippet)
+         else:
+             print(" %s unmodified" % ipy)
+     else:
+         print(" %s doesn't exist, ok" % ipy)
+-        ipy = None
+-
+-    # Make sure both the top-level "versioneer.py" and versionfile_source
+-    # (PKG/_version.py, used by runtime code) are in MANIFEST.in, so
+-    # they'll be copied into source distributions. Pip won't be able to
+-    # install the package without this.
+-    manifest_in = os.path.join(root, "MANIFEST.in")
+-    simple_includes = set()
+-    try:
+-        with open(manifest_in, "r") as f:
+-            for line in f:
+-                if line.startswith("include "):
+-                    for include in line.split()[1:]:
+-                        simple_includes.add(include)
+-    except EnvironmentError:
+-        pass
+-    # That doesn't cover everything MANIFEST.in can do
+-    # (http://docs.python.org/2/distutils/sourcedist.html#commands), so
+-    # it might give some false negatives. Appending redundant 'include'
+-    # lines is safe, though.
+-    if "versioneer.py" not in simple_includes:
+-        print(" appending 'versioneer.py' to MANIFEST.in")
+-        with open(manifest_in, "a") as f:
+-            f.write("include versioneer.py\n")
+-    else:
+-        print(" 'versioneer.py' already in MANIFEST.in")
+-    if cfg.versionfile_source not in simple_includes:
+-        print(" appending versionfile_source ('%s') to MANIFEST.in" %
+-              cfg.versionfile_source)
+-        with open(manifest_in, "a") as f:
+-            f.write("include %s\n" % cfg.versionfile_source)
+-    else:
+-        print(" versionfile_source already in MANIFEST.in")
++        maybe_ipy = None
+ 
+     # Make VCS-specific changes. For git, this means creating/changing
+     # .gitattributes to mark _version.py for export-subst keyword
+     # substitution.
+-    do_vcs_install(manifest_in, cfg.versionfile_source, ipy)
++    do_vcs_install(cfg.versionfile_source, maybe_ipy)
+     return 0
+ 
+ 
+-def scan_setup_py():
++def scan_setup_py() -> int:
+     """Validate the contents of setup.py against Versioneer's expectations."""
+     found = set()
+     setters = False
+@@ -1813,10 +2321,14 @@ def scan_setup_py():
+     return errors
+ 
+ 
++def setup_command() -> NoReturn:
++    """Set up Versioneer and exit with appropriate error code."""
++    errors = do_setup()
++    errors += scan_setup_py()
++    sys.exit(1 if errors else 0)
++
++
+ if __name__ == "__main__":
+     cmd = sys.argv[1]
+     if cmd == "setup":
+-        errors = do_setup()
+-        errors += scan_setup_py()
+-        if errors:
+-            sys.exit(1)
++        setup_command()
+-- 
+2.41.0
+
-- 
2.41.0


_______________________________________________
buildroot mailing list
buildroot@buildroot.org
https://lists.buildroot.org/mailman/listinfo/buildroot


^ permalink raw reply related	[flat|nested] 54+ messages in thread

* [Buildroot] [PATCH 24/30] package/python-spake2: update versioneer to 0.29
  2023-10-26  9:26 [Buildroot] [PATCH 00/30] package/python3: bump version to 3.12.0 Adam Duskett
                   ` (22 preceding siblings ...)
  2023-10-26  9:26 ` [Buildroot] [PATCH 23/30] package/python-magic-wormhole-transit-relay: " Adam Duskett
@ 2023-10-26  9:26 ` Adam Duskett
  2023-11-04 21:32   ` Arnout Vandecappelle via buildroot
  2023-10-26  9:26 ` [Buildroot] [PATCH 25/30] package/python-iptables: use sysconfig.get_path instead of get_python_lib Adam Duskett
                   ` (6 subsequent siblings)
  30 siblings, 1 reply; 54+ messages in thread
From: Adam Duskett @ 2023-10-26  9:26 UTC (permalink / raw)
  To: buildroot
  Cc: Adam Duskett, Andrey Smirnov, Julien Olivain, Asaf Kahlon,
	James Hilliard, Thomas Petazzoni, Mauro Condarelli

Versioneer < 0.21 is incompatible with Python 3.12.0. Use the latest version
which is 0.29 as of this commit.

Signed-off-by: Adam Duskett <adam.duskett@amarulasolutions.com>
---
 .../0001-Update-versioneer-to-0.29.patch      | 2194 +++++++++++++++++
 1 file changed, 2194 insertions(+)
 create mode 100644 package/python-spake2/0001-Update-versioneer-to-0.29.patch

diff --git a/package/python-spake2/0001-Update-versioneer-to-0.29.patch b/package/python-spake2/0001-Update-versioneer-to-0.29.patch
new file mode 100644
index 0000000000..3b5db5342c
--- /dev/null
+++ b/package/python-spake2/0001-Update-versioneer-to-0.29.patch
@@ -0,0 +1,2194 @@
+From 5b5436f11d01e66505bb4c148304c2eb49346529 Mon Sep 17 00:00:00 2001
+From: Adam Duskett <adam.duskett@amarulasolutions.com>
+Date: Tue, 24 Oct 2023 09:56:57 +0200
+Subject: [PATCH] Update versioneer to 0.29
+
+Fixes builds against Python 3.12.0
+
+Upstream: https://github.com/warner/python-spake2/pull/15
+
+Signed-off-by: Adam Duskett <adam.duskett@amarulasolutions.com>
+---
+ versioneer.py | 1350 ++++++++++++++++++++++++++++++++++---------------
+ 1 file changed, 931 insertions(+), 419 deletions(-)
+
+diff --git a/versioneer.py b/versioneer.py
+index 64fea1c..de97d90 100644
+--- a/versioneer.py
++++ b/versioneer.py
+@@ -1,5 +1,4 @@
+-
+-# Version: 0.18
++# Version: 0.29
+ 
+ """The Versioneer - like a rocketeer, but for versions.
+ 
+@@ -7,18 +6,14 @@ The Versioneer
+ ==============
+ 
+ * like a rocketeer, but for versions!
+-* https://github.com/warner/python-versioneer
++* https://github.com/python-versioneer/python-versioneer
+ * Brian Warner
+-* License: Public Domain
+-* Compatible With: python2.6, 2.7, 3.2, 3.3, 3.4, 3.5, 3.6, and pypy
+-* [![Latest Version]
+-(https://pypip.in/version/versioneer/badge.svg?style=flat)
+-](https://pypi.python.org/pypi/versioneer/)
+-* [![Build Status]
+-(https://travis-ci.org/warner/python-versioneer.png?branch=master)
+-](https://travis-ci.org/warner/python-versioneer)
+-
+-This is a tool for managing a recorded version number in distutils-based
++* License: Public Domain (Unlicense)
++* Compatible with: Python 3.7, 3.8, 3.9, 3.10, 3.11 and pypy3
++* [![Latest Version][pypi-image]][pypi-url]
++* [![Build Status][travis-image]][travis-url]
++
++This is a tool for managing a recorded version number in setuptools-based
+ python projects. The goal is to remove the tedious and error-prone "update
+ the embedded version string" step from your release process. Making a new
+ release should be as easy as recording a new tag in your version-control
+@@ -27,9 +22,38 @@ system, and maybe making new tarballs.
+ 
+ ## Quick Install
+ 
+-* `pip install versioneer` to somewhere to your $PATH
+-* add a `[versioneer]` section to your setup.cfg (see below)
+-* run `versioneer install` in your source tree, commit the results
++Versioneer provides two installation modes. The "classic" vendored mode installs
++a copy of versioneer into your repository. The experimental build-time dependency mode
++is intended to allow you to skip this step and simplify the process of upgrading.
++
++### Vendored mode
++
++* `pip install versioneer` to somewhere in your $PATH
++   * A [conda-forge recipe](https://github.com/conda-forge/versioneer-feedstock) is
++     available, so you can also use `conda install -c conda-forge versioneer`
++* add a `[tool.versioneer]` section to your `pyproject.toml` or a
++  `[versioneer]` section to your `setup.cfg` (see [Install](INSTALL.md))
++   * Note that you will need to add `tomli; python_version < "3.11"` to your
++     build-time dependencies if you use `pyproject.toml`
++* run `versioneer install --vendor` in your source tree, commit the results
++* verify version information with `python setup.py version`
++
++### Build-time dependency mode
++
++* `pip install versioneer` to somewhere in your $PATH
++   * A [conda-forge recipe](https://github.com/conda-forge/versioneer-feedstock) is
++     available, so you can also use `conda install -c conda-forge versioneer`
++* add a `[tool.versioneer]` section to your `pyproject.toml` or a
++  `[versioneer]` section to your `setup.cfg` (see [Install](INSTALL.md))
++* add `versioneer` (with `[toml]` extra, if configuring in `pyproject.toml`)
++  to the `requires` key of the `build-system` table in `pyproject.toml`:
++  ```toml
++  [build-system]
++  requires = ["setuptools", "versioneer[toml]"]
++  build-backend = "setuptools.build_meta"
++  ```
++* run `versioneer install --no-vendor` in your source tree, commit the results
++* verify version information with `python setup.py version`
+ 
+ ## Version Identifiers
+ 
+@@ -61,7 +85,7 @@ version 1.3). Many VCS systems can report a description that captures this,
+ for example `git describe --tags --dirty --always` reports things like
+ "0.7-1-g574ab98-dirty" to indicate that the checkout is one revision past the
+ 0.7 tag, has a unique revision id of "574ab98", and is "dirty" (it has
+-uncommitted changes.
++uncommitted changes).
+ 
+ The version identifier is used for multiple purposes:
+ 
+@@ -166,7 +190,7 @@ which may help identify what went wrong).
+ 
+ Some situations are known to cause problems for Versioneer. This details the
+ most significant ones. More can be found on Github
+-[issues page](https://github.com/warner/python-versioneer/issues).
++[issues page](https://github.com/python-versioneer/python-versioneer/issues).
+ 
+ ### Subprojects
+ 
+@@ -180,7 +204,7 @@ two common reasons why `setup.py` might not be in the root:
+   `setup.cfg`, and `tox.ini`. Projects like these produce multiple PyPI
+   distributions (and upload multiple independently-installable tarballs).
+ * Source trees whose main purpose is to contain a C library, but which also
+-  provide bindings to Python (and perhaps other langauges) in subdirectories.
++  provide bindings to Python (and perhaps other languages) in subdirectories.
+ 
+ Versioneer will look for `.git` in parent directories, and most operations
+ should get the right version string. However `pip` and `setuptools` have bugs
+@@ -194,9 +218,9 @@ work too.
+ Pip-8.1.1 is known to have this problem, but hopefully it will get fixed in
+ some later version.
+ 
+-[Bug #38](https://github.com/warner/python-versioneer/issues/38) is tracking
++[Bug #38](https://github.com/python-versioneer/python-versioneer/issues/38) is tracking
+ this issue. The discussion in
+-[PR #61](https://github.com/warner/python-versioneer/pull/61) describes the
++[PR #61](https://github.com/python-versioneer/python-versioneer/pull/61) describes the
+ issue from the Versioneer side in more detail.
+ [pip PR#3176](https://github.com/pypa/pip/pull/3176) and
+ [pip PR#3615](https://github.com/pypa/pip/pull/3615) contain work to improve
+@@ -224,31 +248,20 @@ regenerated while a different version is checked out. Many setup.py commands
+ cause egg_info to be rebuilt (including `sdist`, `wheel`, and installing into
+ a different virtualenv), so this can be surprising.
+ 
+-[Bug #83](https://github.com/warner/python-versioneer/issues/83) describes
++[Bug #83](https://github.com/python-versioneer/python-versioneer/issues/83) describes
+ this one, but upgrading to a newer version of setuptools should probably
+ resolve it.
+ 
+-### Unicode version strings
+-
+-While Versioneer works (and is continually tested) with both Python 2 and
+-Python 3, it is not entirely consistent with bytes-vs-unicode distinctions.
+-Newer releases probably generate unicode version strings on py2. It's not
+-clear that this is wrong, but it may be surprising for applications when then
+-write these strings to a network connection or include them in bytes-oriented
+-APIs like cryptographic checksums.
+-
+-[Bug #71](https://github.com/warner/python-versioneer/issues/71) investigates
+-this question.
+-
+ 
+ ## Updating Versioneer
+ 
+ To upgrade your project to a new release of Versioneer, do the following:
+ 
+ * install the new Versioneer (`pip install -U versioneer` or equivalent)
+-* edit `setup.cfg`, if necessary, to include any new configuration settings
+-  indicated by the release notes. See [UPGRADING](./UPGRADING.md) for details.
+-* re-run `versioneer install` in your source tree, to replace
++* edit `setup.cfg` and `pyproject.toml`, if necessary,
++  to include any new configuration settings indicated by the release notes.
++  See [UPGRADING](./UPGRADING.md) for details.
++* re-run `versioneer install --[no-]vendor` in your source tree, to replace
+   `SRC/_version.py`
+ * commit any changed files
+ 
+@@ -265,35 +278,70 @@ installation by editing setup.py . Alternatively, it might go the other
+ direction and include code from all supported VCS systems, reducing the
+ number of intermediate scripts.
+ 
++## Similar projects
++
++* [setuptools_scm](https://github.com/pypa/setuptools_scm/) - a non-vendored build-time
++  dependency
++* [minver](https://github.com/jbweston/miniver) - a lightweight reimplementation of
++  versioneer
++* [versioningit](https://github.com/jwodder/versioningit) - a PEP 518-based setuptools
++  plugin
+ 
+ ## License
+ 
+ To make Versioneer easier to embed, all its code is dedicated to the public
+ domain. The `_version.py` that it creates is also in the public domain.
+-Specifically, both are released under the Creative Commons "Public Domain
+-Dedication" license (CC0-1.0), as described in
+-https://creativecommons.org/publicdomain/zero/1.0/ .
++Specifically, both are released under the "Unlicense", as described in
++https://unlicense.org/.
++
++[pypi-image]: https://img.shields.io/pypi/v/versioneer.svg
++[pypi-url]: https://pypi.python.org/pypi/versioneer/
++[travis-image]:
++https://img.shields.io/travis/com/python-versioneer/python-versioneer.svg
++[travis-url]: https://travis-ci.com/github/python-versioneer/python-versioneer
+ 
+ """
++# pylint:disable=invalid-name,import-outside-toplevel,missing-function-docstring
++# pylint:disable=missing-class-docstring,too-many-branches,too-many-statements
++# pylint:disable=raise-missing-from,too-many-lines,too-many-locals,import-error
++# pylint:disable=too-few-public-methods,redefined-outer-name,consider-using-with
++# pylint:disable=attribute-defined-outside-init,too-many-arguments
+ 
+-from __future__ import print_function
+-try:
+-    import configparser
+-except ImportError:
+-    import ConfigParser as configparser
++import configparser
+ import errno
+ import json
+ import os
+ import re
+ import subprocess
+ import sys
++from pathlib import Path
++from typing import Any, Callable, cast, Dict, List, Optional, Tuple, Union
++from typing import NoReturn
++import functools
++
++have_tomllib = True
++if sys.version_info >= (3, 11):
++    import tomllib
++else:
++    try:
++        import tomli as tomllib
++    except ImportError:
++        have_tomllib = False
+ 
+ 
+ class VersioneerConfig:
+     """Container for Versioneer configuration parameters."""
+ 
++    VCS: str
++    style: str
++    tag_prefix: str
++    versionfile_source: str
++    versionfile_build: Optional[str]
++    parentdir_prefix: Optional[str]
++    verbose: Optional[bool]
++
+ 
+-def get_root():
++def get_root() -> str:
+     """Get the project root directory.
+ 
+     We require that all commands are run from the project root, i.e. the
+@@ -301,18 +349,30 @@ def get_root():
+     """
+     root = os.path.realpath(os.path.abspath(os.getcwd()))
+     setup_py = os.path.join(root, "setup.py")
++    pyproject_toml = os.path.join(root, "pyproject.toml")
+     versioneer_py = os.path.join(root, "versioneer.py")
+-    if not (os.path.exists(setup_py) or os.path.exists(versioneer_py)):
++    if not (
++        os.path.exists(setup_py)
++        or os.path.exists(pyproject_toml)
++        or os.path.exists(versioneer_py)
++    ):
+         # allow 'python path/to/setup.py COMMAND'
+         root = os.path.dirname(os.path.realpath(os.path.abspath(sys.argv[0])))
+         setup_py = os.path.join(root, "setup.py")
++        pyproject_toml = os.path.join(root, "pyproject.toml")
+         versioneer_py = os.path.join(root, "versioneer.py")
+-    if not (os.path.exists(setup_py) or os.path.exists(versioneer_py)):
+-        err = ("Versioneer was unable to run the project root directory. "
+-               "Versioneer requires setup.py to be executed from "
+-               "its immediate directory (like 'python setup.py COMMAND'), "
+-               "or in a way that lets it use sys.argv[0] to find the root "
+-               "(like 'python path/to/setup.py COMMAND').")
++    if not (
++        os.path.exists(setup_py)
++        or os.path.exists(pyproject_toml)
++        or os.path.exists(versioneer_py)
++    ):
++        err = (
++            "Versioneer was unable to run the project root directory. "
++            "Versioneer requires setup.py to be executed from "
++            "its immediate directory (like 'python setup.py COMMAND'), "
++            "or in a way that lets it use sys.argv[0] to find the root "
++            "(like 'python path/to/setup.py COMMAND')."
++        )
+         raise VersioneerBadRootError(err)
+     try:
+         # Certain runtime workflows (setup.py install/develop in a setuptools
+@@ -321,43 +381,64 @@ def get_root():
+         # module-import table will cache the first one. So we can't use
+         # os.path.dirname(__file__), as that will find whichever
+         # versioneer.py was first imported, even in later projects.
+-        me = os.path.realpath(os.path.abspath(__file__))
+-        me_dir = os.path.normcase(os.path.splitext(me)[0])
++        my_path = os.path.realpath(os.path.abspath(__file__))
++        me_dir = os.path.normcase(os.path.splitext(my_path)[0])
+         vsr_dir = os.path.normcase(os.path.splitext(versioneer_py)[0])
+-        if me_dir != vsr_dir:
+-            print("Warning: build in %s is using versioneer.py from %s"
+-                  % (os.path.dirname(me), versioneer_py))
++        if me_dir != vsr_dir and "VERSIONEER_PEP518" not in globals():
++            print(
++                "Warning: build in %s is using versioneer.py from %s"
++                % (os.path.dirname(my_path), versioneer_py)
++            )
+     except NameError:
+         pass
+     return root
+ 
+ 
+-def get_config_from_root(root):
++def get_config_from_root(root: str) -> VersioneerConfig:
+     """Read the project setup.cfg file to determine Versioneer config."""
+-    # This might raise EnvironmentError (if setup.cfg is missing), or
++    # This might raise OSError (if setup.cfg is missing), or
+     # configparser.NoSectionError (if it lacks a [versioneer] section), or
+     # configparser.NoOptionError (if it lacks "VCS="). See the docstring at
+     # the top of versioneer.py for instructions on writing your setup.cfg .
+-    setup_cfg = os.path.join(root, "setup.cfg")
+-    parser = configparser.SafeConfigParser()
+-    with open(setup_cfg, "r") as f:
+-        parser.readfp(f)
+-    VCS = parser.get("versioneer", "VCS")  # mandatory
+-
+-    def get(parser, name):
+-        if parser.has_option("versioneer", name):
+-            return parser.get("versioneer", name)
+-        return None
++    root_pth = Path(root)
++    pyproject_toml = root_pth / "pyproject.toml"
++    setup_cfg = root_pth / "setup.cfg"
++    section: Union[Dict[str, Any], configparser.SectionProxy, None] = None
++    if pyproject_toml.exists() and have_tomllib:
++        try:
++            with open(pyproject_toml, "rb") as fobj:
++                pp = tomllib.load(fobj)
++            section = pp["tool"]["versioneer"]
++        except (tomllib.TOMLDecodeError, KeyError) as e:
++            print(f"Failed to load config from {pyproject_toml}: {e}")
++            print("Try to load it from setup.cfg")
++    if not section:
++        parser = configparser.ConfigParser()
++        with open(setup_cfg) as cfg_file:
++            parser.read_file(cfg_file)
++        parser.get("versioneer", "VCS")  # raise error if missing
++
++        section = parser["versioneer"]
++
++    # `cast`` really shouldn't be used, but its simplest for the
++    # common VersioneerConfig users at the moment. We verify against
++    # `None` values elsewhere where it matters
++
+     cfg = VersioneerConfig()
+-    cfg.VCS = VCS
+-    cfg.style = get(parser, "style") or ""
+-    cfg.versionfile_source = get(parser, "versionfile_source")
+-    cfg.versionfile_build = get(parser, "versionfile_build")
+-    cfg.tag_prefix = get(parser, "tag_prefix")
+-    if cfg.tag_prefix in ("''", '""'):
++    cfg.VCS = section["VCS"]
++    cfg.style = section.get("style", "")
++    cfg.versionfile_source = cast(str, section.get("versionfile_source"))
++    cfg.versionfile_build = section.get("versionfile_build")
++    cfg.tag_prefix = cast(str, section.get("tag_prefix"))
++    if cfg.tag_prefix in ("''", '""', None):
+         cfg.tag_prefix = ""
+-    cfg.parentdir_prefix = get(parser, "parentdir_prefix")
+-    cfg.verbose = get(parser, "verbose")
++    cfg.parentdir_prefix = section.get("parentdir_prefix")
++    if isinstance(section, configparser.SectionProxy):
++        # Make sure configparser translates to bool
++        cfg.verbose = section.getboolean("verbose")
++    else:
++        cfg.verbose = section.get("verbose")
++
+     return cfg
+ 
+ 
+@@ -366,37 +447,54 @@ class NotThisMethod(Exception):
+ 
+ 
+ # these dictionaries contain VCS-specific tools
+-LONG_VERSION_PY = {}
+-HANDLERS = {}
++LONG_VERSION_PY: Dict[str, str] = {}
++HANDLERS: Dict[str, Dict[str, Callable]] = {}
+ 
+ 
+-def register_vcs_handler(vcs, method):  # decorator
+-    """Decorator to mark a method as the handler for a particular VCS."""
+-    def decorate(f):
++def register_vcs_handler(vcs: str, method: str) -> Callable:  # decorator
++    """Create decorator to mark a method as the handler of a VCS."""
++
++    def decorate(f: Callable) -> Callable:
+         """Store f in HANDLERS[vcs][method]."""
+-        if vcs not in HANDLERS:
+-            HANDLERS[vcs] = {}
+-        HANDLERS[vcs][method] = f
++        HANDLERS.setdefault(vcs, {})[method] = f
+         return f
++
+     return decorate
+ 
+ 
+-def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False,
+-                env=None):
++def run_command(
++    commands: List[str],
++    args: List[str],
++    cwd: Optional[str] = None,
++    verbose: bool = False,
++    hide_stderr: bool = False,
++    env: Optional[Dict[str, str]] = None,
++) -> Tuple[Optional[str], Optional[int]]:
+     """Call the given command(s)."""
+     assert isinstance(commands, list)
+-    p = None
+-    for c in commands:
++    process = None
++
++    popen_kwargs: Dict[str, Any] = {}
++    if sys.platform == "win32":
++        # This hides the console window if pythonw.exe is used
++        startupinfo = subprocess.STARTUPINFO()
++        startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
++        popen_kwargs["startupinfo"] = startupinfo
++
++    for command in commands:
+         try:
+-            dispcmd = str([c] + args)
++            dispcmd = str([command] + args)
+             # remember shell=False, so use git.cmd on windows, not just git
+-            p = subprocess.Popen([c] + args, cwd=cwd, env=env,
+-                                 stdout=subprocess.PIPE,
+-                                 stderr=(subprocess.PIPE if hide_stderr
+-                                         else None))
++            process = subprocess.Popen(
++                [command] + args,
++                cwd=cwd,
++                env=env,
++                stdout=subprocess.PIPE,
++                stderr=(subprocess.PIPE if hide_stderr else None),
++                **popen_kwargs,
++            )
+             break
+-        except EnvironmentError:
+-            e = sys.exc_info()[1]
++        except OSError as e:
+             if e.errno == errno.ENOENT:
+                 continue
+             if verbose:
+@@ -407,26 +505,27 @@ def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False,
+         if verbose:
+             print("unable to find command, tried %s" % (commands,))
+         return None, None
+-    stdout = p.communicate()[0].strip()
+-    if sys.version_info[0] >= 3:
+-        stdout = stdout.decode()
+-    if p.returncode != 0:
++    stdout = process.communicate()[0].strip().decode()
++    if process.returncode != 0:
+         if verbose:
+             print("unable to run %s (error)" % dispcmd)
+             print("stdout was %s" % stdout)
+-        return None, p.returncode
+-    return stdout, p.returncode
++        return None, process.returncode
++    return stdout, process.returncode
+ 
+ 
+-LONG_VERSION_PY['git'] = '''
++LONG_VERSION_PY[
++    "git"
++] = r'''
+ # This file helps to compute a version number in source trees obtained from
+ # git-archive tarball (such as those provided by githubs download-from-tag
+ # feature). Distribution tarballs (built by setup.py sdist) and build
+ # directories (produced by setup.py build) will contain a much shorter file
+ # that just contains the computed version number.
+ 
+-# This file is released into the public domain. Generated by
+-# versioneer-0.18 (https://github.com/warner/python-versioneer)
++# This file is released into the public domain.
++# Generated by versioneer-0.29
++# https://github.com/python-versioneer/python-versioneer
+ 
+ """Git implementation of _version.py."""
+ 
+@@ -435,9 +534,11 @@ import os
+ import re
+ import subprocess
+ import sys
++from typing import Any, Callable, Dict, List, Optional, Tuple
++import functools
+ 
+ 
+-def get_keywords():
++def get_keywords() -> Dict[str, str]:
+     """Get the keywords needed to look up the version information."""
+     # these strings will be replaced by git during git-archive.
+     # setup.py/versioneer.py will grep for the variable names, so they must
+@@ -453,8 +554,15 @@ def get_keywords():
+ class VersioneerConfig:
+     """Container for Versioneer configuration parameters."""
+ 
++    VCS: str
++    style: str
++    tag_prefix: str
++    parentdir_prefix: str
++    versionfile_source: str
++    verbose: bool
++
+ 
+-def get_config():
++def get_config() -> VersioneerConfig:
+     """Create, populate and return the VersioneerConfig() object."""
+     # these strings are filled in when 'setup.py versioneer' creates
+     # _version.py
+@@ -472,13 +580,13 @@ class NotThisMethod(Exception):
+     """Exception raised if a method is not valid for the current scenario."""
+ 
+ 
+-LONG_VERSION_PY = {}
+-HANDLERS = {}
++LONG_VERSION_PY: Dict[str, str] = {}
++HANDLERS: Dict[str, Dict[str, Callable]] = {}
+ 
+ 
+-def register_vcs_handler(vcs, method):  # decorator
+-    """Decorator to mark a method as the handler for a particular VCS."""
+-    def decorate(f):
++def register_vcs_handler(vcs: str, method: str) -> Callable:  # decorator
++    """Create decorator to mark a method as the handler of a VCS."""
++    def decorate(f: Callable) -> Callable:
+         """Store f in HANDLERS[vcs][method]."""
+         if vcs not in HANDLERS:
+             HANDLERS[vcs] = {}
+@@ -487,22 +595,35 @@ def register_vcs_handler(vcs, method):  # decorator
+     return decorate
+ 
+ 
+-def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False,
+-                env=None):
++def run_command(
++    commands: List[str],
++    args: List[str],
++    cwd: Optional[str] = None,
++    verbose: bool = False,
++    hide_stderr: bool = False,
++    env: Optional[Dict[str, str]] = None,
++) -> Tuple[Optional[str], Optional[int]]:
+     """Call the given command(s)."""
+     assert isinstance(commands, list)
+-    p = None
+-    for c in commands:
++    process = None
++
++    popen_kwargs: Dict[str, Any] = {}
++    if sys.platform == "win32":
++        # This hides the console window if pythonw.exe is used
++        startupinfo = subprocess.STARTUPINFO()
++        startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
++        popen_kwargs["startupinfo"] = startupinfo
++
++    for command in commands:
+         try:
+-            dispcmd = str([c] + args)
++            dispcmd = str([command] + args)
+             # remember shell=False, so use git.cmd on windows, not just git
+-            p = subprocess.Popen([c] + args, cwd=cwd, env=env,
+-                                 stdout=subprocess.PIPE,
+-                                 stderr=(subprocess.PIPE if hide_stderr
+-                                         else None))
++            process = subprocess.Popen([command] + args, cwd=cwd, env=env,
++                                       stdout=subprocess.PIPE,
++                                       stderr=(subprocess.PIPE if hide_stderr
++                                               else None), **popen_kwargs)
+             break
+-        except EnvironmentError:
+-            e = sys.exc_info()[1]
++        except OSError as e:
+             if e.errno == errno.ENOENT:
+                 continue
+             if verbose:
+@@ -513,18 +634,20 @@ def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False,
+         if verbose:
+             print("unable to find command, tried %%s" %% (commands,))
+         return None, None
+-    stdout = p.communicate()[0].strip()
+-    if sys.version_info[0] >= 3:
+-        stdout = stdout.decode()
+-    if p.returncode != 0:
++    stdout = process.communicate()[0].strip().decode()
++    if process.returncode != 0:
+         if verbose:
+             print("unable to run %%s (error)" %% dispcmd)
+             print("stdout was %%s" %% stdout)
+-        return None, p.returncode
+-    return stdout, p.returncode
++        return None, process.returncode
++    return stdout, process.returncode
+ 
+ 
+-def versions_from_parentdir(parentdir_prefix, root, verbose):
++def versions_from_parentdir(
++    parentdir_prefix: str,
++    root: str,
++    verbose: bool,
++) -> Dict[str, Any]:
+     """Try to determine the version from the parent directory name.
+ 
+     Source tarballs conventionally unpack into a directory that includes both
+@@ -533,15 +656,14 @@ def versions_from_parentdir(parentdir_prefix, root, verbose):
+     """
+     rootdirs = []
+ 
+-    for i in range(3):
++    for _ in range(3):
+         dirname = os.path.basename(root)
+         if dirname.startswith(parentdir_prefix):
+             return {"version": dirname[len(parentdir_prefix):],
+                     "full-revisionid": None,
+                     "dirty": False, "error": None, "date": None}
+-        else:
+-            rootdirs.append(root)
+-            root = os.path.dirname(root)  # up a level
++        rootdirs.append(root)
++        root = os.path.dirname(root)  # up a level
+ 
+     if verbose:
+         print("Tried directories %%s but none started with prefix %%s" %%
+@@ -550,41 +672,48 @@ def versions_from_parentdir(parentdir_prefix, root, verbose):
+ 
+ 
+ @register_vcs_handler("git", "get_keywords")
+-def git_get_keywords(versionfile_abs):
++def git_get_keywords(versionfile_abs: str) -> Dict[str, str]:
+     """Extract version information from the given file."""
+     # the code embedded in _version.py can just fetch the value of these
+     # keywords. When used from setup.py, we don't want to import _version.py,
+     # so we do it with a regexp instead. This function is not used from
+     # _version.py.
+-    keywords = {}
++    keywords: Dict[str, str] = {}
+     try:
+-        f = open(versionfile_abs, "r")
+-        for line in f.readlines():
+-            if line.strip().startswith("git_refnames ="):
+-                mo = re.search(r'=\s*"(.*)"', line)
+-                if mo:
+-                    keywords["refnames"] = mo.group(1)
+-            if line.strip().startswith("git_full ="):
+-                mo = re.search(r'=\s*"(.*)"', line)
+-                if mo:
+-                    keywords["full"] = mo.group(1)
+-            if line.strip().startswith("git_date ="):
+-                mo = re.search(r'=\s*"(.*)"', line)
+-                if mo:
+-                    keywords["date"] = mo.group(1)
+-        f.close()
+-    except EnvironmentError:
++        with open(versionfile_abs, "r") as fobj:
++            for line in fobj:
++                if line.strip().startswith("git_refnames ="):
++                    mo = re.search(r'=\s*"(.*)"', line)
++                    if mo:
++                        keywords["refnames"] = mo.group(1)
++                if line.strip().startswith("git_full ="):
++                    mo = re.search(r'=\s*"(.*)"', line)
++                    if mo:
++                        keywords["full"] = mo.group(1)
++                if line.strip().startswith("git_date ="):
++                    mo = re.search(r'=\s*"(.*)"', line)
++                    if mo:
++                        keywords["date"] = mo.group(1)
++    except OSError:
+         pass
+     return keywords
+ 
+ 
+ @register_vcs_handler("git", "keywords")
+-def git_versions_from_keywords(keywords, tag_prefix, verbose):
++def git_versions_from_keywords(
++    keywords: Dict[str, str],
++    tag_prefix: str,
++    verbose: bool,
++) -> Dict[str, Any]:
+     """Get version information from git keywords."""
+-    if not keywords:
+-        raise NotThisMethod("no keywords at all, weird")
++    if "refnames" not in keywords:
++        raise NotThisMethod("Short version file found")
+     date = keywords.get("date")
+     if date is not None:
++        # Use only the last line.  Previous lines may contain GPG signature
++        # information.
++        date = date.splitlines()[-1]
++
+         # git-2.2.0 added "%%cI", which expands to an ISO-8601 -compliant
+         # datestamp. However we prefer "%%ci" (which expands to an "ISO-8601
+         # -like" string, which we must then edit to make compliant), because
+@@ -597,11 +726,11 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose):
+         if verbose:
+             print("keywords are unexpanded, not using")
+         raise NotThisMethod("unexpanded keywords, not a git-archive tarball")
+-    refs = set([r.strip() for r in refnames.strip("()").split(",")])
++    refs = {r.strip() for r in refnames.strip("()").split(",")}
+     # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of
+     # just "foo-1.0". If we see a "tag: " prefix, prefer those.
+     TAG = "tag: "
+-    tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)])
++    tags = {r[len(TAG):] for r in refs if r.startswith(TAG)}
+     if not tags:
+         # Either we're using git < 1.8.3, or there really are no tags. We use
+         # a heuristic: assume all version tags have a digit. The old git %%d
+@@ -610,7 +739,7 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose):
+         # between branches and tags. By ignoring refnames without digits, we
+         # filter out many common branch names like "release" and
+         # "stabilization", as well as "HEAD" and "master".
+-        tags = set([r for r in refs if re.search(r'\d', r)])
++        tags = {r for r in refs if re.search(r'\d', r)}
+         if verbose:
+             print("discarding '%%s', no digits" %% ",".join(refs - tags))
+     if verbose:
+@@ -619,6 +748,11 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose):
+         # sorting will prefer e.g. "2.0" over "2.0rc1"
+         if ref.startswith(tag_prefix):
+             r = ref[len(tag_prefix):]
++            # Filter out refs that exactly match prefix or that don't start
++            # with a number once the prefix is stripped (mostly a concern
++            # when prefix is '')
++            if not re.match(r'\d', r):
++                continue
+             if verbose:
+                 print("picking %%s" %% r)
+             return {"version": r,
+@@ -634,7 +768,12 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose):
+ 
+ 
+ @register_vcs_handler("git", "pieces_from_vcs")
+-def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
++def git_pieces_from_vcs(
++    tag_prefix: str,
++    root: str,
++    verbose: bool,
++    runner: Callable = run_command
++) -> Dict[str, Any]:
+     """Get version from 'git describe' in the root of the source tree.
+ 
+     This only gets called if the git-archive 'subst' keywords were *not*
+@@ -645,8 +784,15 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
+     if sys.platform == "win32":
+         GITS = ["git.cmd", "git.exe"]
+ 
+-    out, rc = run_command(GITS, ["rev-parse", "--git-dir"], cwd=root,
+-                          hide_stderr=True)
++    # GIT_DIR can interfere with correct operation of Versioneer.
++    # It may be intended to be passed to the Versioneer-versioned project,
++    # but that should not change where we get our version from.
++    env = os.environ.copy()
++    env.pop("GIT_DIR", None)
++    runner = functools.partial(runner, env=env)
++
++    _, rc = runner(GITS, ["rev-parse", "--git-dir"], cwd=root,
++                   hide_stderr=not verbose)
+     if rc != 0:
+         if verbose:
+             print("Directory %%s not under git control" %% root)
+@@ -654,24 +800,57 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
+ 
+     # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty]
+     # if there isn't one, this yields HEX[-dirty] (no NUM)
+-    describe_out, rc = run_command(GITS, ["describe", "--tags", "--dirty",
+-                                          "--always", "--long",
+-                                          "--match", "%%s*" %% tag_prefix],
+-                                   cwd=root)
++    describe_out, rc = runner(GITS, [
++        "describe", "--tags", "--dirty", "--always", "--long",
++        "--match", f"{tag_prefix}[[:digit:]]*"
++    ], cwd=root)
+     # --long was added in git-1.5.5
+     if describe_out is None:
+         raise NotThisMethod("'git describe' failed")
+     describe_out = describe_out.strip()
+-    full_out, rc = run_command(GITS, ["rev-parse", "HEAD"], cwd=root)
++    full_out, rc = runner(GITS, ["rev-parse", "HEAD"], cwd=root)
+     if full_out is None:
+         raise NotThisMethod("'git rev-parse' failed")
+     full_out = full_out.strip()
+ 
+-    pieces = {}
++    pieces: Dict[str, Any] = {}
+     pieces["long"] = full_out
+     pieces["short"] = full_out[:7]  # maybe improved later
+     pieces["error"] = None
+ 
++    branch_name, rc = runner(GITS, ["rev-parse", "--abbrev-ref", "HEAD"],
++                             cwd=root)
++    # --abbrev-ref was added in git-1.6.3
++    if rc != 0 or branch_name is None:
++        raise NotThisMethod("'git rev-parse --abbrev-ref' returned error")
++    branch_name = branch_name.strip()
++
++    if branch_name == "HEAD":
++        # If we aren't exactly on a branch, pick a branch which represents
++        # the current commit. If all else fails, we are on a branchless
++        # commit.
++        branches, rc = runner(GITS, ["branch", "--contains"], cwd=root)
++        # --contains was added in git-1.5.4
++        if rc != 0 or branches is None:
++            raise NotThisMethod("'git branch --contains' returned error")
++        branches = branches.split("\n")
++
++        # Remove the first line if we're running detached
++        if "(" in branches[0]:
++            branches.pop(0)
++
++        # Strip off the leading "* " from the list of branches.
++        branches = [branch[2:] for branch in branches]
++        if "master" in branches:
++            branch_name = "master"
++        elif not branches:
++            branch_name = None
++        else:
++            # Pick the first branch that is returned. Good or bad.
++            branch_name = branches[0]
++
++    pieces["branch"] = branch_name
++
+     # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty]
+     # TAG might have hyphens.
+     git_describe = describe_out
+@@ -688,7 +867,7 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
+         # TAG-NUM-gHEX
+         mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe)
+         if not mo:
+-            # unparseable. Maybe git-describe is misbehaving?
++            # unparsable. Maybe git-describe is misbehaving?
+             pieces["error"] = ("unable to parse git-describe output: '%%s'"
+                                %% describe_out)
+             return pieces
+@@ -713,26 +892,27 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
+     else:
+         # HEX: no tags
+         pieces["closest-tag"] = None
+-        count_out, rc = run_command(GITS, ["rev-list", "HEAD", "--count"],
+-                                    cwd=root)
+-        pieces["distance"] = int(count_out)  # total number of commits
++        out, rc = runner(GITS, ["rev-list", "HEAD", "--left-right"], cwd=root)
++        pieces["distance"] = len(out.split())  # total number of commits
+ 
+     # commit date: see ISO-8601 comment in git_versions_from_keywords()
+-    date = run_command(GITS, ["show", "-s", "--format=%%ci", "HEAD"],
+-                       cwd=root)[0].strip()
++    date = runner(GITS, ["show", "-s", "--format=%%ci", "HEAD"], cwd=root)[0].strip()
++    # Use only the last line.  Previous lines may contain GPG signature
++    # information.
++    date = date.splitlines()[-1]
+     pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1)
+ 
+     return pieces
+ 
+ 
+-def plus_or_dot(pieces):
++def plus_or_dot(pieces: Dict[str, Any]) -> str:
+     """Return a + if we don't already have one, else return a ."""
+     if "+" in pieces.get("closest-tag", ""):
+         return "."
+     return "+"
+ 
+ 
+-def render_pep440(pieces):
++def render_pep440(pieces: Dict[str, Any]) -> str:
+     """Build up version string, with post-release "local version identifier".
+ 
+     Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you
+@@ -757,23 +937,71 @@ def render_pep440(pieces):
+     return rendered
+ 
+ 
+-def render_pep440_pre(pieces):
+-    """TAG[.post.devDISTANCE] -- No -dirty.
++def render_pep440_branch(pieces: Dict[str, Any]) -> str:
++    """TAG[[.dev0]+DISTANCE.gHEX[.dirty]] .
++
++    The ".dev0" means not master branch. Note that .dev0 sorts backwards
++    (a feature branch will appear "older" than the master branch).
+ 
+     Exceptions:
+-    1: no tags. 0.post.devDISTANCE
++    1: no tags. 0[.dev0]+untagged.DISTANCE.gHEX[.dirty]
+     """
+     if pieces["closest-tag"]:
+         rendered = pieces["closest-tag"]
++        if pieces["distance"] or pieces["dirty"]:
++            if pieces["branch"] != "master":
++                rendered += ".dev0"
++            rendered += plus_or_dot(pieces)
++            rendered += "%%d.g%%s" %% (pieces["distance"], pieces["short"])
++            if pieces["dirty"]:
++                rendered += ".dirty"
++    else:
++        # exception #1
++        rendered = "0"
++        if pieces["branch"] != "master":
++            rendered += ".dev0"
++        rendered += "+untagged.%%d.g%%s" %% (pieces["distance"],
++                                          pieces["short"])
++        if pieces["dirty"]:
++            rendered += ".dirty"
++    return rendered
++
++
++def pep440_split_post(ver: str) -> Tuple[str, Optional[int]]:
++    """Split pep440 version string at the post-release segment.
++
++    Returns the release segments before the post-release and the
++    post-release version number (or -1 if no post-release segment is present).
++    """
++    vc = str.split(ver, ".post")
++    return vc[0], int(vc[1] or 0) if len(vc) == 2 else None
++
++
++def render_pep440_pre(pieces: Dict[str, Any]) -> str:
++    """TAG[.postN.devDISTANCE] -- No -dirty.
++
++    Exceptions:
++    1: no tags. 0.post0.devDISTANCE
++    """
++    if pieces["closest-tag"]:
+         if pieces["distance"]:
+-            rendered += ".post.dev%%d" %% pieces["distance"]
++            # update the post release segment
++            tag_version, post_version = pep440_split_post(pieces["closest-tag"])
++            rendered = tag_version
++            if post_version is not None:
++                rendered += ".post%%d.dev%%d" %% (post_version + 1, pieces["distance"])
++            else:
++                rendered += ".post0.dev%%d" %% (pieces["distance"])
++        else:
++            # no commits, use the tag as the version
++            rendered = pieces["closest-tag"]
+     else:
+         # exception #1
+-        rendered = "0.post.dev%%d" %% pieces["distance"]
++        rendered = "0.post0.dev%%d" %% pieces["distance"]
+     return rendered
+ 
+ 
+-def render_pep440_post(pieces):
++def render_pep440_post(pieces: Dict[str, Any]) -> str:
+     """TAG[.postDISTANCE[.dev0]+gHEX] .
+ 
+     The ".dev0" means dirty. Note that .dev0 sorts backwards
+@@ -800,12 +1028,41 @@ def render_pep440_post(pieces):
+     return rendered
+ 
+ 
+-def render_pep440_old(pieces):
++def render_pep440_post_branch(pieces: Dict[str, Any]) -> str:
++    """TAG[.postDISTANCE[.dev0]+gHEX[.dirty]] .
++
++    The ".dev0" means not master branch.
++
++    Exceptions:
++    1: no tags. 0.postDISTANCE[.dev0]+gHEX[.dirty]
++    """
++    if pieces["closest-tag"]:
++        rendered = pieces["closest-tag"]
++        if pieces["distance"] or pieces["dirty"]:
++            rendered += ".post%%d" %% pieces["distance"]
++            if pieces["branch"] != "master":
++                rendered += ".dev0"
++            rendered += plus_or_dot(pieces)
++            rendered += "g%%s" %% pieces["short"]
++            if pieces["dirty"]:
++                rendered += ".dirty"
++    else:
++        # exception #1
++        rendered = "0.post%%d" %% pieces["distance"]
++        if pieces["branch"] != "master":
++            rendered += ".dev0"
++        rendered += "+g%%s" %% pieces["short"]
++        if pieces["dirty"]:
++            rendered += ".dirty"
++    return rendered
++
++
++def render_pep440_old(pieces: Dict[str, Any]) -> str:
+     """TAG[.postDISTANCE[.dev0]] .
+ 
+     The ".dev0" means dirty.
+ 
+-    Eexceptions:
++    Exceptions:
+     1: no tags. 0.postDISTANCE[.dev0]
+     """
+     if pieces["closest-tag"]:
+@@ -822,7 +1079,7 @@ def render_pep440_old(pieces):
+     return rendered
+ 
+ 
+-def render_git_describe(pieces):
++def render_git_describe(pieces: Dict[str, Any]) -> str:
+     """TAG[-DISTANCE-gHEX][-dirty].
+ 
+     Like 'git describe --tags --dirty --always'.
+@@ -842,7 +1099,7 @@ def render_git_describe(pieces):
+     return rendered
+ 
+ 
+-def render_git_describe_long(pieces):
++def render_git_describe_long(pieces: Dict[str, Any]) -> str:
+     """TAG-DISTANCE-gHEX[-dirty].
+ 
+     Like 'git describe --tags --dirty --always -long'.
+@@ -862,7 +1119,7 @@ def render_git_describe_long(pieces):
+     return rendered
+ 
+ 
+-def render(pieces, style):
++def render(pieces: Dict[str, Any], style: str) -> Dict[str, Any]:
+     """Render the given version pieces into the requested style."""
+     if pieces["error"]:
+         return {"version": "unknown",
+@@ -876,10 +1133,14 @@ def render(pieces, style):
+ 
+     if style == "pep440":
+         rendered = render_pep440(pieces)
++    elif style == "pep440-branch":
++        rendered = render_pep440_branch(pieces)
+     elif style == "pep440-pre":
+         rendered = render_pep440_pre(pieces)
+     elif style == "pep440-post":
+         rendered = render_pep440_post(pieces)
++    elif style == "pep440-post-branch":
++        rendered = render_pep440_post_branch(pieces)
+     elif style == "pep440-old":
+         rendered = render_pep440_old(pieces)
+     elif style == "git-describe":
+@@ -894,7 +1155,7 @@ def render(pieces, style):
+             "date": pieces.get("date")}
+ 
+ 
+-def get_versions():
++def get_versions() -> Dict[str, Any]:
+     """Get version information or return default if unable to do so."""
+     # I am in _version.py, which lives at ROOT/VERSIONFILE_SOURCE. If we have
+     # __file__, we can work backwards from there to the root. Some
+@@ -915,7 +1176,7 @@ def get_versions():
+         # versionfile_source is the relative path from the top of the source
+         # tree (where the .git directory might live) to this file. Invert
+         # this to find the root from __file__.
+-        for i in cfg.versionfile_source.split('/'):
++        for _ in cfg.versionfile_source.split('/'):
+             root = os.path.dirname(root)
+     except NameError:
+         return {"version": "0+unknown", "full-revisionid": None,
+@@ -942,41 +1203,48 @@ def get_versions():
+ 
+ 
+ @register_vcs_handler("git", "get_keywords")
+-def git_get_keywords(versionfile_abs):
++def git_get_keywords(versionfile_abs: str) -> Dict[str, str]:
+     """Extract version information from the given file."""
+     # the code embedded in _version.py can just fetch the value of these
+     # keywords. When used from setup.py, we don't want to import _version.py,
+     # so we do it with a regexp instead. This function is not used from
+     # _version.py.
+-    keywords = {}
++    keywords: Dict[str, str] = {}
+     try:
+-        f = open(versionfile_abs, "r")
+-        for line in f.readlines():
+-            if line.strip().startswith("git_refnames ="):
+-                mo = re.search(r'=\s*"(.*)"', line)
+-                if mo:
+-                    keywords["refnames"] = mo.group(1)
+-            if line.strip().startswith("git_full ="):
+-                mo = re.search(r'=\s*"(.*)"', line)
+-                if mo:
+-                    keywords["full"] = mo.group(1)
+-            if line.strip().startswith("git_date ="):
+-                mo = re.search(r'=\s*"(.*)"', line)
+-                if mo:
+-                    keywords["date"] = mo.group(1)
+-        f.close()
+-    except EnvironmentError:
++        with open(versionfile_abs, "r") as fobj:
++            for line in fobj:
++                if line.strip().startswith("git_refnames ="):
++                    mo = re.search(r'=\s*"(.*)"', line)
++                    if mo:
++                        keywords["refnames"] = mo.group(1)
++                if line.strip().startswith("git_full ="):
++                    mo = re.search(r'=\s*"(.*)"', line)
++                    if mo:
++                        keywords["full"] = mo.group(1)
++                if line.strip().startswith("git_date ="):
++                    mo = re.search(r'=\s*"(.*)"', line)
++                    if mo:
++                        keywords["date"] = mo.group(1)
++    except OSError:
+         pass
+     return keywords
+ 
+ 
+ @register_vcs_handler("git", "keywords")
+-def git_versions_from_keywords(keywords, tag_prefix, verbose):
++def git_versions_from_keywords(
++    keywords: Dict[str, str],
++    tag_prefix: str,
++    verbose: bool,
++) -> Dict[str, Any]:
+     """Get version information from git keywords."""
+-    if not keywords:
+-        raise NotThisMethod("no keywords at all, weird")
++    if "refnames" not in keywords:
++        raise NotThisMethod("Short version file found")
+     date = keywords.get("date")
+     if date is not None:
++        # Use only the last line.  Previous lines may contain GPG signature
++        # information.
++        date = date.splitlines()[-1]
++
+         # git-2.2.0 added "%cI", which expands to an ISO-8601 -compliant
+         # datestamp. However we prefer "%ci" (which expands to an "ISO-8601
+         # -like" string, which we must then edit to make compliant), because
+@@ -989,11 +1257,11 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose):
+         if verbose:
+             print("keywords are unexpanded, not using")
+         raise NotThisMethod("unexpanded keywords, not a git-archive tarball")
+-    refs = set([r.strip() for r in refnames.strip("()").split(",")])
++    refs = {r.strip() for r in refnames.strip("()").split(",")}
+     # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of
+     # just "foo-1.0". If we see a "tag: " prefix, prefer those.
+     TAG = "tag: "
+-    tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)])
++    tags = {r[len(TAG) :] for r in refs if r.startswith(TAG)}
+     if not tags:
+         # Either we're using git < 1.8.3, or there really are no tags. We use
+         # a heuristic: assume all version tags have a digit. The old git %d
+@@ -1002,7 +1270,7 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose):
+         # between branches and tags. By ignoring refnames without digits, we
+         # filter out many common branch names like "release" and
+         # "stabilization", as well as "HEAD" and "master".
+-        tags = set([r for r in refs if re.search(r'\d', r)])
++        tags = {r for r in refs if re.search(r"\d", r)}
+         if verbose:
+             print("discarding '%s', no digits" % ",".join(refs - tags))
+     if verbose:
+@@ -1010,23 +1278,37 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose):
+     for ref in sorted(tags):
+         # sorting will prefer e.g. "2.0" over "2.0rc1"
+         if ref.startswith(tag_prefix):
+-            r = ref[len(tag_prefix):]
++            r = ref[len(tag_prefix) :]
++            # Filter out refs that exactly match prefix or that don't start
++            # with a number once the prefix is stripped (mostly a concern
++            # when prefix is '')
++            if not re.match(r"\d", r):
++                continue
+             if verbose:
+                 print("picking %s" % r)
+-            return {"version": r,
+-                    "full-revisionid": keywords["full"].strip(),
+-                    "dirty": False, "error": None,
+-                    "date": date}
++            return {
++                "version": r,
++                "full-revisionid": keywords["full"].strip(),
++                "dirty": False,
++                "error": None,
++                "date": date,
++            }
+     # no suitable tags, so version is "0+unknown", but full hex is still there
+     if verbose:
+         print("no suitable tags, using unknown + full revision id")
+-    return {"version": "0+unknown",
+-            "full-revisionid": keywords["full"].strip(),
+-            "dirty": False, "error": "no suitable tags", "date": None}
++    return {
++        "version": "0+unknown",
++        "full-revisionid": keywords["full"].strip(),
++        "dirty": False,
++        "error": "no suitable tags",
++        "date": None,
++    }
+ 
+ 
+ @register_vcs_handler("git", "pieces_from_vcs")
+-def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
++def git_pieces_from_vcs(
++    tag_prefix: str, root: str, verbose: bool, runner: Callable = run_command
++) -> Dict[str, Any]:
+     """Get version from 'git describe' in the root of the source tree.
+ 
+     This only gets called if the git-archive 'subst' keywords were *not*
+@@ -1037,8 +1319,14 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
+     if sys.platform == "win32":
+         GITS = ["git.cmd", "git.exe"]
+ 
+-    out, rc = run_command(GITS, ["rev-parse", "--git-dir"], cwd=root,
+-                          hide_stderr=True)
++    # GIT_DIR can interfere with correct operation of Versioneer.
++    # It may be intended to be passed to the Versioneer-versioned project,
++    # but that should not change where we get our version from.
++    env = os.environ.copy()
++    env.pop("GIT_DIR", None)
++    runner = functools.partial(runner, env=env)
++
++    _, rc = runner(GITS, ["rev-parse", "--git-dir"], cwd=root, hide_stderr=not verbose)
+     if rc != 0:
+         if verbose:
+             print("Directory %s not under git control" % root)
+@@ -1046,24 +1334,65 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
+ 
+     # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty]
+     # if there isn't one, this yields HEX[-dirty] (no NUM)
+-    describe_out, rc = run_command(GITS, ["describe", "--tags", "--dirty",
+-                                          "--always", "--long",
+-                                          "--match", "%s*" % tag_prefix],
+-                                   cwd=root)
++    describe_out, rc = runner(
++        GITS,
++        [
++            "describe",
++            "--tags",
++            "--dirty",
++            "--always",
++            "--long",
++            "--match",
++            f"{tag_prefix}[[:digit:]]*",
++        ],
++        cwd=root,
++    )
+     # --long was added in git-1.5.5
+     if describe_out is None:
+         raise NotThisMethod("'git describe' failed")
+     describe_out = describe_out.strip()
+-    full_out, rc = run_command(GITS, ["rev-parse", "HEAD"], cwd=root)
++    full_out, rc = runner(GITS, ["rev-parse", "HEAD"], cwd=root)
+     if full_out is None:
+         raise NotThisMethod("'git rev-parse' failed")
+     full_out = full_out.strip()
+ 
+-    pieces = {}
++    pieces: Dict[str, Any] = {}
+     pieces["long"] = full_out
+     pieces["short"] = full_out[:7]  # maybe improved later
+     pieces["error"] = None
+ 
++    branch_name, rc = runner(GITS, ["rev-parse", "--abbrev-ref", "HEAD"], cwd=root)
++    # --abbrev-ref was added in git-1.6.3
++    if rc != 0 or branch_name is None:
++        raise NotThisMethod("'git rev-parse --abbrev-ref' returned error")
++    branch_name = branch_name.strip()
++
++    if branch_name == "HEAD":
++        # If we aren't exactly on a branch, pick a branch which represents
++        # the current commit. If all else fails, we are on a branchless
++        # commit.
++        branches, rc = runner(GITS, ["branch", "--contains"], cwd=root)
++        # --contains was added in git-1.5.4
++        if rc != 0 or branches is None:
++            raise NotThisMethod("'git branch --contains' returned error")
++        branches = branches.split("\n")
++
++        # Remove the first line if we're running detached
++        if "(" in branches[0]:
++            branches.pop(0)
++
++        # Strip off the leading "* " from the list of branches.
++        branches = [branch[2:] for branch in branches]
++        if "master" in branches:
++            branch_name = "master"
++        elif not branches:
++            branch_name = None
++        else:
++            # Pick the first branch that is returned. Good or bad.
++            branch_name = branches[0]
++
++    pieces["branch"] = branch_name
++
+     # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty]
+     # TAG might have hyphens.
+     git_describe = describe_out
+@@ -1072,17 +1401,16 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
+     dirty = git_describe.endswith("-dirty")
+     pieces["dirty"] = dirty
+     if dirty:
+-        git_describe = git_describe[:git_describe.rindex("-dirty")]
++        git_describe = git_describe[: git_describe.rindex("-dirty")]
+ 
+     # now we have TAG-NUM-gHEX or HEX
+ 
+     if "-" in git_describe:
+         # TAG-NUM-gHEX
+-        mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe)
++        mo = re.search(r"^(.+)-(\d+)-g([0-9a-f]+)$", git_describe)
+         if not mo:
+-            # unparseable. Maybe git-describe is misbehaving?
+-            pieces["error"] = ("unable to parse git-describe output: '%s'"
+-                               % describe_out)
++            # unparsable. Maybe git-describe is misbehaving?
++            pieces["error"] = "unable to parse git-describe output: '%s'" % describe_out
+             return pieces
+ 
+         # tag
+@@ -1091,10 +1419,12 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
+             if verbose:
+                 fmt = "tag '%s' doesn't start with prefix '%s'"
+                 print(fmt % (full_tag, tag_prefix))
+-            pieces["error"] = ("tag '%s' doesn't start with prefix '%s'"
+-                               % (full_tag, tag_prefix))
++            pieces["error"] = "tag '%s' doesn't start with prefix '%s'" % (
++                full_tag,
++                tag_prefix,
++            )
+             return pieces
+-        pieces["closest-tag"] = full_tag[len(tag_prefix):]
++        pieces["closest-tag"] = full_tag[len(tag_prefix) :]
+ 
+         # distance: number of commits since tag
+         pieces["distance"] = int(mo.group(2))
+@@ -1105,19 +1435,20 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
+     else:
+         # HEX: no tags
+         pieces["closest-tag"] = None
+-        count_out, rc = run_command(GITS, ["rev-list", "HEAD", "--count"],
+-                                    cwd=root)
+-        pieces["distance"] = int(count_out)  # total number of commits
++        out, rc = runner(GITS, ["rev-list", "HEAD", "--left-right"], cwd=root)
++        pieces["distance"] = len(out.split())  # total number of commits
+ 
+     # commit date: see ISO-8601 comment in git_versions_from_keywords()
+-    date = run_command(GITS, ["show", "-s", "--format=%ci", "HEAD"],
+-                       cwd=root)[0].strip()
++    date = runner(GITS, ["show", "-s", "--format=%ci", "HEAD"], cwd=root)[0].strip()
++    # Use only the last line.  Previous lines may contain GPG signature
++    # information.
++    date = date.splitlines()[-1]
+     pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1)
+ 
+     return pieces
+ 
+ 
+-def do_vcs_install(manifest_in, versionfile_source, ipy):
++def do_vcs_install(versionfile_source: str, ipy: Optional[str]) -> None:
+     """Git-specific installation logic for Versioneer.
+ 
+     For Git, this means creating/changing .gitattributes to mark _version.py
+@@ -1126,36 +1457,40 @@ def do_vcs_install(manifest_in, versionfile_source, ipy):
+     GITS = ["git"]
+     if sys.platform == "win32":
+         GITS = ["git.cmd", "git.exe"]
+-    files = [manifest_in, versionfile_source]
++    files = [versionfile_source]
+     if ipy:
+         files.append(ipy)
+-    try:
+-        me = __file__
+-        if me.endswith(".pyc") or me.endswith(".pyo"):
+-            me = os.path.splitext(me)[0] + ".py"
+-        versioneer_file = os.path.relpath(me)
+-    except NameError:
+-        versioneer_file = "versioneer.py"
+-    files.append(versioneer_file)
++    if "VERSIONEER_PEP518" not in globals():
++        try:
++            my_path = __file__
++            if my_path.endswith((".pyc", ".pyo")):
++                my_path = os.path.splitext(my_path)[0] + ".py"
++            versioneer_file = os.path.relpath(my_path)
++        except NameError:
++            versioneer_file = "versioneer.py"
++        files.append(versioneer_file)
+     present = False
+     try:
+-        f = open(".gitattributes", "r")
+-        for line in f.readlines():
+-            if line.strip().startswith(versionfile_source):
+-                if "export-subst" in line.strip().split()[1:]:
+-                    present = True
+-        f.close()
+-    except EnvironmentError:
++        with open(".gitattributes", "r") as fobj:
++            for line in fobj:
++                if line.strip().startswith(versionfile_source):
++                    if "export-subst" in line.strip().split()[1:]:
++                        present = True
++                        break
++    except OSError:
+         pass
+     if not present:
+-        f = open(".gitattributes", "a+")
+-        f.write("%s export-subst\n" % versionfile_source)
+-        f.close()
++        with open(".gitattributes", "a+") as fobj:
++            fobj.write(f"{versionfile_source} export-subst\n")
+         files.append(".gitattributes")
+     run_command(GITS, ["add", "--"] + files)
+ 
+ 
+-def versions_from_parentdir(parentdir_prefix, root, verbose):
++def versions_from_parentdir(
++    parentdir_prefix: str,
++    root: str,
++    verbose: bool,
++) -> Dict[str, Any]:
+     """Try to determine the version from the parent directory name.
+ 
+     Source tarballs conventionally unpack into a directory that includes both
+@@ -1164,24 +1499,29 @@ def versions_from_parentdir(parentdir_prefix, root, verbose):
+     """
+     rootdirs = []
+ 
+-    for i in range(3):
++    for _ in range(3):
+         dirname = os.path.basename(root)
+         if dirname.startswith(parentdir_prefix):
+-            return {"version": dirname[len(parentdir_prefix):],
+-                    "full-revisionid": None,
+-                    "dirty": False, "error": None, "date": None}
+-        else:
+-            rootdirs.append(root)
+-            root = os.path.dirname(root)  # up a level
++            return {
++                "version": dirname[len(parentdir_prefix) :],
++                "full-revisionid": None,
++                "dirty": False,
++                "error": None,
++                "date": None,
++            }
++        rootdirs.append(root)
++        root = os.path.dirname(root)  # up a level
+ 
+     if verbose:
+-        print("Tried directories %s but none started with prefix %s" %
+-              (str(rootdirs), parentdir_prefix))
++        print(
++            "Tried directories %s but none started with prefix %s"
++            % (str(rootdirs), parentdir_prefix)
++        )
+     raise NotThisMethod("rootdir doesn't start with parentdir_prefix")
+ 
+ 
+ SHORT_VERSION_PY = """
+-# This file was generated by 'versioneer.py' (0.18) from
++# This file was generated by 'versioneer.py' (0.29) from
+ # revision-control system data, or from the parent directory name of an
+ # unpacked source archive. Distribution tarballs contain a pre-generated copy
+ # of this file.
+@@ -1198,42 +1538,42 @@ def get_versions():
+ """
+ 
+ 
+-def versions_from_file(filename):
++def versions_from_file(filename: str) -> Dict[str, Any]:
+     """Try to determine the version from _version.py if present."""
+     try:
+         with open(filename) as f:
+             contents = f.read()
+-    except EnvironmentError:
++    except OSError:
+         raise NotThisMethod("unable to read _version.py")
+-    mo = re.search(r"version_json = '''\n(.*)'''  # END VERSION_JSON",
+-                   contents, re.M | re.S)
++    mo = re.search(
++        r"version_json = '''\n(.*)'''  # END VERSION_JSON", contents, re.M | re.S
++    )
+     if not mo:
+-        mo = re.search(r"version_json = '''\r\n(.*)'''  # END VERSION_JSON",
+-                       contents, re.M | re.S)
++        mo = re.search(
++            r"version_json = '''\r\n(.*)'''  # END VERSION_JSON", contents, re.M | re.S
++        )
+     if not mo:
+         raise NotThisMethod("no version_json in _version.py")
+     return json.loads(mo.group(1))
+ 
+ 
+-def write_to_version_file(filename, versions):
++def write_to_version_file(filename: str, versions: Dict[str, Any]) -> None:
+     """Write the given version number to the given _version.py file."""
+-    os.unlink(filename)
+-    contents = json.dumps(versions, sort_keys=True,
+-                          indent=1, separators=(",", ": "))
++    contents = json.dumps(versions, sort_keys=True, indent=1, separators=(",", ": "))
+     with open(filename, "w") as f:
+         f.write(SHORT_VERSION_PY % contents)
+ 
+     print("set %s to '%s'" % (filename, versions["version"]))
+ 
+ 
+-def plus_or_dot(pieces):
++def plus_or_dot(pieces: Dict[str, Any]) -> str:
+     """Return a + if we don't already have one, else return a ."""
+     if "+" in pieces.get("closest-tag", ""):
+         return "."
+     return "+"
+ 
+ 
+-def render_pep440(pieces):
++def render_pep440(pieces: Dict[str, Any]) -> str:
+     """Build up version string, with post-release "local version identifier".
+ 
+     Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you
+@@ -1251,30 +1591,76 @@ def render_pep440(pieces):
+                 rendered += ".dirty"
+     else:
+         # exception #1
+-        rendered = "0+untagged.%d.g%s" % (pieces["distance"],
+-                                          pieces["short"])
++        rendered = "0+untagged.%d.g%s" % (pieces["distance"], pieces["short"])
+         if pieces["dirty"]:
+             rendered += ".dirty"
+     return rendered
+ 
+ 
+-def render_pep440_pre(pieces):
+-    """TAG[.post.devDISTANCE] -- No -dirty.
++def render_pep440_branch(pieces: Dict[str, Any]) -> str:
++    """TAG[[.dev0]+DISTANCE.gHEX[.dirty]] .
++
++    The ".dev0" means not master branch. Note that .dev0 sorts backwards
++    (a feature branch will appear "older" than the master branch).
+ 
+     Exceptions:
+-    1: no tags. 0.post.devDISTANCE
++    1: no tags. 0[.dev0]+untagged.DISTANCE.gHEX[.dirty]
+     """
+     if pieces["closest-tag"]:
+         rendered = pieces["closest-tag"]
++        if pieces["distance"] or pieces["dirty"]:
++            if pieces["branch"] != "master":
++                rendered += ".dev0"
++            rendered += plus_or_dot(pieces)
++            rendered += "%d.g%s" % (pieces["distance"], pieces["short"])
++            if pieces["dirty"]:
++                rendered += ".dirty"
++    else:
++        # exception #1
++        rendered = "0"
++        if pieces["branch"] != "master":
++            rendered += ".dev0"
++        rendered += "+untagged.%d.g%s" % (pieces["distance"], pieces["short"])
++        if pieces["dirty"]:
++            rendered += ".dirty"
++    return rendered
++
++
++def pep440_split_post(ver: str) -> Tuple[str, Optional[int]]:
++    """Split pep440 version string at the post-release segment.
++
++    Returns the release segments before the post-release and the
++    post-release version number (or -1 if no post-release segment is present).
++    """
++    vc = str.split(ver, ".post")
++    return vc[0], int(vc[1] or 0) if len(vc) == 2 else None
++
++
++def render_pep440_pre(pieces: Dict[str, Any]) -> str:
++    """TAG[.postN.devDISTANCE] -- No -dirty.
++
++    Exceptions:
++    1: no tags. 0.post0.devDISTANCE
++    """
++    if pieces["closest-tag"]:
+         if pieces["distance"]:
+-            rendered += ".post.dev%d" % pieces["distance"]
++            # update the post release segment
++            tag_version, post_version = pep440_split_post(pieces["closest-tag"])
++            rendered = tag_version
++            if post_version is not None:
++                rendered += ".post%d.dev%d" % (post_version + 1, pieces["distance"])
++            else:
++                rendered += ".post0.dev%d" % (pieces["distance"])
++        else:
++            # no commits, use the tag as the version
++            rendered = pieces["closest-tag"]
+     else:
+         # exception #1
+-        rendered = "0.post.dev%d" % pieces["distance"]
++        rendered = "0.post0.dev%d" % pieces["distance"]
+     return rendered
+ 
+ 
+-def render_pep440_post(pieces):
++def render_pep440_post(pieces: Dict[str, Any]) -> str:
+     """TAG[.postDISTANCE[.dev0]+gHEX] .
+ 
+     The ".dev0" means dirty. Note that .dev0 sorts backwards
+@@ -1301,12 +1687,41 @@ def render_pep440_post(pieces):
+     return rendered
+ 
+ 
+-def render_pep440_old(pieces):
++def render_pep440_post_branch(pieces: Dict[str, Any]) -> str:
++    """TAG[.postDISTANCE[.dev0]+gHEX[.dirty]] .
++
++    The ".dev0" means not master branch.
++
++    Exceptions:
++    1: no tags. 0.postDISTANCE[.dev0]+gHEX[.dirty]
++    """
++    if pieces["closest-tag"]:
++        rendered = pieces["closest-tag"]
++        if pieces["distance"] or pieces["dirty"]:
++            rendered += ".post%d" % pieces["distance"]
++            if pieces["branch"] != "master":
++                rendered += ".dev0"
++            rendered += plus_or_dot(pieces)
++            rendered += "g%s" % pieces["short"]
++            if pieces["dirty"]:
++                rendered += ".dirty"
++    else:
++        # exception #1
++        rendered = "0.post%d" % pieces["distance"]
++        if pieces["branch"] != "master":
++            rendered += ".dev0"
++        rendered += "+g%s" % pieces["short"]
++        if pieces["dirty"]:
++            rendered += ".dirty"
++    return rendered
++
++
++def render_pep440_old(pieces: Dict[str, Any]) -> str:
+     """TAG[.postDISTANCE[.dev0]] .
+ 
+     The ".dev0" means dirty.
+ 
+-    Eexceptions:
++    Exceptions:
+     1: no tags. 0.postDISTANCE[.dev0]
+     """
+     if pieces["closest-tag"]:
+@@ -1323,7 +1738,7 @@ def render_pep440_old(pieces):
+     return rendered
+ 
+ 
+-def render_git_describe(pieces):
++def render_git_describe(pieces: Dict[str, Any]) -> str:
+     """TAG[-DISTANCE-gHEX][-dirty].
+ 
+     Like 'git describe --tags --dirty --always'.
+@@ -1343,7 +1758,7 @@ def render_git_describe(pieces):
+     return rendered
+ 
+ 
+-def render_git_describe_long(pieces):
++def render_git_describe_long(pieces: Dict[str, Any]) -> str:
+     """TAG-DISTANCE-gHEX[-dirty].
+ 
+     Like 'git describe --tags --dirty --always -long'.
+@@ -1363,24 +1778,30 @@ def render_git_describe_long(pieces):
+     return rendered
+ 
+ 
+-def render(pieces, style):
++def render(pieces: Dict[str, Any], style: str) -> Dict[str, Any]:
+     """Render the given version pieces into the requested style."""
+     if pieces["error"]:
+-        return {"version": "unknown",
+-                "full-revisionid": pieces.get("long"),
+-                "dirty": None,
+-                "error": pieces["error"],
+-                "date": None}
++        return {
++            "version": "unknown",
++            "full-revisionid": pieces.get("long"),
++            "dirty": None,
++            "error": pieces["error"],
++            "date": None,
++        }
+ 
+     if not style or style == "default":
+         style = "pep440"  # the default
+ 
+     if style == "pep440":
+         rendered = render_pep440(pieces)
++    elif style == "pep440-branch":
++        rendered = render_pep440_branch(pieces)
+     elif style == "pep440-pre":
+         rendered = render_pep440_pre(pieces)
+     elif style == "pep440-post":
+         rendered = render_pep440_post(pieces)
++    elif style == "pep440-post-branch":
++        rendered = render_pep440_post_branch(pieces)
+     elif style == "pep440-old":
+         rendered = render_pep440_old(pieces)
+     elif style == "git-describe":
+@@ -1390,16 +1811,20 @@ def render(pieces, style):
+     else:
+         raise ValueError("unknown style '%s'" % style)
+ 
+-    return {"version": rendered, "full-revisionid": pieces["long"],
+-            "dirty": pieces["dirty"], "error": None,
+-            "date": pieces.get("date")}
++    return {
++        "version": rendered,
++        "full-revisionid": pieces["long"],
++        "dirty": pieces["dirty"],
++        "error": None,
++        "date": pieces.get("date"),
++    }
+ 
+ 
+ class VersioneerBadRootError(Exception):
+     """The project root directory is unknown or missing key files."""
+ 
+ 
+-def get_versions(verbose=False):
++def get_versions(verbose: bool = False) -> Dict[str, Any]:
+     """Get the project version from whatever source is available.
+ 
+     Returns dict with two keys: 'version' and 'full'.
+@@ -1414,9 +1839,10 @@ def get_versions(verbose=False):
+     assert cfg.VCS is not None, "please set [versioneer]VCS= in setup.cfg"
+     handlers = HANDLERS.get(cfg.VCS)
+     assert handlers, "unrecognized VCS '%s'" % cfg.VCS
+-    verbose = verbose or cfg.verbose
+-    assert cfg.versionfile_source is not None, \
+-        "please set versioneer.versionfile_source"
++    verbose = verbose or bool(cfg.verbose)  # `bool()` used to avoid `None`
++    assert (
++        cfg.versionfile_source is not None
++    ), "please set versioneer.versionfile_source"
+     assert cfg.tag_prefix is not None, "please set versioneer.tag_prefix"
+ 
+     versionfile_abs = os.path.join(root, cfg.versionfile_source)
+@@ -1470,18 +1896,26 @@ def get_versions(verbose=False):
+     if verbose:
+         print("unable to compute version")
+ 
+-    return {"version": "0+unknown", "full-revisionid": None,
+-            "dirty": None, "error": "unable to compute version",
+-            "date": None}
++    return {
++        "version": "0+unknown",
++        "full-revisionid": None,
++        "dirty": None,
++        "error": "unable to compute version",
++        "date": None,
++    }
+ 
+ 
+-def get_version():
++def get_version() -> str:
+     """Get the short version string for this project."""
+     return get_versions()["version"]
+ 
+ 
+-def get_cmdclass():
+-    """Get the custom setuptools/distutils subclasses used by Versioneer."""
++def get_cmdclass(cmdclass: Optional[Dict[str, Any]] = None):
++    """Get the custom setuptools subclasses used by Versioneer.
++
++    If the package uses a different cmdclass (e.g. one from numpy), it
++    should be provide as an argument.
++    """
+     if "versioneer" in sys.modules:
+         del sys.modules["versioneer"]
+         # this fixes the "python setup.py develop" case (also 'install' and
+@@ -1495,25 +1929,25 @@ def get_cmdclass():
+         # parent is protected against the child's "import versioneer". By
+         # removing ourselves from sys.modules here, before the child build
+         # happens, we protect the child from the parent's versioneer too.
+-        # Also see https://github.com/warner/python-versioneer/issues/52
++        # Also see https://github.com/python-versioneer/python-versioneer/issues/52
+ 
+-    cmds = {}
++    cmds = {} if cmdclass is None else cmdclass.copy()
+ 
+-    # we add "version" to both distutils and setuptools
+-    from distutils.core import Command
++    # we add "version" to setuptools
++    from setuptools import Command
+ 
+     class cmd_version(Command):
+         description = "report generated version string"
+-        user_options = []
+-        boolean_options = []
++        user_options: List[Tuple[str, str, str]] = []
++        boolean_options: List[str] = []
+ 
+-        def initialize_options(self):
++        def initialize_options(self) -> None:
+             pass
+ 
+-        def finalize_options(self):
++        def finalize_options(self) -> None:
+             pass
+ 
+-        def run(self):
++        def run(self) -> None:
+             vers = get_versions(verbose=True)
+             print("Version: %s" % vers["version"])
+             print(" full-revisionid: %s" % vers.get("full-revisionid"))
+@@ -1521,9 +1955,10 @@ def get_cmdclass():
+             print(" date: %s" % vers.get("date"))
+             if vers["error"]:
+                 print(" error: %s" % vers["error"])
++
+     cmds["version"] = cmd_version
+ 
+-    # we override "build_py" in both distutils and setuptools
++    # we override "build_py" in setuptools
+     #
+     # most invocation pathways end up running build_py:
+     #  distutils/build -> build_py
+@@ -1538,29 +1973,71 @@ def get_cmdclass():
+     #   then does setup.py bdist_wheel, or sometimes setup.py install
+     #  setup.py egg_info -> ?
+ 
++    # pip install -e . and setuptool/editable_wheel will invoke build_py
++    # but the build_py command is not expected to copy any files.
++
+     # we override different "build_py" commands for both environments
+-    if "setuptools" in sys.modules:
+-        from setuptools.command.build_py import build_py as _build_py
++    if "build_py" in cmds:
++        _build_py: Any = cmds["build_py"]
+     else:
+-        from distutils.command.build_py import build_py as _build_py
++        from setuptools.command.build_py import build_py as _build_py
+ 
+     class cmd_build_py(_build_py):
+-        def run(self):
++        def run(self) -> None:
+             root = get_root()
+             cfg = get_config_from_root(root)
+             versions = get_versions()
+             _build_py.run(self)
++            if getattr(self, "editable_mode", False):
++                # During editable installs `.py` and data files are
++                # not copied to build_lib
++                return
+             # now locate _version.py in the new build/ directory and replace
+             # it with an updated value
+             if cfg.versionfile_build:
+-                target_versionfile = os.path.join(self.build_lib,
+-                                                  cfg.versionfile_build)
++                target_versionfile = os.path.join(self.build_lib, cfg.versionfile_build)
+                 print("UPDATING %s" % target_versionfile)
+                 write_to_version_file(target_versionfile, versions)
++
+     cmds["build_py"] = cmd_build_py
+ 
++    if "build_ext" in cmds:
++        _build_ext: Any = cmds["build_ext"]
++    else:
++        from setuptools.command.build_ext import build_ext as _build_ext
++
++    class cmd_build_ext(_build_ext):
++        def run(self) -> None:
++            root = get_root()
++            cfg = get_config_from_root(root)
++            versions = get_versions()
++            _build_ext.run(self)
++            if self.inplace:
++                # build_ext --inplace will only build extensions in
++                # build/lib<..> dir with no _version.py to write to.
++                # As in place builds will already have a _version.py
++                # in the module dir, we do not need to write one.
++                return
++            # now locate _version.py in the new build/ directory and replace
++            # it with an updated value
++            if not cfg.versionfile_build:
++                return
++            target_versionfile = os.path.join(self.build_lib, cfg.versionfile_build)
++            if not os.path.exists(target_versionfile):
++                print(
++                    f"Warning: {target_versionfile} does not exist, skipping "
++                    "version update. This can happen if you are running build_ext "
++                    "without first running build_py."
++                )
++                return
++            print("UPDATING %s" % target_versionfile)
++            write_to_version_file(target_versionfile, versions)
++
++    cmds["build_ext"] = cmd_build_ext
++
+     if "cx_Freeze" in sys.modules:  # cx_freeze enabled?
+-        from cx_Freeze.dist import build_exe as _build_exe
++        from cx_Freeze.dist import build_exe as _build_exe  # type: ignore
++
+         # nczeczulin reports that py2exe won't like the pep440-style string
+         # as FILEVERSION, but it can be used for PRODUCTVERSION, e.g.
+         # setup(console=[{
+@@ -1569,7 +2046,7 @@ def get_cmdclass():
+         #   ...
+ 
+         class cmd_build_exe(_build_exe):
+-            def run(self):
++            def run(self) -> None:
+                 root = get_root()
+                 cfg = get_config_from_root(root)
+                 versions = get_versions()
+@@ -1581,24 +2058,28 @@ def get_cmdclass():
+                 os.unlink(target_versionfile)
+                 with open(cfg.versionfile_source, "w") as f:
+                     LONG = LONG_VERSION_PY[cfg.VCS]
+-                    f.write(LONG %
+-                            {"DOLLAR": "$",
+-                             "STYLE": cfg.style,
+-                             "TAG_PREFIX": cfg.tag_prefix,
+-                             "PARENTDIR_PREFIX": cfg.parentdir_prefix,
+-                             "VERSIONFILE_SOURCE": cfg.versionfile_source,
+-                             })
++                    f.write(
++                        LONG
++                        % {
++                            "DOLLAR": "$",
++                            "STYLE": cfg.style,
++                            "TAG_PREFIX": cfg.tag_prefix,
++                            "PARENTDIR_PREFIX": cfg.parentdir_prefix,
++                            "VERSIONFILE_SOURCE": cfg.versionfile_source,
++                        }
++                    )
++
+         cmds["build_exe"] = cmd_build_exe
+         del cmds["build_py"]
+ 
+-    if 'py2exe' in sys.modules:  # py2exe enabled?
++    if "py2exe" in sys.modules:  # py2exe enabled?
+         try:
+-            from py2exe.distutils_buildexe import py2exe as _py2exe  # py3
++            from py2exe.setuptools_buildexe import py2exe as _py2exe  # type: ignore
+         except ImportError:
+-            from py2exe.build_exe import py2exe as _py2exe  # py2
++            from py2exe.distutils_buildexe import py2exe as _py2exe  # type: ignore
+ 
+         class cmd_py2exe(_py2exe):
+-            def run(self):
++            def run(self) -> None:
+                 root = get_root()
+                 cfg = get_config_from_root(root)
+                 versions = get_versions()
+@@ -1610,23 +2091,67 @@ def get_cmdclass():
+                 os.unlink(target_versionfile)
+                 with open(cfg.versionfile_source, "w") as f:
+                     LONG = LONG_VERSION_PY[cfg.VCS]
+-                    f.write(LONG %
+-                            {"DOLLAR": "$",
+-                             "STYLE": cfg.style,
+-                             "TAG_PREFIX": cfg.tag_prefix,
+-                             "PARENTDIR_PREFIX": cfg.parentdir_prefix,
+-                             "VERSIONFILE_SOURCE": cfg.versionfile_source,
+-                             })
++                    f.write(
++                        LONG
++                        % {
++                            "DOLLAR": "$",
++                            "STYLE": cfg.style,
++                            "TAG_PREFIX": cfg.tag_prefix,
++                            "PARENTDIR_PREFIX": cfg.parentdir_prefix,
++                            "VERSIONFILE_SOURCE": cfg.versionfile_source,
++                        }
++                    )
++
+         cmds["py2exe"] = cmd_py2exe
+ 
++    # sdist farms its file list building out to egg_info
++    if "egg_info" in cmds:
++        _egg_info: Any = cmds["egg_info"]
++    else:
++        from setuptools.command.egg_info import egg_info as _egg_info
++
++    class cmd_egg_info(_egg_info):
++        def find_sources(self) -> None:
++            # egg_info.find_sources builds the manifest list and writes it
++            # in one shot
++            super().find_sources()
++
++            # Modify the filelist and normalize it
++            root = get_root()
++            cfg = get_config_from_root(root)
++            self.filelist.append("versioneer.py")
++            if cfg.versionfile_source:
++                # There are rare cases where versionfile_source might not be
++                # included by default, so we must be explicit
++                self.filelist.append(cfg.versionfile_source)
++            self.filelist.sort()
++            self.filelist.remove_duplicates()
++
++            # The write method is hidden in the manifest_maker instance that
++            # generated the filelist and was thrown away
++            # We will instead replicate their final normalization (to unicode,
++            # and POSIX-style paths)
++            from setuptools import unicode_utils
++
++            normalized = [
++                unicode_utils.filesys_decode(f).replace(os.sep, "/")
++                for f in self.filelist.files
++            ]
++
++            manifest_filename = os.path.join(self.egg_info, "SOURCES.txt")
++            with open(manifest_filename, "w") as fobj:
++                fobj.write("\n".join(normalized))
++
++    cmds["egg_info"] = cmd_egg_info
++
+     # we override different "sdist" commands for both environments
+-    if "setuptools" in sys.modules:
+-        from setuptools.command.sdist import sdist as _sdist
++    if "sdist" in cmds:
++        _sdist: Any = cmds["sdist"]
+     else:
+-        from distutils.command.sdist import sdist as _sdist
++        from setuptools.command.sdist import sdist as _sdist
+ 
+     class cmd_sdist(_sdist):
+-        def run(self):
++        def run(self) -> None:
+             versions = get_versions()
+             self._versioneer_generated_versions = versions
+             # unless we update this, the command will keep using the old
+@@ -1634,7 +2159,7 @@ def get_cmdclass():
+             self.distribution.metadata.version = versions["version"]
+             return _sdist.run(self)
+ 
+-        def make_release_tree(self, base_dir, files):
++        def make_release_tree(self, base_dir: str, files: List[str]) -> None:
+             root = get_root()
+             cfg = get_config_from_root(root)
+             _sdist.make_release_tree(self, base_dir, files)
+@@ -1643,8 +2168,10 @@ def get_cmdclass():
+             # updated value
+             target_versionfile = os.path.join(base_dir, cfg.versionfile_source)
+             print("UPDATING %s" % target_versionfile)
+-            write_to_version_file(target_versionfile,
+-                                  self._versioneer_generated_versions)
++            write_to_version_file(
++                target_versionfile, self._versioneer_generated_versions
++            )
++
+     cmds["sdist"] = cmd_sdist
+ 
+     return cmds
+@@ -1687,23 +2214,26 @@ SAMPLE_CONFIG = """
+ 
+ """
+ 
+-INIT_PY_SNIPPET = """
++OLD_SNIPPET = """
+ from ._version import get_versions
+ __version__ = get_versions()['version']
+ del get_versions
+ """
+ 
++INIT_PY_SNIPPET = """
++from . import {0}
++__version__ = {0}.get_versions()['version']
++"""
++
+ 
+-def do_setup():
+-    """Main VCS-independent setup function for installing Versioneer."""
++def do_setup() -> int:
++    """Do main VCS-independent setup function for installing Versioneer."""
+     root = get_root()
+     try:
+         cfg = get_config_from_root(root)
+-    except (EnvironmentError, configparser.NoSectionError,
+-            configparser.NoOptionError) as e:
+-        if isinstance(e, (EnvironmentError, configparser.NoSectionError)):
+-            print("Adding sample versioneer config to setup.cfg",
+-                  file=sys.stderr)
++    except (OSError, configparser.NoSectionError, configparser.NoOptionError) as e:
++        if isinstance(e, (OSError, configparser.NoSectionError)):
++            print("Adding sample versioneer config to setup.cfg", file=sys.stderr)
+             with open(os.path.join(root, "setup.cfg"), "a") as f:
+                 f.write(SAMPLE_CONFIG)
+         print(CONFIG_ERROR, file=sys.stderr)
+@@ -1712,71 +2242,49 @@ def do_setup():
+     print(" creating %s" % cfg.versionfile_source)
+     with open(cfg.versionfile_source, "w") as f:
+         LONG = LONG_VERSION_PY[cfg.VCS]
+-        f.write(LONG % {"DOLLAR": "$",
+-                        "STYLE": cfg.style,
+-                        "TAG_PREFIX": cfg.tag_prefix,
+-                        "PARENTDIR_PREFIX": cfg.parentdir_prefix,
+-                        "VERSIONFILE_SOURCE": cfg.versionfile_source,
+-                        })
+-
+-    ipy = os.path.join(os.path.dirname(cfg.versionfile_source),
+-                       "__init__.py")
++        f.write(
++            LONG
++            % {
++                "DOLLAR": "$",
++                "STYLE": cfg.style,
++                "TAG_PREFIX": cfg.tag_prefix,
++                "PARENTDIR_PREFIX": cfg.parentdir_prefix,
++                "VERSIONFILE_SOURCE": cfg.versionfile_source,
++            }
++        )
++
++    ipy = os.path.join(os.path.dirname(cfg.versionfile_source), "__init__.py")
++    maybe_ipy: Optional[str] = ipy
+     if os.path.exists(ipy):
+         try:
+             with open(ipy, "r") as f:
+                 old = f.read()
+-        except EnvironmentError:
++        except OSError:
+             old = ""
+-        if INIT_PY_SNIPPET not in old:
++        module = os.path.splitext(os.path.basename(cfg.versionfile_source))[0]
++        snippet = INIT_PY_SNIPPET.format(module)
++        if OLD_SNIPPET in old:
++            print(" replacing boilerplate in %s" % ipy)
++            with open(ipy, "w") as f:
++                f.write(old.replace(OLD_SNIPPET, snippet))
++        elif snippet not in old:
+             print(" appending to %s" % ipy)
+             with open(ipy, "a") as f:
+-                f.write(INIT_PY_SNIPPET)
++                f.write(snippet)
+         else:
+             print(" %s unmodified" % ipy)
+     else:
+         print(" %s doesn't exist, ok" % ipy)
+-        ipy = None
+-
+-    # Make sure both the top-level "versioneer.py" and versionfile_source
+-    # (PKG/_version.py, used by runtime code) are in MANIFEST.in, so
+-    # they'll be copied into source distributions. Pip won't be able to
+-    # install the package without this.
+-    manifest_in = os.path.join(root, "MANIFEST.in")
+-    simple_includes = set()
+-    try:
+-        with open(manifest_in, "r") as f:
+-            for line in f:
+-                if line.startswith("include "):
+-                    for include in line.split()[1:]:
+-                        simple_includes.add(include)
+-    except EnvironmentError:
+-        pass
+-    # That doesn't cover everything MANIFEST.in can do
+-    # (http://docs.python.org/2/distutils/sourcedist.html#commands), so
+-    # it might give some false negatives. Appending redundant 'include'
+-    # lines is safe, though.
+-    if "versioneer.py" not in simple_includes:
+-        print(" appending 'versioneer.py' to MANIFEST.in")
+-        with open(manifest_in, "a") as f:
+-            f.write("include versioneer.py\n")
+-    else:
+-        print(" 'versioneer.py' already in MANIFEST.in")
+-    if cfg.versionfile_source not in simple_includes:
+-        print(" appending versionfile_source ('%s') to MANIFEST.in" %
+-              cfg.versionfile_source)
+-        with open(manifest_in, "a") as f:
+-            f.write("include %s\n" % cfg.versionfile_source)
+-    else:
+-        print(" versionfile_source already in MANIFEST.in")
++        maybe_ipy = None
+ 
+     # Make VCS-specific changes. For git, this means creating/changing
+     # .gitattributes to mark _version.py for export-subst keyword
+     # substitution.
+-    do_vcs_install(manifest_in, cfg.versionfile_source, ipy)
++    do_vcs_install(cfg.versionfile_source, maybe_ipy)
+     return 0
+ 
+ 
+-def scan_setup_py():
++def scan_setup_py() -> int:
+     """Validate the contents of setup.py against Versioneer's expectations."""
+     found = set()
+     setters = False
+@@ -1813,10 +2321,14 @@ def scan_setup_py():
+     return errors
+ 
+ 
++def setup_command() -> NoReturn:
++    """Set up Versioneer and exit with appropriate error code."""
++    errors = do_setup()
++    errors += scan_setup_py()
++    sys.exit(1 if errors else 0)
++
++
+ if __name__ == "__main__":
+     cmd = sys.argv[1]
+     if cmd == "setup":
+-        errors = do_setup()
+-        errors += scan_setup_py()
+-        if errors:
+-            sys.exit(1)
++        setup_command()
+-- 
+2.41.0
+
-- 
2.41.0


_______________________________________________
buildroot mailing list
buildroot@buildroot.org
https://lists.buildroot.org/mailman/listinfo/buildroot


^ permalink raw reply related	[flat|nested] 54+ messages in thread

* [Buildroot] [PATCH 25/30] package/python-iptables: use sysconfig.get_path instead of get_python_lib
  2023-10-26  9:26 [Buildroot] [PATCH 00/30] package/python3: bump version to 3.12.0 Adam Duskett
                   ` (23 preceding siblings ...)
  2023-10-26  9:26 ` [Buildroot] [PATCH 24/30] package/python-spake2: " Adam Duskett
@ 2023-10-26  9:26 ` Adam Duskett
  2023-11-04 21:36   ` Arnout Vandecappelle via buildroot
  2023-10-26  9:26 ` [Buildroot] [PATCH 26/30] package/python-pathtools: add 0001-replace-imp.patch Adam Duskett
                   ` (5 subsequent siblings)
  30 siblings, 1 reply; 54+ messages in thread
From: Adam Duskett @ 2023-10-26  9:26 UTC (permalink / raw)
  To: buildroot
  Cc: Adam Duskett, Andrey Smirnov, Julien Olivain, Asaf Kahlon,
	James Hilliard, Thomas Petazzoni, Mauro Condarelli

distutils is removed in Python 3.12.0. Switch to using sysconfig.get_path to
facilitate the migration.

Signed-off-by: Adam Duskett <adam.duskett@amarulasolutions.com>
---
 ...g-get_path-instead-of-get_python_lib.patch | 40 +++++++++++++++++++
 1 file changed, 40 insertions(+)
 create mode 100644 package/python-iptables/0001-use-sysconfig-get_path-instead-of-get_python_lib.patch

diff --git a/package/python-iptables/0001-use-sysconfig-get_path-instead-of-get_python_lib.patch b/package/python-iptables/0001-use-sysconfig-get_path-instead-of-get_python_lib.patch
new file mode 100644
index 0000000000..2a4de3daf2
--- /dev/null
+++ b/package/python-iptables/0001-use-sysconfig-get_path-instead-of-get_python_lib.patch
@@ -0,0 +1,40 @@
+From fd415a3613fad872062fb7cb4e271ac1476402ef Mon Sep 17 00:00:00 2001
+From: Adam Duskett <adam.duskett@amarulasolutions.com>
+Date: Tue, 24 Oct 2023 08:47:12 +0200
+Subject: [PATCH] use sysconfig.get_path instead of get_python_lib
+
+Distutils has been removed from python 3.12.0. Use sysconfig.get_path instead
+of get_python_lib.
+
+Upstream: https://github.com/ldx/python-iptables/pull/340
+
+Signed-off-by: Adam Duskett <adam.duskett@amarulasolutions.com>
+---
+ iptc/util.py | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/iptc/util.py b/iptc/util.py
+index 04fe905..94befc5 100644
+--- a/iptc/util.py
++++ b/iptc/util.py
+@@ -3,7 +3,7 @@ import os
+ import sys
+ import ctypes
+ import ctypes.util
+-from distutils.sysconfig import get_python_lib
++import sysconfig
+ from itertools import product
+ from subprocess import Popen, PIPE
+ from sys import version_info
+@@ -64,7 +64,7 @@ def _do_find_library(name):
+ 
+     # probably we have been installed in a virtualenv
+     try:
+-        lib = ctypes.CDLL(os.path.join(get_python_lib(), name),
++        lib = ctypes.CDLL(os.path.join(sysconfig.get_path("purelib"), name),
+                           mode=ctypes.RTLD_GLOBAL)
+         return lib
+     except:
+-- 
+2.41.0
+
-- 
2.41.0

_______________________________________________
buildroot mailing list
buildroot@buildroot.org
https://lists.buildroot.org/mailman/listinfo/buildroot

^ permalink raw reply related	[flat|nested] 54+ messages in thread

* [Buildroot] [PATCH 26/30] package/python-pathtools: add 0001-replace-imp.patch
  2023-10-26  9:26 [Buildroot] [PATCH 00/30] package/python3: bump version to 3.12.0 Adam Duskett
                   ` (24 preceding siblings ...)
  2023-10-26  9:26 ` [Buildroot] [PATCH 25/30] package/python-iptables: use sysconfig.get_path instead of get_python_lib Adam Duskett
@ 2023-10-26  9:26 ` Adam Duskett
  2023-11-04 21:36   ` Arnout Vandecappelle via buildroot
  2023-10-26  9:26 ` [Buildroot] [PATCH 27/30] package/python-pyxb: Drop package Adam Duskett
                   ` (4 subsequent siblings)
  30 siblings, 1 reply; 54+ messages in thread
From: Adam Duskett @ 2023-10-26  9:26 UTC (permalink / raw)
  To: buildroot
  Cc: Adam Duskett, Andrey Smirnov, Julien Olivain, Asaf Kahlon,
	James Hilliard, Thomas Petazzoni, Mauro Condarelli

In preperation of Python 3.12.0, add a patch to python-pathtools that removes
the reliance on the imp module following the instructions found here:
https://docs.python.org/3.12/whatsnew/3.12.html#removed

Also tested with Python 3.9.2 on Debian 11.

Signed-off-by: Adam Duskett <adam.duskett@amarulasolutions.com>
---
 .../python-pathtools/0001-replace-imp.patch   | 55 +++++++++++++++++++
 1 file changed, 55 insertions(+)
 create mode 100644 package/python-pathtools/0001-replace-imp.patch

diff --git a/package/python-pathtools/0001-replace-imp.patch b/package/python-pathtools/0001-replace-imp.patch
new file mode 100644
index 0000000000..a5bc240cea
--- /dev/null
+++ b/package/python-pathtools/0001-replace-imp.patch
@@ -0,0 +1,55 @@
+From e2372bbecdf46a100b09126f2951431c1929637b Mon Sep 17 00:00:00 2001
+From: Adam Duskett <adam.duskett@amarulasolutions.com>
+Date: Tue, 24 Oct 2023 08:59:21 +0200
+Subject: [PATCH] Replace imp
+
+The imp module has been removed in python 3.12.0.
+
+This change has also been tested with Python 3.9.2 on Debian 11.
+
+From: https://docs.python.org/3.12/whatsnew/3.12.html#removed, follow the
+instructions to add the load_source method back into setup.py.
+
+Upstream: https://github.com/gorakhargosh/pathtools/pull/14
+
+Signed-off-by: Adam Duskett <adam.duskett@amarulasolutions.com>
+---
+ setup.py | 19 ++++++++++++++++---
+ 1 file changed, 16 insertions(+), 3 deletions(-)
+
+diff --git a/setup.py b/setup.py
+index 4718885..1be0315 100644
+--- a/setup.py
++++ b/setup.py
+@@ -22,12 +22,25 @@
+ # THE SOFTWARE.
+ 
+ import os
+-import imp
++import importlib.util
++import importlib.machinery
+ from setuptools import setup
+ 
+ PKG_DIR = 'pathtools'
+-version = imp.load_source('version',
+-                          os.path.join(PKG_DIR, 'version.py'))
++
++# From: https://docs.python.org/3.12/whatsnew/3.12.html#removed
++def load_source(modname, filename):
++    loader = importlib.machinery.SourceFileLoader(modname, filename)
++    spec = importlib.util.spec_from_file_location(modname, filename, loader=loader)
++    module = importlib.util.module_from_spec(spec)
++    # The module is always executed and not cached in sys.modules.
++    # Uncomment the following line to cache the module.
++    # sys.modules[module.__name__] = module
++    loader.exec_module(module)
++    return module
++
++version = load_source('version',
++                      os.path.join(PKG_DIR, 'version.py'))
+ 
+ def read_file(filename):
+     """
+-- 
+2.41.0
+
-- 
2.41.0

_______________________________________________
buildroot mailing list
buildroot@buildroot.org
https://lists.buildroot.org/mailman/listinfo/buildroot

^ permalink raw reply related	[flat|nested] 54+ messages in thread

* [Buildroot] [PATCH 27/30] package/python-pyxb: Drop package
  2023-10-26  9:26 [Buildroot] [PATCH 00/30] package/python3: bump version to 3.12.0 Adam Duskett
                   ` (25 preceding siblings ...)
  2023-10-26  9:26 ` [Buildroot] [PATCH 26/30] package/python-pathtools: add 0001-replace-imp.patch Adam Duskett
@ 2023-10-26  9:26 ` Adam Duskett
  2023-10-26  9:26 ` [Buildroot] [PATCH 28/30] package/python-pygame: drop package Adam Duskett
                   ` (3 subsequent siblings)
  30 siblings, 0 replies; 54+ messages in thread
From: Adam Duskett @ 2023-10-26  9:26 UTC (permalink / raw)
  To: buildroot
  Cc: Adam Duskett, Andrey Smirnov, Julien Olivain, Asaf Kahlon,
	James Hilliard, Thomas Petazzoni, Mauro Condarelli

The last time python-pyxb was updated according to pypi.org is in 2017.
As there are no maintainers listed for the package, and the package uses
distutils which has been removed in Python 3.12.0, remove the package.

Signed-off-by: Adam Duskett <adam.duskett@amarulasolutions.com>
---
 Config.in.legacy                     |  7 +++++++
 package/python-pyxb/Config.in        |  8 --------
 package/python-pyxb/python-pyxb.hash |  4 ----
 package/python-pyxb/python-pyxb.mk   | 14 --------------
 4 files changed, 7 insertions(+), 26 deletions(-)
 delete mode 100644 package/python-pyxb/Config.in
 delete mode 100644 package/python-pyxb/python-pyxb.hash
 delete mode 100644 package/python-pyxb/python-pyxb.mk

diff --git a/Config.in.legacy b/Config.in.legacy
index ca80279212..8e406f2b13 100644
--- a/Config.in.legacy
+++ b/Config.in.legacy
@@ -146,6 +146,13 @@ endif
 
 comment "Legacy options removed in 2023.11"
 
+config BR2_PACKAGE_PYTHON_PYXB
+	bool "python-pyxb removed"
+	select BR2_LEGACY
+	help
+	  python-pyxb has been removed due to being abandoned and
+	  distutils no longer being supported in python 3.12.0.
+
 config BR2_KERNEL_HEADERS_6_4
 	bool "kernel headers version 6.4.x are no longer supported"
 	select BR2_LEGACY
diff --git a/package/python-pyxb/Config.in b/package/python-pyxb/Config.in
deleted file mode 100644
index 6555ed80b6..0000000000
--- a/package/python-pyxb/Config.in
+++ /dev/null
@@ -1,8 +0,0 @@
-config BR2_PACKAGE_PYTHON_PYXB
-	bool "python-pyxb"
-	help
-	  PyXB is a pure Python package that generates Python code for
-	  classes that correspond to data structures defined by
-	  XMLSchema.
-
-	  http://pyxb.sourceforge.net/
diff --git a/package/python-pyxb/python-pyxb.hash b/package/python-pyxb/python-pyxb.hash
deleted file mode 100644
index f24cd42e5a..0000000000
--- a/package/python-pyxb/python-pyxb.hash
+++ /dev/null
@@ -1,4 +0,0 @@
-# md5 from https://pypi.python.org/pypi/PyXB/json, sha256 locally computed
-md5  4303573fc7094ce4664c5b71cd4bdb48  PyXB-1.2.6.tar.gz
-sha256  2a00f38dd1d87b88f92d79bc5a09718d730419b88e814545f472bbd5a3bf27b4  PyXB-1.2.6.tar.gz
-sha256  cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30  LICENSE
diff --git a/package/python-pyxb/python-pyxb.mk b/package/python-pyxb/python-pyxb.mk
deleted file mode 100644
index 6af10b51e7..0000000000
--- a/package/python-pyxb/python-pyxb.mk
+++ /dev/null
@@ -1,14 +0,0 @@
-################################################################################
-#
-# python-pyxb
-#
-################################################################################
-
-PYTHON_PYXB_VERSION = 1.2.6
-PYTHON_PYXB_SOURCE = PyXB-$(PYTHON_PYXB_VERSION).tar.gz
-PYTHON_PYXB_SITE = https://pypi.python.org/packages/e3/09/4fdb190ea2b7cb43d6d3e745276ee69f4d6181be70fcbfda7df3c5f72f0e
-PYTHON_PYXB_LICENSE = Apache-2.0
-PYTHON_PYXB_LICENSE_FILES = LICENSE
-PYTHON_PYXB_SETUP_TYPE = distutils
-
-$(eval $(python-package))
-- 
2.41.0

_______________________________________________
buildroot mailing list
buildroot@buildroot.org
https://lists.buildroot.org/mailman/listinfo/buildroot

^ permalink raw reply related	[flat|nested] 54+ messages in thread

* [Buildroot] [PATCH 28/30] package/python-pygame: drop package
  2023-10-26  9:26 [Buildroot] [PATCH 00/30] package/python3: bump version to 3.12.0 Adam Duskett
                   ` (26 preceding siblings ...)
  2023-10-26  9:26 ` [Buildroot] [PATCH 27/30] package/python-pyxb: Drop package Adam Duskett
@ 2023-10-26  9:26 ` Adam Duskett
  2023-10-26 14:13   ` Marcus Hoffmann via buildroot
  2023-10-26  9:27 ` [Buildroot] [PATCH 29/30] package/python-crossbar: " Adam Duskett
                   ` (2 subsequent siblings)
  30 siblings, 1 reply; 54+ messages in thread
From: Adam Duskett @ 2023-10-26  9:26 UTC (permalink / raw)
  To: buildroot
  Cc: Adam Duskett, Andrey Smirnov, Julien Olivain, Asaf Kahlon,
	James Hilliard, Thomas Petazzoni, Mauro Condarelli

The python-pygame package has not recieved any update since
Sun May 1 22:15:17 2016 (commit: a9ec96e545102ae5ccd4280323d35360b0a5072d)

As python 3.12.0 no longer supports distutils, drop the package as the package
is clearly unmaintained.

Signed-off-by: Adam Duskett <adam.duskett@amarulasolutions.com>
---
 Config.in.legacy                         |   7 ++
 package/Config.in                        |   2 -
 package/python-pygame/Config.in          |  50 ----------
 package/python-pygame/python-pygame.hash |   3 -
 package/python-pygame/python-pygame.mk   | 111 -----------------------
 5 files changed, 7 insertions(+), 166 deletions(-)
 delete mode 100644 package/python-pygame/Config.in
 delete mode 100644 package/python-pygame/python-pygame.hash
 delete mode 100644 package/python-pygame/python-pygame.mk

diff --git a/Config.in.legacy b/Config.in.legacy
index 8e406f2b13..f1d5f345c3 100644
--- a/Config.in.legacy
+++ b/Config.in.legacy
@@ -153,6 +153,13 @@ config BR2_PACKAGE_PYTHON_PYXB
 	  python-pyxb has been removed due to being abandoned and
 	  distutils no longer being supported in python 3.12.0.
 
+config BR2_PACKAGE_PYTHON_PYGAME
+	bool "python-pygame removed"
+	select BR2_LEGACY
+	help
+	  python-pygame has been removed due to being abandoned and
+	  distutils no longer being supported in python 3.12.0.
+
 config BR2_KERNEL_HEADERS_6_4
 	bool "kernel headers version 6.4.x are no longer supported"
 	select BR2_LEGACY
diff --git a/package/Config.in b/package/Config.in
index 4e489c4706..d756094c4b 100644
--- a/package/Config.in
+++ b/package/Config.in
@@ -1228,7 +1228,6 @@ menu "External python modules"
 	source "package/python-pydyf/Config.in"
 	source "package/python-pyelftools/Config.in"
 	source "package/python-pyftpdlib/Config.in"
-	source "package/python-pygame/Config.in"
 	source "package/python-pygments/Config.in"
 	source "package/python-pyhamcrest/Config.in"
 	source "package/python-pyicu/Config.in"
@@ -1267,7 +1266,6 @@ menu "External python modules"
 	source "package/python-pytz/Config.in"
 	source "package/python-pyudev/Config.in"
 	source "package/python-pyusb/Config.in"
-	source "package/python-pyxb/Config.in"
 	source "package/python-pyyaml/Config.in"
 	source "package/python-pyzmq/Config.in"
 	source "package/python-qrcode/Config.in"
diff --git a/package/python-pygame/Config.in b/package/python-pygame/Config.in
deleted file mode 100644
index 57eb020742..0000000000
--- a/package/python-pygame/Config.in
+++ /dev/null
@@ -1,50 +0,0 @@
-config BR2_PACKAGE_PYTHON_PYGAME
-	bool "python-pygame"
-	select BR2_PACKAGE_SDL
-	help
-	  Pygame is a cross-platfrom library designed to make it easy
-	  to write multimedia software, such as games, in
-	  Python. Pygame requires the Python language and SDL
-	  multimedia library.
-	  It can also make use of several other popular libraries.
-
-	  http://www.pygame.org/
-
-if BR2_PACKAGE_PYTHON_PYGAME
-config BR2_PACKAGE_PYTHON_PYGAME_IMAGE
-	bool "pygame.image"
-	select BR2_PACKAGE_SDL_IMAGE
-	select BR2_PACKAGE_SDL_IMAGE_PNG
-	select BR2_PACKAGE_SDL_IMAGE_JPEG
-	help
-	  pygame module for loading, saving and transfering images.
-	  Will autoselect sdl_image with png and jpeg support.
-
-config BR2_PACKAGE_PYTHON_PYGAME_EXAMPLES
-	bool "pygame.examples"
-	help
-	  Include examples.
-	  Selecting this option adds about 1.5 MB to the target file
-	  system.
-
-config BR2_PACKAGE_PYTHON_PYGAME_FONT
-	bool "pygame.font"
-	select BR2_PACKAGE_SDL_TTF
-	help
-	  pygame module for loading and rendering fonts.
-	  Will autoselect sdl_ttf.
-
-config BR2_PACKAGE_PYTHON_PYGAME_MIXER
-	bool "pygame.mixer"
-	select BR2_PACKAGE_SDL_MIXER
-	help
-	  pygame module for loading and playing sounds.
-	  Will autoselect sdl_mixer.
-
-config BR2_PACKAGE_PYTHON_PYGAME_SCRAP
-	bool "pygame.scrap"
-	depends on BR2_PACKAGE_SDL_X11
-	help
-	  pygame module for clipboard support (X11 needed)
-
-endif
diff --git a/package/python-pygame/python-pygame.hash b/package/python-pygame/python-pygame.hash
deleted file mode 100644
index c0496515e2..0000000000
--- a/package/python-pygame/python-pygame.hash
+++ /dev/null
@@ -1,3 +0,0 @@
-# Locally computed
-sha256  f95a7dd68ea294d415e36e068d2f533c5a01c67773452d14a535c5c7455681fe  pygame-d61ea8eabd56.tar.gz
-sha256  a190dc9c8043755d90f8b0a75fa66b9e42d4af4c980bf5ddc633f0124db3cee7  LGPL
diff --git a/package/python-pygame/python-pygame.mk b/package/python-pygame/python-pygame.mk
deleted file mode 100644
index 600dd9e743..0000000000
--- a/package/python-pygame/python-pygame.mk
+++ /dev/null
@@ -1,111 +0,0 @@
-################################################################################
-#
-# python-pygame
-#
-################################################################################
-
-# stable 1.9.1 release requires V4L which has been wiped out of recent Linux
-# kernels, so use latest mercurial revision until next stable release is out.
-PYTHON_PYGAME_VERSION = d61ea8eabd56
-PYTHON_PYGAME_SOURCE = pygame-$(PYTHON_PYGAME_VERSION).tar.gz
-PYTHON_PYGAME_SITE = https://bitbucket.org/pygame/pygame
-PYTHON_PYGAME_SITE_METHOD = hg
-PYTHON_PYGAME_SETUP_TYPE = distutils
-PYTHON_PYGAME_LICENSE = LGPL-2.1+
-PYTHON_PYGAME_LICENSE_FILES = LGPL
-
-ifeq ($(BR2_PACKAGE_PYTHON_PYGAME_IMAGE),y)
-PYTHON_PYGAME_OPT_DEPENDS += sdl_image
-endif
-
-ifeq ($(BR2_PACKAGE_PYTHON_PYGAME_FONT),y)
-PYTHON_PYGAME_OPT_DEPENDS += sdl_ttf
-endif
-
-ifeq ($(BR2_PACKAGE_PYTHON_PYGAME_MIXER),y)
-PYTHON_PYGAME_OPT_DEPENDS += sdl_mixer
-endif
-
-PYTHON_PYGAME_DEPENDENCIES = sdl $(PYTHON_PYGAME_OPT_DEPENDS)
-
-ifneq ($(BR2_PACKAGE_PYTHON_PYGAME_IMAGE),y)
-define PYTHON_PYGAME_UNCONFIGURE_IMAGE
-	$(SED) 's/^imageext/#imageext/' $(@D)/Setup
-endef
-endif
-
-ifneq ($(BR2_PACKAGE_PYTHON_PYGAME_FONT),y)
-define PYTHON_PYGAME_UNCONFIGURE_FONT
-	$(SED) 's/^font/#font/' $(@D)/Setup
-endef
-endif
-
-ifneq ($(BR2_PACKAGE_PYTHON_PYGAME_MIXER),y)
-define PYTHON_PYGAME_UNCONFIGURE_MIXER
-	$(SED) 's/^mixer/#mixer/g' $(@D)/Setup
-endef
-endif
-
-# Both require numpy or numeric python module
-define PYTHON_PYGAME_UNCONFIGURE_SNDARRAY
-	$(SED) 's/^_numericsndarray/#_numericsndarray/' $(@D)/Setup
-endef
-
-define PYTHON_PYGAME_UNCONFIGURE_SURFARRAY
-	$(SED) 's/^_numericsurfarray/#_numericsurfarray/' $(@D)/Setup
-endef
-
-# Requires smpeg
-define PYTHON_PYGAME_UNCONFIGURE_MOVIE
-	$(SED) 's/^movie/#movie/' $(@D)/Setup
-endef
-
-ifneq ($(BR2_PACKAGE_PYTHON_PYGAME_SCRAP),y)
-define PYTHON_PYGAME_UNCONFIGURE_SCRAP
-	$(SED) 's/^scrap/#scrap/' $(@D)/Setup
-endef
-endif
-
-define PYTHON_PYGAME_UNCONFIGURE_FREETYPE
-	$(SED) 's/^_freetype/#_freetype/' $(@D)/Setup
-endef
-
-PYTHON_PYGAME_SDL_FLAGS = `$(STAGING_DIR)/usr/bin/sdl-config --cflags`
-PYTHON_PYGAME_SDL_FLAGS += `$(STAGING_DIR)/usr/bin/sdl-config --libs`
-
-# Pygame needs a Setup file where options should be commented out if
-# dependencies are not available
-define PYTHON_PYGAME_CONFIGURE_CMDS
-	cp -f $(@D)/Setup.in $(@D)/Setup
-	$(SED) "s~^SDL = ~SDL = $(PYTHON_PYGAME_SDL_FLAGS) \n#~" $(@D)/Setup
-	$(SED) 's/^pypm/#pypm/' $(@D)/Setup
-	$(PYTHON_PYGAME_UNCONFIGURE_IMAGE)
-	$(PYTHON_PYGAME_UNCONFIGURE_FONT)
-	$(PYTHON_PYGAME_UNCONFIGURE_MIXER)
-	$(PYTHON_PYGAME_UNCONFIGURE_SNDARRAY)
-	$(PYTHON_PYGAME_UNCONFIGURE_SURFARRAY)
-	$(PYTHON_PYGAME_UNCONFIGURE_MOVIE)
-	$(PYTHON_PYGAME_UNCONFIGURE_SCRAP)
-	$(PYTHON_PYGAME_UNCONFIGURE_FREETYPE)
-endef
-
-define PYTHON_PYGAME_REMOVE_DOC
-	rm -rf $(TARGET_DIR)/usr/lib/python*/site-packages/pygame/docs
-endef
-
-PYTHON_PYGAME_POST_INSTALL_TARGET_HOOKS += PYTHON_PYGAME_REMOVE_DOC
-
-define PYTHON_PYGAME_REMOVE_TESTS
-	rm -rf $(TARGET_DIR)/usr/lib/python*/site-packages/pygame/tests
-endef
-
-PYTHON_PYGAME_POST_INSTALL_TARGET_HOOKS += PYTHON_PYGAME_REMOVE_TESTS
-
-ifneq ($(BR2_PACKAGE_PYTHON_PYGAME_EXAMPLES),y)
-define PYTHON_PYGAME_REMOVE_EXAMPLES
-	rm -rf $(TARGET_DIR)/usr/lib/python$(PYTHON3_VERSION_MAJOR)/site-packages/pygame/examples
-endef
-PYTHON_PYGAME_POST_INSTALL_TARGET_HOOKS += PYTHON_PYGAME_REMOVE_EXAMPLES
-endif
-
-$(eval $(python-package))
-- 
2.41.0

_______________________________________________
buildroot mailing list
buildroot@buildroot.org
https://lists.buildroot.org/mailman/listinfo/buildroot

^ permalink raw reply related	[flat|nested] 54+ messages in thread

* [Buildroot] [PATCH 29/30] package/python-crossbar: drop package
  2023-10-26  9:26 [Buildroot] [PATCH 00/30] package/python3: bump version to 3.12.0 Adam Duskett
                   ` (27 preceding siblings ...)
  2023-10-26  9:26 ` [Buildroot] [PATCH 28/30] package/python-pygame: drop package Adam Duskett
@ 2023-10-26  9:27 ` Adam Duskett
  2023-10-26  9:27 ` [Buildroot] [PATCH 30/30] package/python3: bump version to 3.12.0 Adam Duskett
  2023-11-04 17:41 ` [Buildroot] [PATCH 00/30] " Arnout Vandecappelle via buildroot
  30 siblings, 0 replies; 54+ messages in thread
From: Adam Duskett @ 2023-10-26  9:27 UTC (permalink / raw)
  To: buildroot
  Cc: Adam Duskett, Andrey Smirnov, Julien Olivain, Asaf Kahlon,
	James Hilliard, Thomas Petazzoni, Mauro Condarelli

python-crossbar has been removed. The current package has not recieved an
update since Sat Oct 9 2021 33ece2446e25e20929d1c7eefa9f3244a3b79a92 and is not
python 3.12.0 compatible.

Furthermore, the current version requires at least 42 new packages worth of
depedencies of which several require patches to be python 3.12.0 compatible.
As nobody has stepped up to maintain the package and its ever-growing list of
dependencies, along with the other problems, it is time to drop the package.

Signed-off-by: Adam Duskett <adam.duskett@amarulasolutions.com>
---
 Config.in.legacy                              | 16 ++++
 package/Config.in                             |  1 -
 .../0001-Avoid-intentional-syntax-error.patch | 29 --------
 ...s-min.txt-drop-indirect-dependencies.patch | 74 -------------------
 ...ice-wap-use-markupsafe-instead-of-we.patch | 53 -------------
 package/python-crossbar/Config.in             | 71 ------------------
 package/python-crossbar/python-crossbar.hash  |  5 --
 package/python-crossbar/python-crossbar.mk    | 14 ----
 .../tests/package/sample_python_crossbar.py   |  4 -
 .../tests/package/test_python_crossbar.py     | 23 ------
 10 files changed, 16 insertions(+), 274 deletions(-)
 delete mode 100644 package/python-crossbar/0001-Avoid-intentional-syntax-error.patch
 delete mode 100644 package/python-crossbar/0002-requirements-min.txt-drop-indirect-dependencies.patch
 delete mode 100644 package/python-crossbar/0003-crossbar-webservice-wap-use-markupsafe-instead-of-we.patch
 delete mode 100644 package/python-crossbar/Config.in
 delete mode 100644 package/python-crossbar/python-crossbar.hash
 delete mode 100644 package/python-crossbar/python-crossbar.mk
 delete mode 100644 support/testing/tests/package/sample_python_crossbar.py
 delete mode 100644 support/testing/tests/package/test_python_crossbar.py

diff --git a/Config.in.legacy b/Config.in.legacy
index f1d5f345c3..607e0486c9 100644
--- a/Config.in.legacy
+++ b/Config.in.legacy
@@ -146,6 +146,22 @@ endif
 
 comment "Legacy options removed in 2023.11"
 
+config BR2_PACKAGE_PYTHON_CROSSBAR
+	bool "python-crossbar removed"
+	select BR2_LEGACY
+	help
+	  python-crossbar has been removed. The current package has
+	  not recieved an update since Sat Oct 9 13:55:06 2021
+	  commit: 33ece2446e25e20929d1c7eefa9f3244a3b79a92
+	  and is not python 3.12.0 compatible.
+
+	  Furthermore, the current version requires at least 42 new
+	  packages worth of depedencies of which several require
+	  patches to be python 3.12.0 compatible. As nobody has
+	  stepped up to maintain the package and its ever-growing
+	  list of dependencies, along with the other problems, it
+	  is time to drop the package.
+
 config BR2_PACKAGE_PYTHON_PYXB
 	bool "python-pyxb removed"
 	select BR2_LEGACY
diff --git a/package/Config.in b/package/Config.in
index d756094c4b..b7ae35bd89 100644
--- a/package/Config.in
+++ b/package/Config.in
@@ -1034,7 +1034,6 @@ menu "External python modules"
 	source "package/python-crc16/Config.in"
 	source "package/python-crcmod/Config.in"
 	source "package/python-crontab/Config.in"
-	source "package/python-crossbar/Config.in"
 	source "package/python-cryptography/Config.in"
 	source "package/python-cssselect/Config.in"
 	source "package/python-cssselect2/Config.in"
diff --git a/package/python-crossbar/0001-Avoid-intentional-syntax-error.patch b/package/python-crossbar/0001-Avoid-intentional-syntax-error.patch
deleted file mode 100644
index 0ff7cae21a..0000000000
--- a/package/python-crossbar/0001-Avoid-intentional-syntax-error.patch
+++ /dev/null
@@ -1,29 +0,0 @@
-From 423a1b081f6b7198f6a921ca83043270ebbace1a Mon Sep 17 00:00:00 2001
-From: Thomas Petazzoni <thomas.petazzoni@free-electrons.com>
-Date: Sun, 1 May 2016 15:35:32 +0200
-Subject: [PATCH] Avoid intentional syntax error
-
-This file has an intentional syntax error, meant to validate QA, but
-it breaks byte compilation of this package.
-
-Issue reported upstream:
-https://github.com/crossbario/crossbar/issues/750.
-
-Signed-off-by: Thomas Petazzoni <thomas.petazzoni@free-electrons.com>
----
- crossbar/worker/test/examples/syntaxerror.py | 2 --
- 1 file changed, 2 deletions(-)
-
-diff --git a/crossbar/worker/test/examples/syntaxerror.py b/crossbar/worker/test/examples/syntaxerror.py
-index 7b88e088..cd7de901 100644
---- a/crossbar/worker/test/examples/syntaxerror.py
-+++ b/crossbar/worker/test/examples/syntaxerror.py
-@@ -27,5 +27,3 @@
- #  with this program. If not, see <http://www.gnu.org/licenses/agpl-3.0.en.html>.
- #
- #####################################################################################
--
--class # noqa
--- 
-2.20.1
-
diff --git a/package/python-crossbar/0002-requirements-min.txt-drop-indirect-dependencies.patch b/package/python-crossbar/0002-requirements-min.txt-drop-indirect-dependencies.patch
deleted file mode 100644
index 12027d779c..0000000000
--- a/package/python-crossbar/0002-requirements-min.txt-drop-indirect-dependencies.patch
+++ /dev/null
@@ -1,74 +0,0 @@
-From 3ae2b36e48fc0f75f0bb6c89f893ece033bccd87 Mon Sep 17 00:00:00 2001
-From: Thomas Petazzoni <thomas.petazzoni@bootlin.com>
-Date: Sun, 7 Aug 2022 18:44:29 +0200
-Subject: [PATCH] requirements-min.txt: drop indirect dependencies
-
-For some interesting reason, the crossbar maintainers have decided to
-include indirect dependencies in their requirements-min.txt, i.e
-dependencies that they don't use directly, but that packages they
-depend on themselves depend on.
-
-This makes the packaging in Buildroot confusing, as it means not all
-dependencies in requirements-min.txt should be taken into
-account. Also some of these indirect dependencies cause issues due to
-upper bounds set on the version (which is the case for idna and
-urllib3).
-
-This patch therefore clarifies the situation by removing such indirect
-dependencies from requirements-min.txt. As the patch is obviously not
-upstreamable, it will require some maintenance effort, but that effort
-is anyway already there to sort out direct dependencies from indirect
-dependencies when updating the Buildroot packaging for crossbar.
-
-Signed-off-by: Thomas Petazzoni <thomas.petazzoni@bootlin.com>
----
- requirements-min.txt | 12 ------------
- 1 file changed, 12 deletions(-)
-
-diff --git a/requirements-min.txt b/requirements-min.txt
-index 5ac4e0ee..cdd82d27 100644
---- a/requirements-min.txt
-+++ b/requirements-min.txt
-@@ -1,20 +1,13 @@
--attrs>=17.2.0
- autobahn[asyncio,twisted,encryption,compress,serialization,scram]>=21.3.1
- bitstring>=3.1.5
--bcrypt>=3.1.6
- cbor>=1.0.0
- click>=6.7
- colorama>=0.4.4
--constantly>=15.1.0
- cryptography>=2.6.1
--h2>=3.2.0
--idna<2.6,>=2.5
- importlib-resources>=4.1.1
--incremental>=17.5.0
- jinja2>=2.10.1
- lmdb>=0.92
- mistune>=0.7.4
--netaddr>=0.7.19
- passlib>=1.7.1
- priority>=1.3.0
- psutil>=5.2.2
-@@ -28,7 +21,6 @@ pyqrcode>=1.2.1
- pytrie>=0.3
- pyyaml>=4.2b4
- sdnotify>=0.3.1
--service_identity>=17.0.0
- setproctitle>=1.1.10
- setuptools>=36.2.7
- treq>=20.4.1
-@@ -37,10 +29,6 @@ twisted[tls,conch,http2,osx_platform]>=20.3.0; sys_platform == 'darwin'
- twisted[tls,conch,http2,windows_platform]>=20.3.0; sys_platform == 'win32'
- txaio>=21.2.1
- txtorcon>=20.0.0
--u-msgpack-python>=2.4.1
--# urllib3 is an indirect dependency, but we force a recent version because of https://nvd.nist.gov/vuln/detail/CVE-2019-11324
--# workaround for version conflict in requests vs sth else:
--urllib3<1.25,>=1.21.1
- vmprof>=0.4.12; platform_machine=='x86_64' or platform_machine=='i386' or platform_machine=='arm'
- watchdog>=0.8.3
- werkzeug>=0.14.1
--- 
-2.37.1
-
diff --git a/package/python-crossbar/0003-crossbar-webservice-wap-use-markupsafe-instead-of-we.patch b/package/python-crossbar/0003-crossbar-webservice-wap-use-markupsafe-instead-of-we.patch
deleted file mode 100644
index ab2d4709bd..0000000000
--- a/package/python-crossbar/0003-crossbar-webservice-wap-use-markupsafe-instead-of-we.patch
+++ /dev/null
@@ -1,53 +0,0 @@
-From a6866509b0387ab6d6f99f68cd82bcac922fe839 Mon Sep 17 00:00:00 2001
-From: Romain Naour <romain.naour@gmail.com>
-Date: Mon, 30 May 2022 19:38:11 +0200
-Subject: [PATCH] crossbar/webservice/wap: use markupsafe instead of werkzeug
-
-wap.py use escape from werkzeug but it has been removed since
-the version 2.1.0 [1].
-
-Replace with escape from markupsafe like upstream commit [2]
-(wihout other changes).
-
-[1] https://github.com/pallets/werkzeug/commit/22d1e9ac13829b83347107a9b4d77072a8e1af6a
-[2] https://github.com/crossbario/crossbar/commit/ca8d383f01231e2b3f986e791f215f12f2deee5d
-
-Signed-off-by: Romain Naour <romain.naour@gmail.com>
----
- crossbar/webservice/wap.py | 7 ++++++-
- requirements-min.txt       | 1 +
- 2 files changed, 7 insertions(+), 1 deletion(-)
-
-diff --git a/crossbar/webservice/wap.py b/crossbar/webservice/wap.py
-index 825558b1..6daa9b21 100644
---- a/crossbar/webservice/wap.py
-+++ b/crossbar/webservice/wap.py
-@@ -36,7 +36,12 @@ from collections.abc import Mapping, Sequence
- 
- from werkzeug.routing import Map, Rule
- from werkzeug.exceptions import NotFound, MethodNotAllowed
--from werkzeug.utils import escape
-+
-+try:
-+    # removed in werkzeug 2.1.0
-+    from werkzeug.utils import escape
-+except ImportError:
-+    from markupsafe import escape
- 
- from jinja2 import Environment, FileSystemLoader
- from jinja2.sandbox import SandboxedEnvironment
-diff --git a/requirements-min.txt b/requirements-min.txt
-index cdd82d27..50cb1489 100644
---- a/requirements-min.txt
-+++ b/requirements-min.txt
-@@ -7,6 +7,7 @@ cryptography>=2.6.1
- importlib-resources>=4.1.1
- jinja2>=2.10.1
- lmdb>=0.92
-+MarkupSafe>=1.1.1
- mistune>=0.7.4
- passlib>=1.7.1
- priority>=1.3.0
--- 
-2.37.1
-
diff --git a/package/python-crossbar/Config.in b/package/python-crossbar/Config.in
deleted file mode 100644
index aadcb2c360..0000000000
--- a/package/python-crossbar/Config.in
+++ /dev/null
@@ -1,71 +0,0 @@
-config BR2_PACKAGE_PYTHON_CROSSBAR
-	bool "python-crossbar"
-	depends on BR2_PACKAGE_HOST_RUSTC_TARGET_ARCH_SUPPORTS # python-cryptography
-	depends on BR2_PACKAGE_PYTHON_NUMPY_ARCH_SUPPORTS # python-numpy
-	depends on BR2_TOOLCHAIN_USES_GLIBC || BR2_TOOLCHAIN_USES_MUSL # python-numpy
-	depends on BR2_INSTALL_LIBSTDCPP # python-autobahn's compress and serialization
-	# All the following dependencies are runtime dependencies. It
-	# matches almost 1:1 the requirements-min.txt from crossbar
-	# with the following exceptions:
-	# - importlib-resources is in Python itself, so no external
-	#   module is needed
-	# - vmprof, while listed as a needed dependency, isn't
-	#   actually strictly necesary
-	# - wsaccel is not a direct dependency, it is there to make
-	#   sure autobahn has the 'accelerate' feature, when the
-	#   Python implementation is CPython, and our package does
-	#   enable autobahn[accelerate]
-	select BR2_PACKAGE_PYTHON_AUTOBAHN
-	select BR2_PACKAGE_PYTHON_AUTOBAHN_ACCELERATE
-	select BR2_PACKAGE_PYTHON_AUTOBAHN_COMPRESS
-	select BR2_PACKAGE_PYTHON_AUTOBAHN_ENCRYPTION
-	select BR2_PACKAGE_PYTHON_AUTOBAHN_SCRAM
-	select BR2_PACKAGE_PYTHON_AUTOBAHN_SERIALIZATION
-	select BR2_PACKAGE_PYTHON_AUTOBAHN_TWISTED
-	select BR2_PACKAGE_PYTHON_BITSTRING
-	select BR2_PACKAGE_PYTHON_CBOR
-	select BR2_PACKAGE_PYTHON_CLICK
-	select BR2_PACKAGE_PYTHON_COLORAMA
-	select BR2_PACKAGE_PYTHON_CRYPTOGRAPHY
-	select BR2_PACKAGE_PYTHON_JINJA2
-	select BR2_PACKAGE_PYTHON_LMDB
-	select BR2_PACKAGE_PYTHON_MARKUPSAFE
-	select BR2_PACKAGE_PYTHON_MISTUNE
-	select BR2_PACKAGE_PYTHON_PASSLIB
-	select BR2_PACKAGE_PYTHON_PRIORITY
-	select BR2_PACKAGE_PYTHON_PSUTIL
-	select BR2_PACKAGE_PYTHON_PYASN1
-	select BR2_PACKAGE_PYTHON_PYASN1_MODULES
-	select BR2_PACKAGE_PYTHON_PYGMENTS
-	select BR2_PACKAGE_PYTHON_PYNACL
-	select BR2_PACKAGE_PYTHON_PYOPENSSL
-	select BR2_PACKAGE_PYTHON_PYQRCODE
-	select BR2_PACKAGE_PYTHON_PYTRIE
-	select BR2_PACKAGE_PYTHON_PYYAML
-	select BR2_PACKAGE_PYTHON_SDNOTIFY
-	select BR2_PACKAGE_PYTHON_SETPROCTITLE
-	select BR2_PACKAGE_PYTHON_SETUPTOOLS
-	select BR2_PACKAGE_PYTHON_TREQ
-	select BR2_PACKAGE_PYTHON_TWISTED
-	select BR2_PACKAGE_PYTHON_TWISTED_CONCH
-	select BR2_PACKAGE_PYTHON_TWISTED_HTTP2
-	select BR2_PACKAGE_PYTHON_TWISTED_TLS
-	select BR2_PACKAGE_PYTHON_TXAIO
-	select BR2_PACKAGE_PYTHON_TXTORCON
-	select BR2_PACKAGE_PYTHON_UBJSON
-	select BR2_PACKAGE_PYTHON_WATCHDOG
-	select BR2_PACKAGE_PYTHON_WERKZEUG
-	select BR2_PACKAGE_PYTHON_ZLMDB
-	select BR2_PACKAGE_PYTHON_ZOPE_INTERFACE
-	help
-	  Crossbar.io is an open-source WAMP application router that
-	  allows to build advanced applications from loosely-coupled
-	  components that can talk in real-time with each other.
-
-	  https://pypi.python.org/pypi/crossbar
-
-comment "python-crossbar needs a glibc or musl toolchain w/ C++"
-	depends on BR2_PACKAGE_HOST_RUSTC_TARGET_ARCH_SUPPORTS
-	depends on BR2_PACKAGE_PYTHON_NUMPY_ARCH_SUPPORTS
-	depends on !(BR2_TOOLCHAIN_USES_GLIBC || BR2_TOOLCHAIN_USES_MUSL) || \
-		!BR2_INSTALL_LIBSTDCPP
diff --git a/package/python-crossbar/python-crossbar.hash b/package/python-crossbar/python-crossbar.hash
deleted file mode 100644
index 4441578a29..0000000000
--- a/package/python-crossbar/python-crossbar.hash
+++ /dev/null
@@ -1,5 +0,0 @@
-# md5, sha256 from https://pypi.org/pypi/crossbar/json
-md5  df576100bcf6e423cdc1e2e96b602140  crossbar-21.3.1.tar.gz
-sha256  ac71959f0c57ab08d43f7830b85c6312e000b25543a179cd751ac357944dd7ef  crossbar-21.3.1.tar.gz
-# Locally computed
-sha256  57c8ff33c9c0cfc3ef00e650a1cc910d7ee479a8bc509f6c9209a7c2a11399d6  crossbar/LICENSE
diff --git a/package/python-crossbar/python-crossbar.mk b/package/python-crossbar/python-crossbar.mk
deleted file mode 100644
index aae61cd2e8..0000000000
--- a/package/python-crossbar/python-crossbar.mk
+++ /dev/null
@@ -1,14 +0,0 @@
-################################################################################
-#
-# python-crossbar
-#
-################################################################################
-
-PYTHON_CROSSBAR_VERSION = 21.3.1
-PYTHON_CROSSBAR_SOURCE = crossbar-$(PYTHON_CROSSBAR_VERSION).tar.gz
-PYTHON_CROSSBAR_SITE = https://files.pythonhosted.org/packages/17/37/aafc4ec30068fd7ebb97f1a00d4ddf8de482dfa4c1d2a1fc6bb814d91400
-PYTHON_CROSSBAR_LICENSE = AGPL-3.0
-PYTHON_CROSSBAR_LICENSE_FILES = crossbar/LICENSE
-PYTHON_CROSSBAR_SETUP_TYPE = setuptools
-
-$(eval $(python-package))
diff --git a/support/testing/tests/package/sample_python_crossbar.py b/support/testing/tests/package/sample_python_crossbar.py
deleted file mode 100644
index 8160a37219..0000000000
--- a/support/testing/tests/package/sample_python_crossbar.py
+++ /dev/null
@@ -1,4 +0,0 @@
-import os
-import crossbar
-
-crossbar.run(["version"])
diff --git a/support/testing/tests/package/test_python_crossbar.py b/support/testing/tests/package/test_python_crossbar.py
deleted file mode 100644
index e67ab9ff6e..0000000000
--- a/support/testing/tests/package/test_python_crossbar.py
+++ /dev/null
@@ -1,23 +0,0 @@
-from tests.package.test_python import TestPythonPackageBase
-import os
-
-
-class TestPythonPy3Crossbar(TestPythonPackageBase):
-    __test__ = True
-    config = TestPythonPackageBase.config + \
-        """
-        BR2_PACKAGE_PYTHON3=y
-        BR2_PACKAGE_PYTHON_CROSSBAR=y
-        BR2_TARGET_ROOTFS_EXT2=y
-        BR2_TARGET_ROOTFS_EXT2_SIZE="120M"
-        """
-    sample_scripts = ["tests/package/sample_python_crossbar.py"]
-    timeout = 60
-
-    def login(self):
-        ext2_file = os.path.join(self.builddir, "images", "rootfs.ext2")
-        self.emulator.boot(arch="armv5",
-                           kernel="builtin",
-                           options=["-drive", "file=%s,if=scsi,format=raw" % ext2_file],
-                           kernel_cmdline=["rootwait", "root=/dev/sda"])
-        self.emulator.login()
-- 
2.41.0

_______________________________________________
buildroot mailing list
buildroot@buildroot.org
https://lists.buildroot.org/mailman/listinfo/buildroot

^ permalink raw reply related	[flat|nested] 54+ messages in thread

* [Buildroot] [PATCH 30/30] package/python3: bump version to 3.12.0
  2023-10-26  9:26 [Buildroot] [PATCH 00/30] package/python3: bump version to 3.12.0 Adam Duskett
                   ` (28 preceding siblings ...)
  2023-10-26  9:27 ` [Buildroot] [PATCH 29/30] package/python-crossbar: " Adam Duskett
@ 2023-10-26  9:27 ` Adam Duskett
  2023-11-04 17:41 ` [Buildroot] [PATCH 00/30] " Arnout Vandecappelle via buildroot
  30 siblings, 0 replies; 54+ messages in thread
From: Adam Duskett @ 2023-10-26  9:27 UTC (permalink / raw)
  To: buildroot
  Cc: Adam Duskett, Andrey Smirnov, Julien Olivain, Asaf Kahlon,
	James Hilliard, Thomas Petazzoni, Mauro Condarelli

Python 3.12.0 has removed distutils support. As such, we remove the distutils
option from pkg-python.mk as well.

Tested on Fedora 38, 39, and Debian 11. All 68 package tests pass.

Signed-off-by: Adam Duskett <adam.duskett@amarulasolutions.com>
---
 package/pkg-python.mk                         |  44 +-
 ...e-the-build-of-pyc-files-conditional.patch |  24 +-
 ...taddrinfo-configure-test-when-cross-.patch |   2 +-
 ...y-header-paths-for-cross-compilation.patch |  31 +-
 ...ook-in-usr-lib-termcap-for-libraries.patch |  31 -
 ...tch => 0004-Serial-ioctl-workaround.patch} |   0
 .../0005-Don-t-add-multiarch-paths.patch      |  37 -
 ...g.sh.in-ensure-sed-invocations-only.patch} |   0
 .../0006-Abort-on-failed-module-build.patch   |  30 -
 ...0006-Add-an-option-to-disable-pydoc.patch} |  50 +-
 ...07-Add-an-option-to-disable-lib2to3.patch} |  74 +-
 ... 0008-Add-an-option-to-disable-IDLE.patch} |  43 +-
 ...e-shebang-of-Python-scripts-for-cros.patch |  35 -
 ...hon-config.sh-don-t-reassign-prefix.patch} |   0
 ...-an-option-to-disable-the-tk-module.patch} |  47 +-
 ...fix-building-on-older-distributions.patch} |  12 +-
 ...p-CC-print-multiarch-output-for-mus.patch} |   2 +-
 ...ng-doesn-t-set-errno-when-encryptio.patch} |   0
 ...18-Port-_dbm-module-to-PY_STDLIB_MOD.patch | 293 -------
 .../0019-Port-_ctypes-to-PY_STDLIB_MOD.patch  | 441 -----------
 ...readline-and-curses-to-PY_STDLIB_MOD.patch | 718 ------------------
 package/python3/python3.hash                  |   6 +-
 package/python3/python3.mk                    |   4 +-
 23 files changed, 118 insertions(+), 1806 deletions(-)
 delete mode 100644 package/python3/0004-Don-t-look-in-usr-lib-termcap-for-libraries.patch
 rename package/python3/{0007-Serial-ioctl-workaround.patch => 0004-Serial-ioctl-workaround.patch} (100%)
 delete mode 100644 package/python3/0005-Don-t-add-multiarch-paths.patch
 rename package/python3/{0009-Misc-python-config.sh.in-ensure-sed-invocations-only.patch => 0005-Misc-python-config.sh.in-ensure-sed-invocations-only.patch} (100%)
 delete mode 100644 package/python3/0006-Abort-on-failed-module-build.patch
 rename package/python3/{0010-Add-an-option-to-disable-pydoc.patch => 0006-Add-an-option-to-disable-pydoc.patch} (54%)
 rename package/python3/{0011-Add-an-option-to-disable-lib2to3.patch => 0007-Add-an-option-to-disable-lib2to3.patch} (59%)
 rename package/python3/{0012-Add-an-option-to-disable-IDLE.patch => 0008-Add-an-option-to-disable-IDLE.patch} (57%)
 delete mode 100644 package/python3/0008-Do-not-adjust-the-shebang-of-Python-scripts-for-cros.patch
 rename package/python3/{0013-python-config.sh-don-t-reassign-prefix.patch => 0009-python-config.sh-don-t-reassign-prefix.patch} (100%)
 rename package/python3/{0014-Add-an-option-to-disable-the-tk-module.patch => 0010-Add-an-option-to-disable-the-tk-module.patch} (57%)
 rename package/python3/{0015-fix-building-on-older-distributions.patch => 0011-fix-building-on-older-distributions.patch} (82%)
 rename package/python3/{0016-configure.ac-fixup-CC-print-multiarch-output-for-mus.patch => 0012-configure.ac-fixup-CC-print-multiarch-output-for-mus.patch} (97%)
 rename package/python3/{0017-lib-crypt-uClibc-ng-doesn-t-set-errno-when-encryptio.patch => 0013-lib-crypt-uClibc-ng-doesn-t-set-errno-when-encryptio.patch} (100%)
 delete mode 100644 package/python3/0018-Port-_dbm-module-to-PY_STDLIB_MOD.patch
 delete mode 100644 package/python3/0019-Port-_ctypes-to-PY_STDLIB_MOD.patch
 delete mode 100644 package/python3/0020-Port-readline-and-curses-to-PY_STDLIB_MOD.patch

diff --git a/package/pkg-python.mk b/package/pkg-python.mk
index 28ee4cb85f..8f52372321 100644
--- a/package/pkg-python.mk
+++ b/package/pkg-python.mk
@@ -52,33 +52,6 @@ HOST_PKG_PYTHON_ENV = \
 	PYTHONNOUSERSITE=1 \
 	$(HOST_CONFIGURE_OPTS)
 
-# Target distutils-based packages
-PKG_PYTHON_DISTUTILS_ENV = \
-	$(PKG_PYTHON_ENV) \
-	LDSHARED="$(TARGET_CROSS)gcc -shared"
-
-PKG_PYTHON_DISTUTILS_BUILD_OPTS = \
-	--executable=/usr/bin/python
-
-PKG_PYTHON_DISTUTILS_INSTALL_OPTS = \
-	--install-headers=/usr/include/python$(PYTHON3_VERSION_MAJOR) \
-	--prefix=/usr
-
-PKG_PYTHON_DISTUTILS_INSTALL_TARGET_OPTS = \
-	$(PKG_PYTHON_DISTUTILS_INSTALL_OPTS) \
-	--root=$(TARGET_DIR)
-
-PKG_PYTHON_DISTUTILS_INSTALL_STAGING_OPTS = \
-	$(PKG_PYTHON_DISTUTILS_INSTALL_OPTS) \
-	--root=$(STAGING_DIR)
-
-# Host distutils-based packages
-HOST_PKG_PYTHON_DISTUTILS_ENV = \
-	$(HOST_PKG_PYTHON_ENV)
-
-HOST_PKG_PYTHON_DISTUTILS_INSTALL_OPTS = \
-	--prefix=$(HOST_DIR)
-
 # Target setuptools-based packages
 PKG_PYTHON_SETUPTOOLS_ENV = \
 	$(PKG_PYTHON_ENV)
@@ -194,21 +167,8 @@ ifndef $(2)_SETUP_TYPE
  endif
 endif
 
-# Distutils
-ifeq ($$($(2)_SETUP_TYPE),distutils)
-ifeq ($(4),target)
-$(2)_BASE_ENV = $$(PKG_PYTHON_DISTUTILS_ENV)
-$(2)_BASE_BUILD_CMD = setup.py build
-$(2)_BASE_BUILD_OPTS = $$(PKG_PYTHON_DISTUTILS_BUILD_OPTS)
-$(2)_BASE_INSTALL_TARGET_CMD  = setup.py install --no-compile $$(PKG_PYTHON_DISTUTILS_INSTALL_TARGET_OPTS)
-$(2)_BASE_INSTALL_STAGING_CMD = setup.py install $$(PKG_PYTHON_DISTUTILS_INSTALL_STAGING_OPTS)
-else
-$(2)_BASE_ENV         = $$(HOST_PKG_PYTHON_DISTUTILS_ENV)
-$(2)_BASE_BUILD_CMD   = setup.py build
-$(2)_BASE_INSTALL_CMD = setup.py install $$(HOST_PKG_PYTHON_DISTUTILS_INSTALL_OPTS)
-endif
 # Setuptools
-else ifneq ($$(filter setuptools setuptools-rust,$$($(2)_SETUP_TYPE)),)
+ifneq ($$(filter setuptools setuptools-rust,$$($(2)_SETUP_TYPE)),)
 ifeq ($(4),target)
 ifeq ($$($(2)_SETUP_TYPE),setuptools-rust)
 $(2)_BASE_ENV = $$(PKG_PYTHON_SETUPTOOLS_RUST_ENV)
@@ -255,7 +215,7 @@ $(2)_BASE_BUILD_CMD = -m flit_core.wheel
 $(2)_BASE_INSTALL_CMD ?= $(TOPDIR)/support/scripts/pyinstaller.py dist/* $$(HOST_PKG_PYTHON_PEP517_INSTALL_OPTS)
 endif
 else
-$$(error "Invalid $(2)_SETUP_TYPE. Valid options are 'distutils', 'maturin', 'setuptools', 'setuptools-rust', 'pep517' or 'flit'.")
+$$(error "Invalid $(2)_SETUP_TYPE. Valid options are 'maturin', 'setuptools', 'setuptools-rust', 'pep517' or 'flit'.")
 endif
 
 # We need to vendor the Cargo crates at download time for pyo3 based
diff --git a/package/python3/0001-Make-the-build-of-pyc-files-conditional.patch b/package/python3/0001-Make-the-build-of-pyc-files-conditional.patch
index 92aa7274ba..6445068877 100644
--- a/package/python3/0001-Make-the-build-of-pyc-files-conditional.patch
+++ b/package/python3/0001-Make-the-build-of-pyc-files-conditional.patch
@@ -9,6 +9,8 @@ the compilation of pyc.
 Signed-off-by: Thomas Petazzoni <thomas.petazzoni@free-electrons.com>
 [ Andrey Smrinov: ported to Python 3.6 ]
 Signed-off-by: Andrey Smirnov <andrew.smirnov@gmail.com>
+[ Adam Duskett: ported to Python 3.12.0 ]
+Signed-off-by: Adam Duskett <adam.duskett@amarulasolutions.com>
 ---
  Makefile.pre.in | 2 ++
  configure.ac    | 6 ++++++
@@ -18,17 +20,17 @@ diff --git a/Makefile.pre.in b/Makefile.pre.in
 index 8fbcd7ac17..2957c8e5a1 100644
 --- a/Makefile.pre.in
 +++ b/Makefile.pre.in
-@@ -2078,6 +2078,7 @@ libinstall:	all $(srcdir)/Modules/xxmodule.c
- 		$(INSTALL_DATA) $(srcdir)/Modules/xxmodule.c \
- 			$(DESTDIR)$(LIBDEST)/distutils/tests ; \
- 	fi
+@@ -2305,6 +2305,7 @@ libinstall:	all $(srcdir)/Modules/xxmodule.c
+ 		$(DESTDIR)$(LIBDEST); \
+ 	$(INSTALL_DATA) $(srcdir)/LICENSE $(DESTDIR)$(LIBDEST)/LICENSE.txt
+ 	@ # Build PYC files for the 3 optimization levels (0, 1, 2)
 +ifeq (@PYC_BUILD@,yes)
- 	-PYTHONPATH=$(DESTDIR)$(LIBDEST)  $(RUNSHARED) \
+ 	-PYTHONPATH=$(DESTDIR)$(LIBDEST) $(RUNSHARED) \
+ 		$(PYTHON_FOR_BUILD) -Wi $(DESTDIR)$(LIBDEST)/compileall.py \
+ 		-o 0 -o 1 -o 2 $(COMPILEALL_OPTS) -d $(LIBDEST) -f \
+@@ -2314,6 +2315,7 @@ libinstall:	all $(srcdir)/Modules/xxmodule.c
  		$(PYTHON_FOR_BUILD) -Wi $(DESTDIR)$(LIBDEST)/compileall.py \
- 		-j0 -d $(LIBDEST) -f \
-@@ -2105,6 +2106,7 @@ libinstall:	all $(srcdir)/Modules/xxmodule.c
- 		$(PYTHON_FOR_BUILD) -Wi -OO $(DESTDIR)$(LIBDEST)/compileall.py \
- 		-j0 -d $(LIBDEST)/site-packages -f \
+ 		-o 0 -o 1 -o 2 $(COMPILEALL_OPTS) -d $(LIBDEST)/site-packages -f \
  		-x badsyntax $(DESTDIR)$(LIBDEST)/site-packages
 +endif
  	-PYTHONPATH=$(DESTDIR)$(LIBDEST) $(RUNSHARED) \
@@ -38,9 +40,9 @@ diff --git a/configure.ac b/configure.ac
 index ab5e1de6fa..0cf89ed641 100644
 --- a/configure.ac
 +++ b/configure.ac
-@@ -1441,6 +1441,12 @@ fi
+@@ -1474,6 +1474,12 @@ fi
  
- AC_MSG_CHECKING(LDLIBRARY)
+ AC_MSG_CHECKING([LDLIBRARY])
  
 +AC_SUBST(PYC_BUILD)
 +
diff --git a/package/python3/0002-Disable-buggy_getaddrinfo-configure-test-when-cross-.patch b/package/python3/0002-Disable-buggy_getaddrinfo-configure-test-when-cross-.patch
index 5389cb5d15..3e65c848c4 100644
--- a/package/python3/0002-Disable-buggy_getaddrinfo-configure-test-when-cross-.patch
+++ b/package/python3/0002-Disable-buggy_getaddrinfo-configure-test-when-cross-.patch
@@ -13,7 +13,7 @@ diff --git a/configure.ac b/configure.ac
 index 0cf89ed641..830885fcb3 100644
 --- a/configure.ac
 +++ b/configure.ac
-@@ -5086,7 +5086,7 @@ fi]))
+@@ -5391,7 +5391,7 @@ fi]))
  dnl if ac_cv_func_getaddrinfo
  ])
  
diff --git a/package/python3/0003-Adjust-library-header-paths-for-cross-compilation.patch b/package/python3/0003-Adjust-library-header-paths-for-cross-compilation.patch
index 9a55d2582d..93f522008d 100644
--- a/package/python3/0003-Adjust-library-header-paths-for-cross-compilation.patch
+++ b/package/python3/0003-Adjust-library-header-paths-for-cross-compilation.patch
@@ -15,34 +15,19 @@ values, and get correct header/library paths when cross-compiling
 third-party Python modules.
 
 Signed-off-by: Thomas Petazzoni <thomas.petazzoni@free-electrons.com>
+[ Adam Duskett: ported to Python 3.10.0 ]
 Signed-off-by: Adam Duskett <aduskett@gmail.com>
-Refresh for 3.10.0
+[ Adam Duskett: ported to Python 3.12.0 ]
+Signed-off-by: Adam Duskett <adam.duskett@amarulasolutions.com>
 ---
- Lib/distutils/command/build_ext.py |  5 ++++-
- Lib/sysconfig.py                   | 15 +++++++++++----
- 2 files changed, 15 insertions(+), 5 deletions(-)
+ Lib/sysconfig.py | 15 +++++++++++----
+ 1 file changed, 11 insertions(+), 4 deletions(-)
 
-diff --git a/Lib/distutils/command/build_ext.py b/Lib/distutils/command/build_ext.py
-index f287b34998..298234d6a1 100644
---- a/Lib/distutils/command/build_ext.py
-+++ b/Lib/distutils/command/build_ext.py
-@@ -234,7 +234,10 @@ def finalize_options(self):
-         if (sysconfig.get_config_var('Py_ENABLE_SHARED')):
-             if not sysconfig.python_build:
-                 # building third party extensions
--                self.library_dirs.append(sysconfig.get_config_var('LIBDIR'))
-+                libdir = sysconfig.get_config_var('LIBDIR')
-+                if "_python_sysroot" in os.environ:
-+                    libdir = os.environ.get("_python_sysroot") + libdir
-+                self.library_dirs.append(libdir)
-             else:
-                 # building python standard extensions
-                 self.library_dirs.append('.')
 diff --git a/Lib/sysconfig.py b/Lib/sysconfig.py
 index ebe3711827..6328ec41af 100644
 --- a/Lib/sysconfig.py
 +++ b/Lib/sysconfig.py
-@@ -168,10 +168,17 @@ def joinuser(*args):
+@@ -169,10 +169,17 @@ _SCHEME_KEYS = ('stdlib', 'platstdlib', 'purelib', 'platlib', 'include',
  _PY_VERSION = sys.version.split()[0]
  _PY_VERSION_SHORT = f'{sys.version_info[0]}.{sys.version_info[1]}'
  _PY_VERSION_SHORT_NO_DOT = f'{sys.version_info[0]}{sys.version_info[1]}'
@@ -61,9 +46,9 @@ index ebe3711827..6328ec41af 100644
 +    _EXEC_PREFIX = os.path.normpath(sys.exec_prefix)
 +    _BASE_PREFIX = os.path.normpath(sys.base_prefix)
 +    _BASE_EXEC_PREFIX = os.path.normpath(sys.base_exec_prefix)
+ # Mutex guarding initialization of _CONFIG_VARS.
+ _CONFIG_VARS_LOCK = threading.RLock()
  _CONFIG_VARS = None
- _USER_BASE = None
- 
 -- 
 2.34.1
 
diff --git a/package/python3/0004-Don-t-look-in-usr-lib-termcap-for-libraries.patch b/package/python3/0004-Don-t-look-in-usr-lib-termcap-for-libraries.patch
deleted file mode 100644
index 78b3ae596d..0000000000
--- a/package/python3/0004-Don-t-look-in-usr-lib-termcap-for-libraries.patch
+++ /dev/null
@@ -1,31 +0,0 @@
-From 5d13e384b30a2c0b1c7b65718590b7fb0c3ba55e Mon Sep 17 00:00:00 2001
-From: Thomas Petazzoni <thomas.petazzoni@free-electrons.com>
-Date: Wed, 23 Dec 2015 11:36:00 +0100
-Subject: [PATCH] Don't look in /usr/lib/termcap for libraries
-
-Signed-off-by: Thomas Petazzoni <thomas.petazzoni@free-electrons.com>
----
- setup.py | 5 +----
- 1 file changed, 1 insertion(+), 4 deletions(-)
-
-diff --git a/setup.py b/setup.py
-index e496ee34c2..1904898165 100644
---- a/setup.py
-+++ b/setup.py
-@@ -1107,12 +1107,9 @@ def detect_readline_curses(self):
-                 pass # Issue 7384: Already linked against curses or tinfo.
-             elif curses_library:
-                 readline_libs.append(curses_library)
--            elif self.compiler.find_library_file(self.lib_dirs +
--                                                     ['/usr/lib/termcap'],
--                                                     'termcap'):
-+            elif self.compiler.find_library_file(self.lib_dirs, 'termcap'):
-                 readline_libs.append('termcap')
-             self.add(Extension('readline', ['readline.c'],
--                               library_dirs=['/usr/lib/termcap'],
-                                libraries=readline_libs))
-         else:
-             self.missing.append('readline')
--- 
-2.34.1
-
diff --git a/package/python3/0007-Serial-ioctl-workaround.patch b/package/python3/0004-Serial-ioctl-workaround.patch
similarity index 100%
rename from package/python3/0007-Serial-ioctl-workaround.patch
rename to package/python3/0004-Serial-ioctl-workaround.patch
diff --git a/package/python3/0005-Don-t-add-multiarch-paths.patch b/package/python3/0005-Don-t-add-multiarch-paths.patch
deleted file mode 100644
index 749e295df1..0000000000
--- a/package/python3/0005-Don-t-add-multiarch-paths.patch
+++ /dev/null
@@ -1,37 +0,0 @@
-From ad463b5d58ae79f69b011fb048861bd874d34369 Mon Sep 17 00:00:00 2001
-From: Thomas Petazzoni <thomas.petazzoni@free-electrons.com>
-Date: Wed, 23 Dec 2015 11:36:27 +0100
-Subject: [PATCH] Don't add multiarch paths
-
-The add_multiarch_paths() function leads, in certain build
-environments, to the addition of host header paths to the CFLAGS,
-which is not appropriate for cross-compilation. This patch fixes that
-by simply removing the call to add_multiarch_paths() when we're
-cross-compiling.
-
-Investigation done by David <buildroot-2014@inbox.com>.
-
-Signed-off-by: Thomas Petazzoni <thomas.petazzoni@free-electrons.com>
----
- setup.py | 2 +-
- 1 file changed, 1 insertion(+), 1 deletion(-)
-
-diff --git a/setup.py b/setup.py
-index 1904898165..32294546b6 100644
---- a/setup.py
-+++ b/setup.py
-@@ -852,10 +852,10 @@ def configure_compiler(self):
-         if not CROSS_COMPILING:
-             add_dir_to_list(self.compiler.library_dirs, '/usr/local/lib')
-             add_dir_to_list(self.compiler.include_dirs, '/usr/local/include')
-+            self.add_multiarch_paths()
-         # only change this for cross builds for 3.3, issues on Mageia
-         if CROSS_COMPILING:
-             self.add_cross_compiling_paths()
--        self.add_multiarch_paths()
-         self.add_ldflags_cppflags()
- 
-     def init_inc_lib_dirs(self):
--- 
-2.34.1
-
diff --git a/package/python3/0009-Misc-python-config.sh.in-ensure-sed-invocations-only.patch b/package/python3/0005-Misc-python-config.sh.in-ensure-sed-invocations-only.patch
similarity index 100%
rename from package/python3/0009-Misc-python-config.sh.in-ensure-sed-invocations-only.patch
rename to package/python3/0005-Misc-python-config.sh.in-ensure-sed-invocations-only.patch
diff --git a/package/python3/0006-Abort-on-failed-module-build.patch b/package/python3/0006-Abort-on-failed-module-build.patch
deleted file mode 100644
index a473896127..0000000000
--- a/package/python3/0006-Abort-on-failed-module-build.patch
+++ /dev/null
@@ -1,30 +0,0 @@
-From 60b1664a7acebadb1a3d6df871145147f33b5afe Mon Sep 17 00:00:00 2001
-From: Thomas Petazzoni <thomas.petazzoni@free-electrons.com>
-Date: Wed, 23 Dec 2015 11:43:24 +0100
-Subject: [PATCH] Abort on failed module build
-
-When building a Python module fails, the setup.py script currently
-doesn't exit with an error, and simply continues. This is not a really
-nice behavior, so this patch changes setup.py to abort with an error,
-so that the build issue is clearly noticeable.
-
-Signed-off-by: Thomas Petazzoni <thomas.petazzoni@free-electrons.com>
----
- setup.py | 1 +
- 1 file changed, 1 insertion(+)
-
-diff --git a/setup.py b/setup.py
-index 32294546b6..0e04944ce0 100644
---- a/setup.py
-+++ b/setup.py
-@@ -579,6 +579,7 @@ def print_three_column(lst):
-             print("Failed to build these modules:")
-             print_three_column(failed)
-             print()
-+            sys.exit(1)
- 
-         if self.failed_on_import:
-             failed = self.failed_on_import[:]
--- 
-2.34.1
-
diff --git a/package/python3/0010-Add-an-option-to-disable-pydoc.patch b/package/python3/0006-Add-an-option-to-disable-pydoc.patch
similarity index 54%
rename from package/python3/0010-Add-an-option-to-disable-pydoc.patch
rename to package/python3/0006-Add-an-option-to-disable-pydoc.patch
index f7bfd437bb..43d47e9a10 100644
--- a/package/python3/0010-Add-an-option-to-disable-pydoc.patch
+++ b/package/python3/0006-Add-an-option-to-disable-pydoc.patch
@@ -12,17 +12,18 @@ Signed-off-by: Samuel Martin <s.martin49@gmail.com>
 Signed-off-by: Andrey Smirnov <andrew.smirnov@gmail.com>
 [ Adam Duskett: ported to Python 3.10.0 ]
 Signed-off-by: Adam Duskett <aduskett@gmail.com>
+[ Adam Duskett: ported to Python 3.12.0 ]
+Signed-off-by: Adam Duskett <adam.duskett@amarulasolutions.com>
 ---
  Makefile.pre.in | 7 ++++++-
  configure.ac    | 6 ++++++
- setup.py        | 9 +++++++--
- 3 files changed, 19 insertions(+), 3 deletions(-)
+ 2 files changed, 12 insertions(+), 1 deletion(-)
 
 diff --git a/Makefile.pre.in b/Makefile.pre.in
 index c1cfb96767..403380e181 100644
 --- a/Makefile.pre.in
 +++ b/Makefile.pre.in
-@@ -1864,7 +1864,9 @@ bininstall: altbininstall
+@@ -2045,7 +2045,9 @@ bininstall: commoninstall altbininstall
  	-rm -f $(DESTDIR)$(BINDIR)/idle3
  	(cd $(DESTDIR)$(BINDIR); $(LN) -s idle$(VERSION) idle3)
  	-rm -f $(DESTDIR)$(BINDIR)/pydoc3
@@ -32,7 +33,7 @@ index c1cfb96767..403380e181 100644
  	-rm -f $(DESTDIR)$(BINDIR)/2to3
  	(cd $(DESTDIR)$(BINDIR); $(LN) -s 2to3-$(VERSION) 2to3)
  	if test "x$(LIPO_32BIT_FLAGS)" != "x" ; then \
-@@ -1915,7 +1917,6 @@ LIBSUBDIRS=	asyncio \
+@@ -2097,7 +2099,6 @@ LIBSUBDIRS=	asyncio \
  		lib2to3 lib2to3/fixes lib2to3/pgen2 \
  		logging \
  		multiprocessing multiprocessing/dummy \
@@ -40,22 +41,22 @@ index c1cfb96767..403380e181 100644
  		re \
  		site-packages \
  		sqlite3 \
-@@ -2008,6 +2009,10 @@ TESTSUBDIRS=	ctypes/test \
- 		tkinter/test/test_ttk \
- 		unittest/test unittest/test/testmock
+@@ -2233,6 +2234,10 @@ TESTSUBDIRS=	idlelib/idle_test \
+ 
+ COMPILEALL_OPTS=-j0
  
 +ifeq (@PYDOC@,yes)
 +LIBSUBDIRS += pydoc_data
 +endif
 +
  TEST_MODULES=@TEST_MODULES@
- libinstall:	all $(srcdir)/Modules/xxmodule.c
- 	@for i in $(SCRIPTDIR) $(LIBDEST); \
+ 
+ .PHONY: libinstall
 diff --git a/configure.ac b/configure.ac
 index 5a6a1fe608..f68ea72321 100644
 --- a/configure.ac
 +++ b/configure.ac
-@@ -4171,6 +4171,12 @@ AS_VAR_IF([posix_threads], [stub], [
+@@ -4475,6 +4475,12 @@ AS_VAR_IF([posix_threads], [stub], [
    AC_DEFINE([HAVE_PTHREAD_STUBS], [1], [Define if platform requires stubbed pthreads support])
  ])
  
@@ -66,35 +67,8 @@ index 5a6a1fe608..f68ea72321 100644
 +	[ PYDOC="${enableval}" ], [ PYDOC=yes ])
 +
  # Check for enable-ipv6
- AH_TEMPLATE(ENABLE_IPV6, [Define if --enable-ipv6 is specified])
+ AH_TEMPLATE([ENABLE_IPV6], [Define if --enable-ipv6 is specified])
  AC_MSG_CHECKING([if --enable-ipv6 is specified])
-diff --git a/setup.py b/setup.py
-index 0e04944ce0..3e55f5b2e0 100644
---- a/setup.py
-+++ b/setup.py
-@@ -1593,6 +1593,12 @@ class DummyProcess:
-     # turn off warnings when deprecated modules are imported
-     import warnings
-     warnings.filterwarnings("ignore",category=DeprecationWarning)
-+
-+    scripts = ['Tools/scripts/idle3', 'Tools/scripts/2to3',
-+               'Lib/smtpd.py']
-+    if not '--disable-pydoc' in sysconfig.get_config_var("CONFIG_ARGS"):
-+        scripts += [ 'Tools/scripts/pydoc3' ]
-+
-     setup(# PyPI Metadata (PEP 301)
-           name = "Python",
-           version = sys.version.split()[0],
-@@ -1617,8 +1623,7 @@ class DummyProcess:
-           # If you change the scripts installed here, you also need to
-           # check the PyBuildScripts command above, and change the links
-           # created by the bininstall target in Makefile.pre.in
--          scripts = ["Tools/scripts/pydoc3", "Tools/scripts/idle3",
--                     "Tools/scripts/2to3"]
-+          scripts = scripts
-         )
- 
- # --install-platlib
 -- 
 2.34.1
 
diff --git a/package/python3/0011-Add-an-option-to-disable-lib2to3.patch b/package/python3/0007-Add-an-option-to-disable-lib2to3.patch
similarity index 59%
rename from package/python3/0011-Add-an-option-to-disable-lib2to3.patch
rename to package/python3/0007-Add-an-option-to-disable-lib2to3.patch
index 228b86a90b..06766fc5ab 100644
--- a/package/python3/0011-Add-an-option-to-disable-lib2to3.patch
+++ b/package/python3/0007-Add-an-option-to-disable-lib2to3.patch
@@ -14,17 +14,18 @@ Signed-off-by: Andrey Smirnov <andrew.smirnov@gmail.com>
 Signed-off-by: Adam Duskett <aduskett@gmail.com>
 [ Bernd Kuhls: ported to Python 3.11.4]
 Signed-off-by: Bernd Kuhls <bernd.kuhls@t-online.de>
+[ Adam Duskett: ported to Python 3.12.0 ]
+Signed-off-by: Adam Duskett <adam.duskett@amarulasolutions.com>
 ---
  Makefile.pre.in | 17 ++++++++++++-----
  configure.ac    |  6 ++++++
- setup.py        |  6 +++---
- 3 files changed, 21 insertions(+), 8 deletions(-)
+ 2 files changed, 18 insertions(+), 5 deletions(-)
 
 diff --git a/Makefile.pre.in b/Makefile.pre.in
 index 403380e181..f5d0573067 100644
 --- a/Makefile.pre.in
 +++ b/Makefile.pre.in
-@@ -1868,7 +1868,9 @@ ifeq (@PYDOC@,yes)
+@@ -2049,7 +2049,9 @@ ifeq (@PYDOC@,yes)
  	(cd $(DESTDIR)$(BINDIR); $(LN) -s pydoc$(VERSION) pydoc3)
  endif
  	-rm -f $(DESTDIR)$(BINDIR)/2to3
@@ -34,7 +35,7 @@ index 403380e181..f5d0573067 100644
  	if test "x$(LIPO_32BIT_FLAGS)" != "x" ; then \
  		rm -f $(DESTDIR)$(BINDIR)/python3-32$(EXE); \
  		(cd $(DESTDIR)$(BINDIR); $(LN) -s python$(VERSION)-32$(EXE) python3-32$(EXE)) \
-@@ -1914,7 +1916,6 @@ LIBSUBDIRS=	asyncio \
+@@ -2096,7 +2098,6 @@ LIBSUBDIRS=	asyncio \
  		idlelib idlelib/Icons \
  		importlib importlib/resources importlib/metadata \
  		json \
@@ -42,34 +43,34 @@ index 403380e181..f5d0573067 100644
  		logging \
  		multiprocessing multiprocessing/dummy \
  		re \
-@@ -1934,10 +1935,6 @@ LIBSUBDIRS=	asyncio \
- TESTSUBDIRS=	ctypes/test \
- 		distutils/tests \
- 		idlelib/idle_test \
--		lib2to3/tests \
--		lib2to3/tests/data \
--		lib2to3/tests/data/fixers \
--		lib2to3/tests/data/fixers/myfixes \
- 		test \
- 		test/audiodata \
- 		test/capath \
-@@ -2013,6 +2010,14 @@ ifeq (@PYDOC@,yes)
+@@ -2190,10 +2191,6 @@ TESTSUBDIRS=	idlelib/idle_test \
+ 		test/test_importlib/resources/zipdata02 \
+ 		test/test_importlib/source \
+ 		test/test_json \
+-		test/test_lib2to3 \
+-		test/test_lib2to3/data \
+-		test/test_lib2to3/data/fixers \
+-		test/test_lib2to3/data/fixers/myfixes \
+ 		test/test_module \
+ 		test/test_peg_generator \
+ 		test/test_sqlite3 \
+@@ -2238,6 +2235,14 @@ ifeq (@PYDOC@,yes)
  LIBSUBDIRS += pydoc_data
  endif
  
 +ifeq (@LIB2TO3@,yes)
 +LIBSUBDIRS += lib2to3 lib2to3/fixes lib2to3/pgen2
-+TESTSUBDIRS += lib2to3/tests			\
-+	lib2to3/tests/data			\
-+	lib2to3/tests/data/fixers		\
-+	lib2to3/tests/data/fixers/myfixes
++TESTSUBDIRS += test/test_lib2to3			\
++		test/test_lib2to3/data \
++		test/test_lib2to3/data/fixers \
++		test/test_lib2to3/data/fixers/myfixes
 +endif
 +
  TEST_MODULES=@TEST_MODULES@
- libinstall:	all $(srcdir)/Modules/xxmodule.c
- 	@for i in $(SCRIPTDIR) $(LIBDEST); \
-@@ -2115,10 +2120,12 @@ ifeq (@PYC_BUILD@,yes)
- 		-j0 -d $(LIBDEST)/site-packages -f \
+ 
+ .PHONY: libinstall
+@@ -2321,10 +2326,12 @@ ifeq (@PYC_BUILD@,yes)
+ 		-o 0 -o 1 -o 2 $(COMPILEALL_OPTS) -d $(LIBDEST)/site-packages -f \
  		-x badsyntax $(DESTDIR)$(LIBDEST)/site-packages
  endif
 +ifeq (@LIB2TO3@,yes)
@@ -85,7 +86,7 @@ diff --git a/configure.ac b/configure.ac
 index f68ea72321..d8e10cf2b2 100644
 --- a/configure.ac
 +++ b/configure.ac
-@@ -7078,6 +7078,12 @@ PY_STDLIB_MOD([xxlimited_35], [test "$with_trace_refs" = "no"], [test "$ac_cv_fu
+@@ -7510,6 +7510,12 @@ PY_STDLIB_MOD([xxlimited_35], [test "$with_trace_refs" = "no"], [test "$ac_cv_fu
  # substitute multiline block, must come after last PY_STDLIB_MOD()
  AC_SUBST([MODULE_BLOCK])
  
@@ -96,27 +97,8 @@ index f68ea72321..d8e10cf2b2 100644
 +	[ LIB2TO3="${enableval}" ], [ LIB2TO3=yes ])
 +
  # generate output files
- AC_CONFIG_FILES(Makefile.pre Misc/python.pc Misc/python-embed.pc Misc/python-config.sh)
- AC_CONFIG_FILES([Modules/Setup.bootstrap Modules/Setup.stdlib])
-diff --git a/setup.py b/setup.py
-index 3e55f5b2e0..c490b0b08f 100644
---- a/setup.py
-+++ b/setup.py
-@@ -1594,11 +1594,11 @@ class DummyProcess:
-     import warnings
-     warnings.filterwarnings("ignore",category=DeprecationWarning)
- 
--    scripts = ['Tools/scripts/idle3', 'Tools/scripts/2to3',
--               'Lib/smtpd.py']
-+    scripts = ['Tools/scripts/idle3', 'Lib/smtpd.py']
-     if not '--disable-pydoc' in sysconfig.get_config_var("CONFIG_ARGS"):
-         scripts += [ 'Tools/scripts/pydoc3' ]
--
-+    if not '--disable-lib2to3' in sysconfig.get_config_var("CONFIG_ARGS"):
-+        scripts += [ 'Tools/scripts/2to3' ]
-     setup(# PyPI Metadata (PEP 301)
-           name = "Python",
-           version = sys.version.split()[0],
+ AC_CONFIG_FILES(m4_normalize([
+   Makefile.pre
 -- 
 2.34.1
 
diff --git a/package/python3/0012-Add-an-option-to-disable-IDLE.patch b/package/python3/0008-Add-an-option-to-disable-IDLE.patch
similarity index 57%
rename from package/python3/0012-Add-an-option-to-disable-IDLE.patch
rename to package/python3/0008-Add-an-option-to-disable-IDLE.patch
index 09b5f13cdb..7bfeec8015 100644
--- a/package/python3/0012-Add-an-option-to-disable-IDLE.patch
+++ b/package/python3/0008-Add-an-option-to-disable-IDLE.patch
@@ -11,17 +11,18 @@ Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
 Signed-off-by: Andrey Smirnov <andrew.smirnov@gmail.com>
 [ Adam Duskett: ported to Python 3.10.0 ]
 Signed-off-by: Adam Duskett <aduskett@gmail.com>
+[ Adam Duskett: ported to Python 3.12.0 ]
+Signed-off-by: Adam Duskett <adam.duskett@amarulasolutions.com>
 ---
  Makefile.pre.in | 7 ++++++-
  configure.ac    | 6 ++++++
- setup.py        | 5 ++++-
- 3 files changed, 16 insertions(+), 2 deletions(-)
+ 2 files changed, 12 insertions(+), 1 deletion(-)
 
 diff --git a/Makefile.pre.in b/Makefile.pre.in
 index 80d617cf7f..8d1ba1356c 100644
 --- a/Makefile.pre.in
 +++ b/Makefile.pre.in
-@@ -1862,7 +1862,9 @@ bininstall: altbininstall
+@@ -2043,7 +2043,9 @@ bininstall: commoninstall altbininstall
  	-rm -f $(DESTDIR)$(LIBPC)/python3-embed.pc
  	(cd $(DESTDIR)$(LIBPC); $(LN) -s python-$(VERSION)-embed.pc python3-embed.pc)
  	-rm -f $(DESTDIR)$(BINDIR)/idle3
@@ -31,7 +32,7 @@ index 80d617cf7f..8d1ba1356c 100644
  	-rm -f $(DESTDIR)$(BINDIR)/pydoc3
  ifeq (@PYDOC@,yes)
  	(cd $(DESTDIR)$(BINDIR); $(LN) -s pydoc$(VERSION) pydoc3)
-@@ -1912,7 +1914,6 @@ LIBSUBDIRS=	asyncio \
+@@ -2095,7 +2097,6 @@ LIBSUBDIRS=	asyncio \
  		ensurepip ensurepip/_bundled \
  		html \
  		http \
@@ -39,8 +40,8 @@ index 80d617cf7f..8d1ba1356c 100644
  		importlib importlib/resources importlib/metadata \
  		json \
  		logging \
-@@ -2030,6 +2031,10 @@ ifeq (@EXPAT@,yes)
- LIBSUBDIRS += $(XMLLIBSUBDIRS)
+@@ -2243,6 +2244,10 @@ TESTSUBDIRS += test/test_lib2to3			\
+ 		test/test_lib2to3/data/fixers/myfixes
  endif
  
 +ifeq (@IDLE@,yes)
@@ -48,13 +49,13 @@ index 80d617cf7f..8d1ba1356c 100644
 +endif
 +
  TEST_MODULES=@TEST_MODULES@
- libinstall:	all $(srcdir)/Modules/xxmodule.c
- 	@for i in $(SCRIPTDIR) $(LIBDEST); \
+ 
+ .PHONY: libinstall
 diff --git a/configure.ac b/configure.ac
 index ba4b0e0c1c..5e6d72f7db 100644
 --- a/configure.ac
 +++ b/configure.ac
-@@ -7137,6 +7137,12 @@ AC_ARG_ENABLE(lib2to3,
+@@ -7516,6 +7516,12 @@ AC_ARG_ENABLE(lib2to3,
  	AS_HELP_STRING([--disable-lib2to3], [disable lib2to3]),
  	[ LIB2TO3="${enableval}" ], [ LIB2TO3=yes ])
  
@@ -65,28 +66,8 @@ index ba4b0e0c1c..5e6d72f7db 100644
 +	[ IDLE="${enableval}" ], [ IDLE=yes ])
 +
  # generate output files
- AC_CONFIG_FILES(Makefile.pre Misc/python.pc Misc/python-embed.pc Misc/python-config.sh)
- AC_CONFIG_FILES([Modules/Setup.bootstrap Modules/Setup.stdlib])
-diff --git a/setup.py b/setup.py
-index c490b0b08f..4d49a792f7 100644
---- a/setup.py
-+++ b/setup.py
-@@ -1594,11 +1594,14 @@ class DummyProcess:
-     import warnings
-     warnings.filterwarnings("ignore",category=DeprecationWarning)
- 
--    scripts = ['Tools/scripts/idle3', 'Lib/smtpd.py']
-+    scripts = [ 'Lib/smtpd.py']
-     if not '--disable-pydoc' in sysconfig.get_config_var("CONFIG_ARGS"):
-         scripts += [ 'Tools/scripts/pydoc3' ]
-     if not '--disable-lib2to3' in sysconfig.get_config_var("CONFIG_ARGS"):
-         scripts += [ 'Tools/scripts/2to3' ]
-+    if not '--disable-idle3' in sysconfig.get_config_var("CONFIG_ARGS"):
-+        scripts += [ 'Tools/scripts/idle3' ]
-+
-     setup(# PyPI Metadata (PEP 301)
-           name = "Python",
-           version = sys.version.split()[0],
+ AC_CONFIG_FILES(m4_normalize([
+   Makefile.pre
 -- 
 2.34.1
 
diff --git a/package/python3/0008-Do-not-adjust-the-shebang-of-Python-scripts-for-cros.patch b/package/python3/0008-Do-not-adjust-the-shebang-of-Python-scripts-for-cros.patch
deleted file mode 100644
index eff8fc7694..0000000000
--- a/package/python3/0008-Do-not-adjust-the-shebang-of-Python-scripts-for-cros.patch
+++ /dev/null
@@ -1,35 +0,0 @@
-From 2439bd2ed5dbdd7e5fda15adefd0f6f1b047ec1b Mon Sep 17 00:00:00 2001
-From: Christophe Vu-Brugier <cvubrugier@fastmail.fm>
-Date: Wed, 23 Dec 2015 11:44:30 +0100
-Subject: [PATCH] Do not adjust the shebang of Python scripts for
- cross-compilation
-
-The copy_scripts() method in distutils copies the scripts listed in
-the setup file and adjusts the first line to refer to the current
-Python interpreter. When cross-compiling, this means that the adjusted
-shebang refers to the host Python interpreter.
-
-This patch modifies copy_scripts() to preserve the shebang when
-cross-compilation is detected.
-
-Signed-off-by: Christophe Vu-Brugier <cvubrugier@fastmail.fm>
----
- Lib/distutils/command/build_scripts.py | 2 +-
- 1 file changed, 1 insertion(+), 1 deletion(-)
-
-diff --git a/Lib/distutils/command/build_scripts.py b/Lib/distutils/command/build_scripts.py
-index ccc70e6465..d6d54195c1 100644
---- a/Lib/distutils/command/build_scripts.py
-+++ b/Lib/distutils/command/build_scripts.py
-@@ -91,7 +91,7 @@ def copy_scripts(self):
-                     adjust = True
-                     post_interp = match.group(1) or b''
- 
--            if adjust:
-+            if adjust and not '_python_sysroot' in os.environ:
-                 log.info("copying and adjusting %s -> %s", script,
-                          self.build_dir)
-                 updated_files.append(outfile)
--- 
-2.34.1
-
diff --git a/package/python3/0013-python-config.sh-don-t-reassign-prefix.patch b/package/python3/0009-python-config.sh-don-t-reassign-prefix.patch
similarity index 100%
rename from package/python3/0013-python-config.sh-don-t-reassign-prefix.patch
rename to package/python3/0009-python-config.sh-don-t-reassign-prefix.patch
diff --git a/package/python3/0014-Add-an-option-to-disable-the-tk-module.patch b/package/python3/0010-Add-an-option-to-disable-the-tk-module.patch
similarity index 57%
rename from package/python3/0014-Add-an-option-to-disable-the-tk-module.patch
rename to package/python3/0010-Add-an-option-to-disable-the-tk-module.patch
index 1ed8959dba..ff2d94a391 100644
--- a/package/python3/0014-Add-an-option-to-disable-the-tk-module.patch
+++ b/package/python3/0010-Add-an-option-to-disable-the-tk-module.patch
@@ -11,16 +11,18 @@ Signed-off-by: Andrey Smirnov <andrew.smirnov@gmail.com>
 Signed-off-by: Adam Duskett <aduskett@gmail.com>
 [ Bernd Kuhls: ported to Python 3.11.4]
 Signed-off-by: Bernd Kuhls <bernd.kuhls@t-online.de>
+[ Adam Duskett: ported to Python 3.12.0 ]
+Signed-off-by: Adam Duskett <adam.duskett@amarulasolutions.com>
 ---
- Makefile.pre.in | 10 +++++++---
- configure.ac    |  9 +++++++++
- 2 files changed, 16 insertions(+), 3 deletions(-)
+ Makefile.pre.in | 8 +++++---
+ configure.ac    | 5 +++++
+ 2 files changed, 10 insertions(+), 3 deletions(-)
 
 diff --git a/Makefile.pre.in b/Makefile.pre.in
 index 9f4cdf14cf..4f83911200 100644
 --- a/Makefile.pre.in
 +++ b/Makefile.pre.in
-@@ -1921,7 +1921,6 @@
+@@ -2104,7 +2104,6 @@ LIBSUBDIRS=	asyncio \
  		re \
  		site-packages \
  		sqlite3 \
@@ -28,30 +30,39 @@ index 9f4cdf14cf..4f83911200 100644
  		tomllib \
  		turtledemo \
  		unittest \
-@@ -2039,12 +2038,15 @@
- 		test/xmltestdata \
+@@ -2195,7 +2194,6 @@ TESTSUBDIRS=	idlelib/idle_test \
+ 		test/test_module \
+ 		test/test_peg_generator \
+ 		test/test_sqlite3 \
+-		test/test_tkinter \
+ 		test/test_tomllib \
+ 		test/test_tomllib/data \
+ 		test/test_tomllib/data/invalid \
+@@ -2215,7 +2213,6 @@ TESTSUBDIRS=	idlelib/idle_test \
+ 		test/test_tomllib/data/valid/dates-and-times \
+ 		test/test_tomllib/data/valid/multiline-basic-str \
+ 		test/test_tools \
+-		test/test_ttk \
+ 		test/test_unittest \
+ 		test/test_unittest/testmock \
+ 		test/test_warnings \
+@@ -2230,6 +2227,11 @@ TESTSUBDIRS=	idlelib/idle_test \
  		test/xmltestdata/c14n-20 \
- 		test/ziptestdata \
--		tkinter/test \
--		tkinter/test/test_tkinter \
--		tkinter/test/test_ttk \
- 		unittest/test \
- 		unittest/test/testmock
+ 		test/ziptestdata
  
 +ifeq (@TK@,yes)
 +LIBSUBDIRS += tkinter
-+TESTSUBDIRS += tkinter/test tkinter/test/test_tkinter \
-+	tkinter/test/test_ttk
++TESTSUBDIRS += test/test_tkinter test/test_ttk
 +endif
 +
+ COMPILEALL_OPTS=-j0
+ 
  ifeq (@PYDOC@,yes)
- LIBSUBDIRS += pydoc_data
- endif
 diff --git a/configure.ac b/configure.ac
 index 4cc0951ab9..f4ce506801 100644
 --- a/configure.ac
 +++ b/configure.ac
-@@ -4202,6 +4202,11 @@
+@@ -4481,6 +4481,11 @@ AC_ARG_ENABLE(pydoc,
  	AS_HELP_STRING([--disable-pydoc], [disable pydoc]),
  	[ PYDOC="${enableval}" ], [ PYDOC=yes ])
  
@@ -61,7 +72,7 @@ index 4cc0951ab9..f4ce506801 100644
 +	[ TK="${enableval}" ], [ TK=yes ])
 +
  # Check for enable-ipv6
- AH_TEMPLATE(ENABLE_IPV6, [Define if --enable-ipv6 is specified])
+ AH_TEMPLATE([ENABLE_IPV6], [Define if --enable-ipv6 is specified])
  AC_MSG_CHECKING([if --enable-ipv6 is specified])
 -- 
 2.34.1
diff --git a/package/python3/0015-fix-building-on-older-distributions.patch b/package/python3/0011-fix-building-on-older-distributions.patch
similarity index 82%
rename from package/python3/0015-fix-building-on-older-distributions.patch
rename to package/python3/0011-fix-building-on-older-distributions.patch
index 4541b31c5f..e22e85bd66 100644
--- a/package/python3/0015-fix-building-on-older-distributions.patch
+++ b/package/python3/0011-fix-building-on-older-distributions.patch
@@ -16,14 +16,16 @@ This change fixes building on older systems such as CentOS7, that only come
 with python 2.
 
 Signed-off-by: Adam Duskett <aduskett@gmail.com>
+[ Adam Duskett: ported to Python 3.12.0 ]
+Signed-off-by: Adam Duskett <adam.duskett@amarulasolutions.com>
 ---
- Tools/scripts/update_file.py | 4 ++--
+ Tools/build/update_file.py | 4 ++--
  1 file changed, 2 insertions(+), 2 deletions(-)
 
-diff --git a/Tools/scripts/update_file.py b/Tools/scripts/update_file.py
-index b4182c1d0c..ab443cb1a6 100644
---- a/Tools/scripts/update_file.py
-+++ b/Tools/scripts/update_file.py
+diff --git a/Tools/build/update_file.py b/Tools/build/update_file.py
+index b4182c1..ab443cb 100644
+--- a/Tools/build/update_file.py
++++ b/Tools/build/update_file.py
 @@ -53,7 +53,7 @@ def update_file_with_tmpfile(filename, tmpfile, *, create=False):
          if not create:
              raise  # re-raise
diff --git a/package/python3/0016-configure.ac-fixup-CC-print-multiarch-output-for-mus.patch b/package/python3/0012-configure.ac-fixup-CC-print-multiarch-output-for-mus.patch
similarity index 97%
rename from package/python3/0016-configure.ac-fixup-CC-print-multiarch-output-for-mus.patch
rename to package/python3/0012-configure.ac-fixup-CC-print-multiarch-output-for-mus.patch
index 5253076d90..d689b7f7a3 100644
--- a/package/python3/0016-configure.ac-fixup-CC-print-multiarch-output-for-mus.patch
+++ b/package/python3/0012-configure.ac-fixup-CC-print-multiarch-output-for-mus.patch
@@ -33,7 +33,7 @@ diff --git a/configure.ac b/configure.ac
 index ed03b27fb1..841fd6732c 100644
 --- a/configure.ac
 +++ b/configure.ac
-@@ -1086,7 +1086,11 @@ AC_MSG_CHECKING([for multiarch])
+@@ -1118,7 +1118,11 @@ AC_MSG_CHECKING([for multiarch])
  AS_CASE([$ac_sys_system],
    [Darwin*], [MULTIARCH=""],
    [FreeBSD*], [MULTIARCH=""],
diff --git a/package/python3/0017-lib-crypt-uClibc-ng-doesn-t-set-errno-when-encryptio.patch b/package/python3/0013-lib-crypt-uClibc-ng-doesn-t-set-errno-when-encryptio.patch
similarity index 100%
rename from package/python3/0017-lib-crypt-uClibc-ng-doesn-t-set-errno-when-encryptio.patch
rename to package/python3/0013-lib-crypt-uClibc-ng-doesn-t-set-errno-when-encryptio.patch
diff --git a/package/python3/0018-Port-_dbm-module-to-PY_STDLIB_MOD.patch b/package/python3/0018-Port-_dbm-module-to-PY_STDLIB_MOD.patch
deleted file mode 100644
index 92d2594eef..0000000000
--- a/package/python3/0018-Port-_dbm-module-to-PY_STDLIB_MOD.patch
+++ /dev/null
@@ -1,293 +0,0 @@
-From ec5e253556875640b1ac514e85c545346ac3f1e0 Mon Sep 17 00:00:00 2001
-From: Christian Heimes <christian@python.org>
-Date: Fri, 1 Jul 2022 21:48:38 +0200
-Subject: [PATCH] gh-90005: Port _dbm module to PY_STDLIB_MOD (GH-94433)
-
-Upstream: https://github.com/python/cpython/commit/ec5e253556875640b1ac514e85c545346ac3f1e0
-
-[Bernd: backported to 3.11.4]
-Signed-off-by: Bernd Kuhls <bernd@kuhls.net>
----
- ...2-06-30-09-57-39.gh-issue-90005.9-pQyR.rst |   1 +
- Modules/Setup.stdlib.in                       |   2 +-
- configure                                     | 295 ++++++++++++------
- configure.ac                                  | 100 ++++--
- pyconfig.h.in                                 |   6 -
- setup.py                                      |  72 +----
- 6 files changed, 278 insertions(+), 198 deletions(-)
- create mode 100644 Misc/NEWS.d/next/Build/2022-06-30-09-57-39.gh-issue-90005.9-pQyR.rst
-
-diff --git a/Misc/NEWS.d/next/Build/2022-06-30-09-57-39.gh-issue-90005.9-pQyR.rst b/Misc/NEWS.d/next/Build/2022-06-30-09-57-39.gh-issue-90005.9-pQyR.rst
-new file mode 100644
-index 0000000000000..9b14f767847da
---- /dev/null
-+++ b/Misc/NEWS.d/next/Build/2022-06-30-09-57-39.gh-issue-90005.9-pQyR.rst
-@@ -0,0 +1 @@
-+``_dbm`` module dependencies are now detected by configure.
-diff --git a/Modules/Setup.stdlib.in b/Modules/Setup.stdlib.in
-index a199aefc51011..ad34f85e25451 100644
---- a/Modules/Setup.stdlib.in
-+++ b/Modules/Setup.stdlib.in
-@@ -68,7 +68,7 @@
- 
- # dbm/gdbm
- # dbm needs either libndbm, libgdbm_compat, or libdb 5.x
--#@MODULE__DBM_TRUE@_dbm _dbmmodule.c
-+@MODULE__DBM_TRUE@_dbm _dbmmodule.c
- # gdbm module needs -lgdbm
- @MODULE__GDBM_TRUE@_gdbm _gdbmmodule.c
- 
-diff --git a/configure.ac b/configure.ac
-index 12ae2ae8d87eb..b03ead3bdefa0 100644
---- a/configure.ac
-+++ b/configure.ac
-@@ -3956,17 +3956,30 @@ WITH_SAVE_ENV([
-   ], [have_gdbm=no])
- ])
- 
--# check for _dbmmodule.c dependencies
-+dnl check for _dbmmodule.c dependencies
-+dnl ndbm, gdbm_compat, libdb
- AC_CHECK_HEADERS([ndbm.h], [
--  LIBS_SAVE="$LIBS"
--  AC_CHECK_LIB([ndbm], [dbm_open])
--  LIBS="$LIBS_SAVE"
--  AC_CHECK_LIB([gdbm_compat], [dbm_open])
--  LIBS="$LIBS_SAVE"
-+  WITH_SAVE_ENV([
-+    AC_SEARCH_LIBS([dbm_open], [ndbm gdbm_compat])
-+  ])
- ])
- 
--# "gdbm-ndbm.h" and "gdbm/ndbm.h" are both normalized to "gdbm_ndbm_h"
--# unset ac_cv_header_gdbm_ndbm_h to prevent false positive cache hits.
-+AC_MSG_CHECKING([for ndbm presence and linker args])
-+AS_CASE([$ac_cv_search_dbm_open],
-+  [*ndbm*|*gdbm_compat*], [
-+    dbm_ndbm="$ac_cv_search_dbm_open"
-+    have_ndbm=yes
-+  ],
-+  [none*], [
-+    dbm_ndbm=""
-+    have_ndbm=yes
-+  ],
-+  [no], [have_ndbm=no]
-+)
-+AC_MSG_RESULT([$have_ndbm ($dbm_ndbm)])
-+
-+dnl "gdbm-ndbm.h" and "gdbm/ndbm.h" are both normalized to "gdbm_ndbm_h"
-+dnl unset ac_cv_header_gdbm_ndbm_h to prevent false positive cache hits.
- AS_UNSET([ac_cv_header_gdbm_ndbm_h])
- AC_CACHE_VAL([ac_cv_header_gdbm_slash_ndbm_h], [
-   AC_CHECK_HEADER(
-@@ -3991,26 +4004,26 @@ AS_VAR_IF([ac_cv_header_gdbm_dash_ndbm_h], [yes], [
- AS_UNSET([ac_cv_header_gdbm_ndbm_h])
- 
- if test "$ac_cv_header_gdbm_slash_ndbm_h" = yes -o "$ac_cv_header_gdbm_dash_ndbm_h" = yes; then
--  LIBS_SAVE="$LIBS"
--  AC_CHECK_LIB([gdbm_compat], [dbm_open])
--  LIBS="$LIBS_SAVE"
-+  WITH_SAVE_ENV([
-+    AC_SEARCH_LIBS([dbm_open], [gdbm_compat])
-+  ])
- fi
- 
- # Check for libdb >= 5 with dbm_open()
- # db.h re-defines the name of the function
- AC_CHECK_HEADERS([db.h], [
-   AC_CACHE_CHECK([for libdb], [ac_cv_have_libdb], [
--    LIBS_SAVE="$LIBS"
--    LIBS="$LIBS -ldb"
--    AC_LINK_IFELSE([AC_LANG_PROGRAM([
--      #define DB_DBM_HSEARCH 1
--      #include <db.h>
--      #if DB_VERSION_MAJOR < 5
--        #error "dh.h: DB_VERSION_MAJOR < 5 is not supported."
--      #endif
--      ], [DBM *dbm = dbm_open(NULL, 0, 0)])
--    ], [ac_cv_have_libdb=yes], [ac_cv_have_libdb=no])
--    LIBS="$LIBS_SAVE"
-+    WITH_SAVE_ENV([
-+      LIBS="$LIBS -ldb"
-+      AC_LINK_IFELSE([AC_LANG_PROGRAM([
-+        #define DB_DBM_HSEARCH 1
-+        #include <db.h>
-+        #if DB_VERSION_MAJOR < 5
-+          #error "dh.h: DB_VERSION_MAJOR < 5 is not supported."
-+        #endif
-+        ], [DBM *dbm = dbm_open(NULL, 0, 0)])
-+      ], [ac_cv_have_libdb=yes], [ac_cv_have_libdb=no])
-+    ])
-   ])
-   AS_VAR_IF([ac_cv_have_libdb], [yes], [
-     AC_DEFINE([HAVE_LIBDB], [1], [Define to 1 if you have the `db' library (-ldb).])
-@@ -4018,7 +4031,7 @@ AC_CHECK_HEADERS([db.h], [
- ])
- 
- # Check for --with-dbmliborder
--AC_MSG_CHECKING(for --with-dbmliborder)
-+AC_MSG_CHECKING([for --with-dbmliborder])
- AC_ARG_WITH(dbmliborder,
-             AS_HELP_STRING([--with-dbmliborder=db1:db2:...], [override order to check db backends for dbm; a valid value is a colon separated string with the backend names `ndbm', `gdbm' and `bdb'.]),
- [], [with_dbmliborder=gdbm:ndbm:bdb])
-@@ -4038,7 +4051,42 @@ IFS=$as_save_IFS
- AS_VAR_IF([with_dbmliborder], [error], [
-   AC_MSG_ERROR([proper usage is --with-dbmliborder=db1:db2:... (gdbm:ndbm:bdb)])
- ])
--AC_MSG_RESULT($with_dbmliborder)
-+AC_MSG_RESULT([$with_dbmliborder])
-+
-+AC_MSG_CHECKING([for _dbm module CFLAGS and LIBS])
-+have_dbm=no
-+as_save_IFS=$IFS
-+IFS=:
-+for db in $with_dbmliborder; do
-+  case "$db" in
-+    ndbm)
-+      if test "$have_ndbm" = yes; then
-+        DBM_CFLAGS="-DUSE_NDBM"
-+        DBM_LIBS="$dbm_ndbm"
-+        have_dbm=yes
-+        break
-+      fi
-+      ;;
-+    gdbm)
-+      if test "$have_gdbm_compat" = yes; then
-+        DBM_CFLAGS="-DUSE_GDBM_COMPAT"
-+        DBM_LIBS="-lgdbm_compat"
-+        have_dbm=yes
-+        break
-+      fi
-+      ;;
-+    bdb)
-+      if test "$ac_cv_have_libdb" = yes; then
-+        DBM_CFLAGS="-DUSE_BERKDB"
-+        DBM_LIBS="-ldb"
-+        have_dbm=yes
-+        break
-+      fi
-+     ;;
-+  esac
-+done
-+IFS=$as_save_IFS
-+AC_MSG_RESULT([$DBM_CFLAGS $DBM_LIBS])
- 
- # Templates for things AC_DEFINEd more than once.
- # For a single AC_DEFINE, no template is needed.
-@@ -6940,7 +6988,9 @@ PY_STDLIB_MOD([_ctypes],
- dnl PY_STDLIB_MOD([_curses], [], [], [], [])
- dnl PY_STDLIB_MOD([_curses_panel], [], [], [], [])
- PY_STDLIB_MOD([_decimal], [], [], [$LIBMPDEC_CFLAGS], [$LIBMPDEC_LDFLAGS])
--dnl PY_STDLIB_MOD([_dbm], [], [], [], [])
-+PY_STDLIB_MOD([_dbm],
-+  [test -n "$with_dbmliborder"], [test "$have_dbm" != "no"],
-+  [$DBM_CFLAGS], [$DBM_LIBS])
- PY_STDLIB_MOD([_gdbm],
-   [test "$have_gdbm_dbmliborder" = yes], [test "$have_gdbm" = yes],
-   [$GDBM_CFLAGS], [$GDBM_LIBS])
-diff --git a/pyconfig.h.in b/pyconfig.h.in
-index 15933e75b1b07..b05ddd41c2bba 100644
---- a/pyconfig.h.in
-+++ b/pyconfig.h.in
-@@ -640,18 +640,12 @@
- /* Define to 1 if you have the `dld' library (-ldld). */
- #undef HAVE_LIBDLD
- 
--/* Define to 1 if you have the `gdbm_compat' library (-lgdbm_compat). */
--#undef HAVE_LIBGDBM_COMPAT
--
- /* Define to 1 if you have the `ieee' library (-lieee). */
- #undef HAVE_LIBIEEE
- 
- /* Define to 1 if you have the <libintl.h> header file. */
- #undef HAVE_LIBINTL_H
- 
--/* Define to 1 if you have the `ndbm' library (-lndbm). */
--#undef HAVE_LIBNDBM
--
- /* Define to build the readline module. */
- #undef HAVE_LIBREADLINE
- 
-diff --git a/setup.py b/setup.py
-index 843ec35effe10..cc11dedee1b2e 100644
---- a/setup.py
-+++ b/setup.py
-@@ -1163,77 +1163,7 @@ def detect_crypt(self):
-         self.addext(Extension('_crypt', ['_cryptmodule.c']))
- 
-     def detect_dbm_gdbm(self):
--        # Modules that provide persistent dictionary-like semantics.  You will
--        # probably want to arrange for at least one of them to be available on
--        # your machine, though none are defined by default because of library
--        # dependencies.  The Python module dbm/__init__.py provides an
--        # implementation independent wrapper for these; dbm/dumb.py provides
--        # similar functionality (but slower of course) implemented in Python.
--
--        dbm_setup_debug = False   # verbose debug prints from this script?
--        dbm_order = ['gdbm']
--
--        # libdb, gdbm and ndbm headers and libraries
--        have_ndbm_h = sysconfig.get_config_var("HAVE_NDBM_H")
--        have_gdbm_ndbm_h = sysconfig.get_config_var("HAVE_GDBM_NDBM_H")
--        have_gdbm_dash_ndbm_h = sysconfig.get_config_var("HAVE_GDBM_DASH_NDBM_H")
--        have_libndbm = sysconfig.get_config_var("HAVE_LIBNDBM")
--        have_libgdbm_compat = sysconfig.get_config_var("HAVE_LIBGDBM_COMPAT")
--        have_libdb = sysconfig.get_config_var("HAVE_LIBDB")
--
--        # The standard Unix dbm module:
--        if not CYGWIN:
--            config_args = [arg.strip("'")
--                           for arg in sysconfig.get_config_var("CONFIG_ARGS").split()]
--            dbm_args = [arg for arg in config_args
--                        if arg.startswith('--with-dbmliborder=')]
--            if dbm_args:
--                dbm_order = [arg.split('=')[-1] for arg in dbm_args][-1].split(":")
--            else:
--                dbm_order = "gdbm:ndbm:bdb".split(":")
--            dbmext = None
--            for cand in dbm_order:
--                if cand == "ndbm":
--                    if have_ndbm_h:
--                        # Some systems have -lndbm, others have -lgdbm_compat,
--                        # others don't have either
--                        if have_libndbm:
--                            ndbm_libs = ['ndbm']
--                        elif have_libgdbm_compat:
--                            ndbm_libs = ['gdbm_compat']
--                        else:
--                            ndbm_libs = []
--                        if dbm_setup_debug: print("building dbm using ndbm")
--                        dbmext = Extension(
--                            '_dbm', ['_dbmmodule.c'],
--                            define_macros=[('USE_NDBM', None)],
--                            libraries=ndbm_libs
--                        )
--                        break
--                elif cand == "gdbm":
--                    # dbm_open() is provided by libgdbm_compat, which wraps libgdbm
--                    if have_libgdbm_compat and (have_gdbm_ndbm_h or have_gdbm_dash_ndbm_h):
--                        if dbm_setup_debug: print("building dbm using gdbm")
--                        dbmext = Extension(
--                            '_dbm', ['_dbmmodule.c'],
--                            define_macros=[('USE_GDBM_COMPAT', None)],
--                            libraries=['gdbm_compat']
--                        )
--                        break
--                elif cand == "bdb":
--                    if have_libdb:
--                        if dbm_setup_debug: print("building dbm using bdb")
--                        dbmext = Extension(
--                            '_dbm', ['_dbmmodule.c'],
--                            define_macros=[('USE_BERKDB', None)],
--                            libraries=['db']
--                        )
--                        break
--            if dbmext is not None:
--                self.add(dbmext)
--            else:
--                self.missing.append('_dbm')
--
-+        self.addext(Extension('_dbm', ['_dbmmodule.c']))
-         # Anthony Baxter's gdbm module.  GNU dbm(3) will require -lgdbm:
-         self.addext(Extension('_gdbm', ['_gdbmmodule.c']))
- 
diff --git a/package/python3/0019-Port-_ctypes-to-PY_STDLIB_MOD.patch b/package/python3/0019-Port-_ctypes-to-PY_STDLIB_MOD.patch
deleted file mode 100644
index f8e3e43927..0000000000
--- a/package/python3/0019-Port-_ctypes-to-PY_STDLIB_MOD.patch
+++ /dev/null
@@ -1,441 +0,0 @@
-From bb8b931385ba9df4e01f7dd3ce4575d49f60efdf Mon Sep 17 00:00:00 2001
-From: Christian Heimes <christian@python.org>
-Date: Sun, 26 Jun 2022 13:04:43 +0200
-Subject: [PATCH] gh-90005: Port _ctypes to PY_STDLIB_MOD (GH-32229)
-
-Co-authored-by: Erlend Egeberg Aasland <erlend.aasland@innova.no>
-
-Automerge-Triggered-By: GH:tiran
-
-Upstream: https://github.com/python/cpython/commit/bb8b931385ba9df4e01f7dd3ce4575d49f60efdf
-
-[Bernd: backported to 3.11.4]
-Signed-off-by: Bernd Kuhls <bernd@kuhls.net>
----
- Makefile.pre.in                               |   9 +-
- ...2-04-01-12-35-44.gh-issue-90005.pvaLHQ.rst |   1 +
- Modules/Setup.stdlib.in                       |   2 +-
- Modules/_ctypes/callproc.c                    |   3 +
- Modules/_ctypes/malloc_closure.c              |   3 +
- configure                                     | 540 +++++++++++++++++-
- configure.ac                                  | 125 +++-
- pyconfig.h.in                                 |   9 +
- setup.py                                      | 115 +---
- 9 files changed, 660 insertions(+), 147 deletions(-)
- create mode 100644 Misc/NEWS.d/next/Library/2022-04-01-12-35-44.gh-issue-90005.pvaLHQ.rst
-
-diff --git a/Makefile.pre.in b/Makefile.pre.in
-index 102cd752c39cd..c0333cea48cd7 100644
---- a/Makefile.pre.in
-+++ b/Makefile.pre.in
-@@ -340,10 +340,6 @@ IO_OBJS=	\
- 		Modules/_io/bytesio.o \
- 		Modules/_io/stringio.o
- 
--##########################################################################
--
--LIBFFI_INCLUDEDIR=	@LIBFFI_INCLUDEDIR@
--
- ##########################################################################
- # Parser
- 
-@@ -2595,7 +2595,8 @@
- MODULE_PYEXPAT_DEPS=@LIBEXPAT_INTERNAL@
- MODULE_UNICODEDATA_DEPS=$(srcdir)/Modules/unicodedata_db.h $(srcdir)/Modules/unicodename_db.h
- MODULE__BLAKE2_DEPS=$(srcdir)/Modules/_blake2/impl/blake2-config.h $(srcdir)/Modules/_blake2/impl/blake2-impl.h $(srcdir)/Modules/_blake2/impl/blake2.h $(srcdir)/Modules/_blake2/impl/blake2b-load-sse2.h $(srcdir)/Modules/_blake2/impl/blake2b-load-sse41.h $(srcdir)/Modules/_blake2/impl/blake2b-ref.c $(srcdir)/Modules/_blake2/impl/blake2b-round.h $(srcdir)/Modules/_blake2/impl/blake2b.c $(srcdir)/Modules/_blake2/impl/blake2s-load-sse2.h $(srcdir)/Modules/_blake2/impl/blake2s-load-sse41.h $(srcdir)/Modules/_blake2/impl/blake2s-load-xop.h $(srcdir)/Modules/_blake2/impl/blake2s-ref.c $(srcdir)/Modules/_blake2/impl/blake2s-round.h $(srcdir)/Modules/_blake2/impl/blake2s.c $(srcdir)/Modules/_blake2/blake2module.h $(srcdir)/Modules/hashlib.h
--MODULE__CTYPES_DEPS=$(srcdir)/Modules/_ctypes/ctypes.h
-+MODULE__CTYPES_DEPS=$(srcdir)/Modules/_ctypes/ctypes.h $(srcdir)/Modules/_ctypes/darwin/dlfcn.h
-+MODULE__CTYPES_MALLOC_CLOSURE=@MODULE__CTYPES_MALLOC_CLOSURE@
- MODULE__DECIMAL_DEPS=$(srcdir)/Modules/_decimal/docstrings.h @LIBMPDEC_INTERNAL@
- MODULE__ELEMENTTREE_DEPS=$(srcdir)/Modules/pyexpat.c @LIBEXPAT_INTERNAL@
- MODULE__HASHLIB_DEPS=$(srcdir)/Modules/hashlib.h
-diff --git a/Misc/NEWS.d/next/Library/2022-04-01-12-35-44.gh-issue-90005.pvaLHQ.rst b/Misc/NEWS.d/next/Library/2022-04-01-12-35-44.gh-issue-90005.pvaLHQ.rst
-new file mode 100644
-index 0000000000000..ef6a881a4d094
---- /dev/null
-+++ b/Misc/NEWS.d/next/Library/2022-04-01-12-35-44.gh-issue-90005.pvaLHQ.rst
-@@ -0,0 +1 @@
-+:mod:`ctypes` dependency ``libffi`` is now detected with ``pkg-config``.
-diff --git a/Modules/Setup.stdlib.in b/Modules/Setup.stdlib.in
-index 2730030a15650..a199aefc51011 100644
---- a/Modules/Setup.stdlib.in
-+++ b/Modules/Setup.stdlib.in
-@@ -136,7 +136,7 @@
- #
- 
- # needs -lffi and -ldl
--#@MODULE__CTYPES_TRUE@_ctypes _ctypes/_ctypes.c _ctypes/callbacks.c _ctypes/callproc.c _ctypes/stgdict.c _ctypes/cfield.c
-+@MODULE__CTYPES_TRUE@_ctypes _ctypes/_ctypes.c _ctypes/callbacks.c _ctypes/callproc.c _ctypes/stgdict.c _ctypes/cfield.c @MODULE__CTYPES_MALLOC_CLOSURE@
- 
- # needs -lncurses, -lncursesw or -lcurses, sometimes -ltermcap
- #@MODULE__CURSES_TRUE@_curses _cursesmodule.c
-diff --git a/Modules/_ctypes/callproc.c b/Modules/_ctypes/callproc.c
-index 3fab9ad0c1e7b..fa1dfac6c7d94 100644
---- a/Modules/_ctypes/callproc.c
-+++ b/Modules/_ctypes/callproc.c
-@@ -54,6 +54,9 @@
- 
-  */
- 
-+#ifndef Py_BUILD_CORE_BUILTIN
-+#  define Py_BUILD_CORE_MODULE 1
-+#endif
- #define NEEDS_PY_IDENTIFIER
- 
- #include "Python.h"
-diff --git a/Modules/_ctypes/malloc_closure.c b/Modules/_ctypes/malloc_closure.c
-index 38edc90e70763..d47153f1d7f3e 100644
---- a/Modules/_ctypes/malloc_closure.c
-+++ b/Modules/_ctypes/malloc_closure.c
-@@ -1,3 +1,6 @@
-+#ifndef Py_BUILD_CORE_BUILTIN
-+#  define Py_BUILD_CORE_MODULE 1
-+#endif
- #include <Python.h>
- #include <ffi.h>
- #ifdef MS_WIN32
-diff --git a/configure.ac b/configure.ac
-index f9abd851ea5cb..6a8a0a963afa2 100644
---- a/configure.ac
-+++ b/configure.ac
-@@ -49,6 +49,26 @@ AC_DEFUN([WITH_SAVE_ENV],
- [RESTORE_ENV]
- )dnl
- 
-+dnl PY_CHECK_FUNC(FUNCTION, [INCLUDES], [AC_DEFINE-VAR])
-+AC_DEFUN([PY_CHECK_FUNC],
-+[ AS_VAR_PUSHDEF([py_var], [ac_cv_func_$1])
-+  AS_VAR_PUSHDEF([py_define], m4_ifblank([$3], [[HAVE_]m4_toupper($1)], [$3]))
-+  AC_CACHE_CHECK(
-+    [for $1],
-+    [py_var],
-+    [AC_COMPILE_IFELSE(
-+      [AC_LANG_PROGRAM([$2], [void *x=$1])],
-+      [AS_VAR_SET([py_var], [yes])],
-+      [AS_VAR_SET([py_var], [no])])]
-+  )
-+  AS_VAR_IF(
-+    [py_var],
-+    [yes],
-+    [AC_DEFINE([py_define], [1], [Define if you have the '$1' function.])])
-+  AS_VAR_POPDEF([py_var])
-+  AS_VAR_POPDEF([py_define])
-+])
-+
- AC_SUBST(BASECPPFLAGS)
- if test "$srcdir" != . -a "$srcdir" != "$(pwd)"; then
-     # If we're building out-of-tree, we need to make sure the following
-@@ -713,6 +733,21 @@ fi
- 
- if test "$ac_sys_system" = "Darwin"
- then
-+  dnl look for SDKROOT
-+  AC_CHECK_PROG([HAS_XCRUN], [xcrun], [yes], [missing])
-+  AC_MSG_CHECKING([macOS SDKROOT])
-+  if test -z "$SDKROOT"; then
-+    dnl SDKROOT not set
-+    if test "$HAS_XCRUN" = "yes"; then
-+      dnl detect with Xcode
-+      SDKROOT=$(xcrun --show-sdk-path)
-+    else
-+      dnl default to root
-+      SDKROOT="/"
-+    fi
-+  fi
-+  AC_MSG_RESULT([$SDKROOT])
-+
- 	# Compiler selection on MacOSX is more complicated than
- 	# AC_PROG_CC can handle, see Mac/README for more
- 	# information
-@@ -1101,7 +1136,7 @@ AC_DEFINE_UNQUOTED([PY_SUPPORT_TIER], [$PY_SUPPORT_TIER], [PEP 11 Support tier (
- 
- AC_CACHE_CHECK([for -Wl,--no-as-needed], [ac_cv_wl_no_as_needed], [
-   save_LDFLAGS="$LDFLAGS"
--  AS_VAR_APPEND([LDFLAGS], [-Wl,--no-as-needed])
-+  AS_VAR_APPEND([LDFLAGS], [" -Wl,--no-as-needed"])
-   AC_LINK_IFELSE([AC_LANG_PROGRAM([[]], [[]])],
-     [NO_AS_NEEDED="-Wl,--no-as-needed"
-      ac_cv_wl_no_as_needed=yes],
-@@ -3564,12 +3599,60 @@ else
-     with_system_ffi="yes"
- fi
- 
--if test "$with_system_ffi" = "yes" && test -n "$PKG_CONFIG"; then
--    LIBFFI_INCLUDEDIR="`"$PKG_CONFIG" libffi --cflags-only-I 2>/dev/null | sed -e 's/^-I//;s/ *$//'`"
--else
--    LIBFFI_INCLUDEDIR=""
--fi
--AC_SUBST(LIBFFI_INCLUDEDIR)
-+dnl detect libffi
-+have_libffi=missing
-+AS_VAR_IF([with_system_ffi], [yes], [
-+  PKG_CHECK_MODULES([LIBFFI], [libffi], [have_libffi=yes], [
-+    AC_CHECK_HEADER([ffi.h], [
-+      WITH_SAVE_ENV([
-+        AC_CHECK_LIB([ffi], [ffi_call], [have_libffi=yes], [have_libffi=no])
-+      ])
-+    ])
-+  ])
-+], [
-+  AS_VAR_IF([ac_sys_system], [Darwin], [
-+    WITH_SAVE_ENV([
-+      CFLAGS="-I${SDKROOT}/usr/include/ffi $CFLAGS"
-+      AC_CHECK_HEADER([ffi.h], [
-+        AC_CHECK_LIB([ffi], [ffi_call], [
-+          dnl use ffi from SDK root
-+          have_libffi=yes
-+          LIBFFI_CFLAGS="-I${SDKROOT}/usr/include/ffi -DUSING_APPLE_OS_LIBFFI=1"
-+          LIBFFI_LIBS="-lffi"
-+        ], [have_libffi=no])
-+      ])
-+    ])
-+  ])
-+])
-+
-+AS_VAR_IF([have_libffi], [yes], [
-+  ctypes_malloc_closure=no
-+  AS_CASE([$ac_sys_system],
-+    [Darwin], [
-+      dnl when do we need USING_APPLE_OS_LIBFFI?
-+      AS_VAR_APPEND([LIBFFI_CFLAGS], [" -I\$(srcdir)/Modules/_ctypes/darwin -DMACOSX"])
-+      ctypes_malloc_closure=yes
-+    ],
-+    [sunos5], [AS_VAR_APPEND([LIBFFI_LIBS], [" -mimpure-text"])]
-+  )
-+  AS_VAR_IF([ctypes_malloc_closure], [yes], [
-+    MODULE__CTYPES_MALLOC_CLOSURE=_ctypes/malloc_closure.c
-+    AS_VAR_APPEND([LIBFFI_CFLAGS], [" -DUSING_MALLOC_CLOSURE_DOT_C=1"])
-+  ])
-+  AC_SUBST([MODULE__CTYPES_MALLOC_CLOSURE])
-+
-+  dnl HAVE_LIBDL: for dlopen, see gh-76828
-+  AS_VAR_IF([ac_cv_lib_dl_dlopen], [yes], [AS_VAR_APPEND([LIBFFI_LIBS], [" -ldl"])])
-+
-+  WITH_SAVE_ENV([
-+    CFLAGS="$LIBFFI_CFLAGS $CFLAGS"
-+    LDFLAGS="$LIBFFI_LIBS $LDFLAGS"
-+
-+    PY_CHECK_FUNC([ffi_prep_cif_var], [#include <ffi.h>])
-+    PY_CHECK_FUNC([ffi_prep_closure_loc], [#include <ffi.h>])
-+    PY_CHECK_FUNC([ffi_closure_alloc], [#include <ffi.h>])
-+  ])
-+])
- 
- # Check for use of the system libmpdec library
- AC_MSG_CHECKING(for --with-system-libmpdec)
-@@ -4526,26 +4609,6 @@ AC_CHECK_DECL(dirfd,
-       [#include <sys/types.h>
-        #include <dirent.h>])
- 
--dnl PY_CHECK_FUNC(FUNCTION, [INCLUDES], [AC_DEFINE-VAR])
--AC_DEFUN([PY_CHECK_FUNC],
--[ AS_VAR_PUSHDEF([py_var], [ac_cv_func_$1])
--  AS_VAR_PUSHDEF([py_define], m4_ifblank([$3], [[HAVE_]m4_toupper($1)], [$3]))
--  AC_CACHE_CHECK(
--    [for $1],
--    [py_var],
--    [AC_COMPILE_IFELSE(
--      [AC_LANG_PROGRAM([$2], [void *x=$1])],
--      [AS_VAR_SET([py_var], [yes])],
--      [AS_VAR_SET([py_var], [no])])]
--  )
--  AS_VAR_IF(
--    [py_var],
--    [yes],
--    [AC_DEFINE([py_define], [1], [Define if you have the '$1' function.])])
--  AS_VAR_POPDEF([py_var])
--  AS_VAR_POPDEF([py_define])
--])
--
- # For some functions, having a definition is not sufficient, since
- # we want to take their address.
- PY_CHECK_FUNC([chroot], [#include <unistd.h>])
-@@ -6868,7 +6931,9 @@ PY_STDLIB_MOD([_blake2],
- PY_STDLIB_MOD([_crypt],
-   [], [test "$ac_cv_crypt_crypt" = yes],
-   [$LIBCRYPT_CFLAGS], [$LIBCRYPT_LIBS])
--dnl PY_STDLIB_MOD([_ctypes], [], [], [], [])
-+PY_STDLIB_MOD([_ctypes],
-+  [], [test "$have_libffi" = yes],
-+  [$LIBFFI_CFLAGS], [$LIBFFI_LIBS])
- dnl PY_STDLIB_MOD([_curses], [], [], [], [])
- dnl PY_STDLIB_MOD([_curses_panel], [], [], [], [])
- PY_STDLIB_MOD([_decimal], [], [], [$LIBMPDEC_CFLAGS], [$LIBMPDEC_LDFLAGS])
-@@ -6914,7 +6979,9 @@ PY_STDLIB_MOD([_testbuffer], [test "$TEST_MODULES" = yes])
- PY_STDLIB_MOD([_testimportmultiple], [test "$TEST_MODULES" = yes], [test "$ac_cv_func_dlopen" = yes])
- PY_STDLIB_MOD([_testmultiphase], [test "$TEST_MODULES" = yes], [test "$ac_cv_func_dlopen" = yes])
- PY_STDLIB_MOD([_xxtestfuzz], [test "$TEST_MODULES" = yes])
--PY_STDLIB_MOD([_ctypes_test], [test "$TEST_MODULES" = yes], [test "$ac_cv_func_dlopen" = yes], [], [-lm])
-+PY_STDLIB_MOD([_ctypes_test],
-+  [test "$TEST_MODULES" = yes], [test "$have_libffi" = yes -a "$ac_cv_func_dlopen" = yes],
-+  [], [$LIBM])
- 
- dnl Limited API template modules.
- dnl The limited C API is not compatible with the Py_TRACE_REFS macro.
-diff --git a/pyconfig.h.in b/pyconfig.h.in
-index a09652ec15e53..15933e75b1b07 100644
---- a/pyconfig.h.in
-+++ b/pyconfig.h.in
-@@ -356,6 +356,15 @@
- /* Define to 1 if you have the `fexecve' function. */
- #undef HAVE_FEXECVE
- 
-+/* Define if you have the 'ffi_closure_alloc' function. */
-+#undef HAVE_FFI_CLOSURE_ALLOC
-+
-+/* Define if you have the 'ffi_prep_cif_var' function. */
-+#undef HAVE_FFI_PREP_CIF_VAR
-+
-+/* Define if you have the 'ffi_prep_closure_loc' function. */
-+#undef HAVE_FFI_PREP_CLOSURE_LOC
-+
- /* Define to 1 if you have the `flock' function. */
- #undef HAVE_FLOCK
- 
-diff --git a/setup.py b/setup.py
-index bba344c3af07c..af2800744091c 100644
---- a/setup.py
-+++ b/setup.py
-@@ -395,11 +395,6 @@ def remove_disabled(self):
-         # Remove modules that are present on the disabled list
-         extensions = [ext for ext in self.extensions
-                       if ext.name not in DISABLED_MODULE_LIST]
--        # move ctypes to the end, it depends on other modules
--        ext_map = dict((ext.name, i) for i, ext in enumerate(extensions))
--        if "_ctypes" in ext_map:
--            ctypes = extensions.pop(ext_map["_ctypes"])
--            extensions.append(ctypes)
-         self.extensions = extensions
- 
-     def update_sources_depends(self):
-@@ -600,12 +595,6 @@ def print_three_column(lst):
-             raise RuntimeError("Failed to build some stdlib modules")
- 
-     def build_extension(self, ext):
--
--        if ext.name == '_ctypes':
--            if not self.configure_ctypes(ext):
--                self.failed.append(ext.name)
--                return
--
-         try:
-             build_ext.build_extension(self, ext)
-         except (CCompilerError, DistutilsError) as why:
-@@ -1370,102 +1359,24 @@ def detect_modules(self):
-     def detect_tkinter(self):
-         self.addext(Extension('_tkinter', ['_tkinter.c', 'tkappinit.c']))
- 
--    def configure_ctypes(self, ext):
--        return True
--
-     def detect_ctypes(self):
-         # Thomas Heller's _ctypes module
-+        src = [
-+            '_ctypes/_ctypes.c',
-+            '_ctypes/callbacks.c',
-+            '_ctypes/callproc.c',
-+            '_ctypes/stgdict.c',
-+            '_ctypes/cfield.c',
-+        ]
-+        malloc_closure = sysconfig.get_config_var(
-+            "MODULE__CTYPES_MALLOC_CLOSURE"
-+        )
-+        if malloc_closure:
-+            src.append(malloc_closure)
- 
--        if (not sysconfig.get_config_var("LIBFFI_INCLUDEDIR") and MACOS):
--            self.use_system_libffi = True
--        else:
--            self.use_system_libffi = '--with-system-ffi' in sysconfig.get_config_var("CONFIG_ARGS")
--
--        include_dirs = []
--        extra_compile_args = []
--        extra_link_args = []
--        sources = ['_ctypes/_ctypes.c',
--                   '_ctypes/callbacks.c',
--                   '_ctypes/callproc.c',
--                   '_ctypes/stgdict.c',
--                   '_ctypes/cfield.c']
--
--        if MACOS:
--            sources.append('_ctypes/malloc_closure.c')
--            extra_compile_args.append('-DUSING_MALLOC_CLOSURE_DOT_C=1')
--            extra_compile_args.append('-DMACOSX')
--            include_dirs.append('_ctypes/darwin')
--
--        elif HOST_PLATFORM == 'sunos5':
--            # XXX This shouldn't be necessary; it appears that some
--            # of the assembler code is non-PIC (i.e. it has relocations
--            # when it shouldn't. The proper fix would be to rewrite
--            # the assembler code to be PIC.
--            # This only works with GCC; the Sun compiler likely refuses
--            # this option. If you want to compile ctypes with the Sun
--            # compiler, please research a proper solution, instead of
--            # finding some -z option for the Sun compiler.
--            extra_link_args.append('-mimpure-text')
--
--        ext = Extension('_ctypes',
--                        include_dirs=include_dirs,
--                        extra_compile_args=extra_compile_args,
--                        extra_link_args=extra_link_args,
--                        libraries=[],
--                        sources=sources)
--        self.add(ext)
--        # function my_sqrt() needs libm for sqrt()
-+        self.addext(Extension('_ctypes', src))
-         self.addext(Extension('_ctypes_test', ['_ctypes/_ctypes_test.c']))
- 
--        ffi_inc = sysconfig.get_config_var("LIBFFI_INCLUDEDIR")
--        ffi_lib = None
--
--        ffi_inc_dirs = self.inc_dirs.copy()
--        if MACOS:
--            ffi_in_sdk = os.path.join(macosx_sdk_root(), "usr/include/ffi")
--
--            if not ffi_inc:
--                if os.path.exists(ffi_in_sdk):
--                    ext.extra_compile_args.append("-DUSING_APPLE_OS_LIBFFI=1")
--                    ffi_inc = ffi_in_sdk
--                    ffi_lib = 'ffi'
--                else:
--                    # OS X 10.5 comes with libffi.dylib; the include files are
--                    # in /usr/include/ffi
--                    ffi_inc_dirs.append('/usr/include/ffi')
--
--        if not ffi_inc:
--            found = find_file('ffi.h', [], ffi_inc_dirs)
--            if found:
--                ffi_inc = found[0]
--        if ffi_inc:
--            ffi_h = ffi_inc + '/ffi.h'
--            if not os.path.exists(ffi_h):
--                ffi_inc = None
--                print('Header file {} does not exist'.format(ffi_h))
--        if ffi_lib is None and ffi_inc:
--            for lib_name in ('ffi', 'ffi_pic'):
--                if (self.compiler.find_library_file(self.lib_dirs, lib_name)):
--                    ffi_lib = lib_name
--                    break
--
--        if ffi_inc and ffi_lib:
--            ffi_headers = glob(os.path.join(ffi_inc, '*.h'))
--            if grep_headers_for('ffi_prep_cif_var', ffi_headers):
--                ext.extra_compile_args.append("-DHAVE_FFI_PREP_CIF_VAR=1")
--            if grep_headers_for('ffi_prep_closure_loc', ffi_headers):
--                ext.extra_compile_args.append("-DHAVE_FFI_PREP_CLOSURE_LOC=1")
--            if grep_headers_for('ffi_closure_alloc', ffi_headers):
--                ext.extra_compile_args.append("-DHAVE_FFI_CLOSURE_ALLOC=1")
--
--            ext.include_dirs.append(ffi_inc)
--            ext.libraries.append(ffi_lib)
--            self.use_system_libffi = True
--
--        if sysconfig.get_config_var('HAVE_LIBDL'):
--            # for dlopen, see bpo-32647
--            ext.libraries.append('dl')
--
-     def detect_decimal(self):
-         # Stefan Krah's _decimal module
-         self.addext(
---- Makefile.pre.in.orig	2023-08-07 20:50:54.600398448 +0200
-+++ Makefile.pre.in	2023-08-07 20:53:37.130317846 +0200
diff --git a/package/python3/0020-Port-readline-and-curses-to-PY_STDLIB_MOD.patch b/package/python3/0020-Port-readline-and-curses-to-PY_STDLIB_MOD.patch
deleted file mode 100644
index 43a5eb07ef..0000000000
--- a/package/python3/0020-Port-readline-and-curses-to-PY_STDLIB_MOD.patch
+++ /dev/null
@@ -1,718 +0,0 @@
-From e925241d95d8095adf67f492042f97254ff82ec1 Mon Sep 17 00:00:00 2001
-From: Christian Heimes <christian@python.org>
-Date: Wed, 6 Jul 2022 11:56:25 +0200
-Subject: [PATCH] gh-90005: Port readline and curses to PY_STDLIB_MOD
- (GH-94452)
-
-Co-authored-by: Erlend Egeberg Aasland <erlend.aasland@protonmail.com>
-
-Upstream: https://github.com/python/cpython/commit/e925241d95d8095adf67f492042f97254ff82ec1
-
-[Bernd: backported to 3.11.4]
-Signed-off-by: Bernd Kuhls <bernd@kuhls.net>
----
- ...2-06-30-17-18-23.gh-issue-90005.EIOOla.rst |    5 +
- Modules/Setup.stdlib.in                       |   12 +-
- configure                                     | 1977 ++++++++++++++---
- configure.ac                                  |  388 +++-
- pyconfig.h.in                                 |   17 +-
- setup.py                                      |  146 +-
- 6 files changed, 2015 insertions(+), 530 deletions(-)
- create mode 100644 Misc/NEWS.d/next/Build/2022-06-30-17-18-23.gh-issue-90005.EIOOla.rst
-
-diff --git a/Misc/NEWS.d/next/Build/2022-06-30-17-18-23.gh-issue-90005.EIOOla.rst b/Misc/NEWS.d/next/Build/2022-06-30-17-18-23.gh-issue-90005.EIOOla.rst
-new file mode 100644
-index 0000000000000..90a2dd486c195
---- /dev/null
-+++ b/Misc/NEWS.d/next/Build/2022-06-30-17-18-23.gh-issue-90005.EIOOla.rst
-@@ -0,0 +1,5 @@
-+Dependencies of :mod:`readline` and :mod:`curses` module are now detected in
-+``configure`` script with ``pkg-config``. Only ``ncurses`` / ``ncursesw``
-+are detected automatically. The old ``curses`` library is not configured
-+automatically. Workaround for missing ``termcap`` or ``tinfo`` library
-+has been removed.
-diff --git a/Modules/Setup.stdlib.in b/Modules/Setup.stdlib.in
-index ad34f85e25451..7357aacd7267a 100644
---- a/Modules/Setup.stdlib.in
-+++ b/Modules/Setup.stdlib.in
-@@ -72,8 +72,8 @@
- # gdbm module needs -lgdbm
- @MODULE__GDBM_TRUE@_gdbm _gdbmmodule.c
- 
--# needs -lreadline or -leditline, sometimes termcap, termlib, or tinfo
--#@MODULE_READLINE_TRUE@readline readline.c
-+# needs -lreadline or -ledit, sometimes termcap, termlib, or tinfo
-+@MODULE_READLINE_TRUE@readline readline.c
- 
- # hashing builtins, can be disabled with --without-builtin-hashlib-hashes
- @MODULE__MD5_TRUE@_md5 md5module.c
-@@ -138,10 +138,10 @@
- # needs -lffi and -ldl
- @MODULE__CTYPES_TRUE@_ctypes _ctypes/_ctypes.c _ctypes/callbacks.c _ctypes/callproc.c _ctypes/stgdict.c _ctypes/cfield.c @MODULE__CTYPES_MALLOC_CLOSURE@
- 
--# needs -lncurses, -lncursesw or -lcurses, sometimes -ltermcap
--#@MODULE__CURSES_TRUE@_curses _cursesmodule.c
--# needs -lncurses and -lpanel
--#@MODULE__CURSES_PANEL_TRUE@_curses_panel _curses_panel.c
-+# needs -lncurses[w], sometimes -ltermcap/tinfo
-+@MODULE__CURSES_TRUE@_curses _cursesmodule.c
-+# needs -lncurses[w] and -lpanel[w]
-+@MODULE__CURSES_PANEL_TRUE@_curses_panel _curses_panel.c
- 
- @MODULE__SQLITE3_TRUE@_sqlite3 _sqlite/blob.c _sqlite/connection.c _sqlite/cursor.c _sqlite/microprotocols.c _sqlite/module.c _sqlite/prepare_protocol.c _sqlite/row.c _sqlite/statement.c _sqlite/util.c
- 
-diff --git a/configure.ac b/configure.ac
-index b03ead3bdefa0..42e181bca9dac 100644
---- a/configure.ac
-+++ b/configure.ac
-@@ -5780,127 +5780,169 @@ then
-   [Define this if you have flockfile(), getc_unlocked(), and funlockfile()])
- fi
- 
--AC_ARG_WITH([readline],
--  [AS_HELP_STRING([--with(out)-readline@<:@=editline@:>@],
--    [use Editline for backend or disable readline module])],
--    [],
--    [with_readline=yes])
-+dnl Check for libreadline and libedit
-+dnl - libreadline provides "readline/readline.h" header and "libreadline"
-+dnl   shared library. pkg-config file is readline.pc
-+dnl - libedit provides "editline/readline.h" header and "libedit" shared
-+dnl   library. pkg-config file ins libedit.pc
-+dnl - editline is not supported ("readline.h" and "libeditline" shared library)
-+dnl
-+dnl NOTE: In the past we checked if readline needs an additional termcap
-+dnl library (tinfo ncursesw ncurses termcap). We now assume that libreadline
-+dnl or readline.pc provide correct linker information.
- 
--# check where readline lives
--py_cv_lib_readline=no
--# save the value of LIBS so we don't actually link Python with readline
--LIBS_no_readline=$LIBS
-+AH_TEMPLATE([WITH_EDITLINE], [Define to build the readline module against libedit.])
- 
--if test "$with_readline" != no; then
--  case "$with_readline" in
--  editline|edit)
--    LIBREADLINE=edit
--    AC_DEFINE(WITH_EDITLINE, 1,
--      [Define to build the readline module against Editline.])
--    ;;
--  yes|readline)
-+AC_ARG_WITH(
-+  [readline],
-+  [AS_HELP_STRING([--with(out)-readline@<:@=editline|readline|no@:>@],
-+                  [use libedit for backend or disable readline module])],
-+  [
-+    AS_CASE([$with_readline],
-+      [editline|edit], [with_readline=edit],
-+      [yes|readline], [with_readline=readline],
-+      [no], [],
-+      [AC_MSG_ERROR([proper usage is --with(out)-readline@<:@=editline|readline|no@:>@])]
-+    )
-+  ],
-+  [with_readline=readline]
-+)
-+
-+AS_VAR_IF([with_readline], [readline], [
-+  PKG_CHECK_MODULES([LIBREADLINE], [readline], [
-     LIBREADLINE=readline
--    ;;
--  *)
--    AC_MSG_ERROR([proper usage is --with(out)-readline@<:@=editline@:>@])
--    ;;
--  esac
-+    READLINE_CFLAGS=$LIBREADLINE_CFLAGS
-+    READLINE_LIBS=$LIBREADLINE_LIBS
-+  ], [
-+    AC_CHECK_HEADERS([readline/readline.h], [
-+      WITH_SAVE_ENV([
-+        AC_CHECK_LIB([readline], [readline], [
-+          LIBREADLINE=readline
-+          READLINE_CFLAGS=${LIBREADLINE_CFLAGS-""}
-+          READLINE_LIBS=${LIBREADLINE_LIBS-"-lreadline"}
-+        ], [
-+          with_readline=no
-+        ])
-+      ])
-+    ], [with_readline=no])
-+  ])
-+])
- 
--  # On some systems we need to link readline to a termcap compatible
--  # library.  NOTE: Keep the precedence of listed libraries synchronised
--  # with setup.py.
--  AC_MSG_CHECKING([how to link readline libs])
--  for py_libtermcap in "" tinfo ncursesw ncurses curses termcap; do
--    if test -z "$py_libtermcap"; then
--      READLINE_LIBS="-l$LIBREADLINE"
--    else
--      READLINE_LIBS="-l$LIBREADLINE -l$py_libtermcap"
--    fi
--    LIBS="$READLINE_LIBS $LIBS_no_readline"
--    AC_LINK_IFELSE(
--      [AC_LANG_CALL([],[readline])],
--      [py_cv_lib_readline=yes])
--    if test $py_cv_lib_readline = yes; then
--      break
--    fi
--  done
-+AS_VAR_IF([with_readline], [edit], [
-+  PKG_CHECK_MODULES([LIBEDIT], [libedit], [
-+    AC_DEFINE([WITH_EDITLINE], [1])
-+    LIBREADLINE=edit
-+    READLINE_CFLAGS=$LIBEDIT_CFLAGS
-+    READLINE_LIBS=$LIBEDIT_LIBS
-+  ], [
-+    AC_CHECK_HEADERS([editline/readline.h], [
-+      WITH_SAVE_ENV([
-+        AC_CHECK_LIB([edit], [readline], [
-+          LIBREADLINE=edit
-+          AC_DEFINE([WITH_EDITLINE], [1])
-+          READLINE_CFLAGS=${LIBEDIT_CFLAGS-""}
-+          READLINE_LIBS=${LIBEDIT_LIBS-"-ledit"}
-+        ], [
-+          with_readline=no
-+        ])
-+      ])
-+    ], [with_readline=no])
-+  ])
-+])
- 
--  # Uncomment this line if you want to use READLINE_LIBS in Makefile or scripts
--  #AC_SUBST([READLINE_LIBS])
--  if test $py_cv_lib_readline = no; then
--    AC_MSG_RESULT([none])
--  else
--    AC_MSG_RESULT([$READLINE_LIBS])
--    AC_DEFINE(HAVE_LIBREADLINE, 1,
--      [Define to build the readline module.])
--  fi
--fi
- 
--if test "$py_cv_lib_readline" = yes; then
--  # check for readline 2.2
--  AC_CHECK_DECL(rl_completion_append_character,
--    AC_DEFINE(HAVE_RL_COMPLETION_APPEND_CHARACTER, 1,
--      [Define if you have readline 2.2]),,
--    [
--#include <stdio.h> /* Must be first for Gnu Readline */
--#ifdef WITH_EDITLINE
--# include <editline/readline.h>
--#else
--# include <readline/readline.h>
--#endif
-+AC_MSG_CHECKING([how to link readline])
-+AS_VAR_IF([with_readline], [no], [
-+  AC_MSG_RESULT([no])
-+], [
-+  AC_MSG_RESULT([$with_readline (CFLAGS: $READLINE_CFLAGS, LIBS: $READLINE_LIBS)])
-+
-+  WITH_SAVE_ENV([
-+    CPPFLAGS="$READLINE_CFLAGS $CFLAGS"
-+    LIBS="$READLINE_LIBS $LIBS"
-+    LIBS_SAVE=$LIBS
-+
-+    m4_define([readline_includes], [
-+      #include <stdio.h> /* Must be first for Gnu Readline */
-+      #ifdef WITH_EDITLINE
-+      # include <editline/readline.h>
-+      #else
-+      # include <readline/readline.h>
-+      # include <readline/history.h>
-+      #endif
-     ])
--  AC_CHECK_DECL(rl_completion_suppress_append,
--    AC_DEFINE(HAVE_RL_COMPLETION_SUPPRESS_APPEND, 1,
--      [Define if you have rl_completion_suppress_append]),,
--    [
--#include <stdio.h> /* Must be first for Gnu Readline */
--#ifdef WITH_EDITLINE
--# include <editline/readline.h>
--#else
--# include <readline/readline.h>
--#endif
-+
-+    # check for readline 2.2
-+    AC_CHECK_DECL([rl_completion_append_character], [
-+      AC_DEFINE([HAVE_RL_COMPLETION_APPEND_CHARACTER], [1], [Define if you have readline 2.2])
-+    ], [], [readline_includes])
-+
-+    AC_CHECK_DECL([rl_completion_suppress_append], [
-+      AC_DEFINE([HAVE_RL_COMPLETION_SUPPRESS_APPEND], [1], [Define if you have rl_completion_suppress_append])
-+    ], [], [readline_includes])
-+
-+    # check for readline 4.0
-+    AC_CACHE_CHECK([for rl_pre_input_hook in -l$LIBREADLINE], [ac_cv_readline_rl_pre_input_hook], [
-+      AC_LINK_IFELSE(
-+        [AC_LANG_PROGRAM([readline_includes], [void *x = rl_pre_input_hook])],
-+        [ac_cv_readline_rl_pre_input_hook=yes], [ac_cv_readline_rl_pre_input_hook=no]
-+      )
-+    ])
-+    AS_VAR_IF([ac_cv_readline_rl_pre_input_hook], [yes], [
-+      AC_DEFINE([HAVE_RL_PRE_INPUT_HOOK], [1], [Define if you have readline 4.0])
-     ])
- 
--  # check for readline 4.0
--  AC_CHECK_LIB($LIBREADLINE, rl_pre_input_hook,
--    AC_DEFINE(HAVE_RL_PRE_INPUT_HOOK, 1,
--      [Define if you have readline 4.0]),,$READLINE_LIBS)
--
--  # also in 4.0
--  AC_CHECK_LIB($LIBREADLINE, rl_completion_display_matches_hook,
--    AC_DEFINE(HAVE_RL_COMPLETION_DISPLAY_MATCHES_HOOK, 1,
--      [Define if you have readline 4.0]),,$READLINE_LIBS)
--
--  # also in 4.0, but not in editline
--  AC_CHECK_LIB($LIBREADLINE, rl_resize_terminal,
--    AC_DEFINE(HAVE_RL_RESIZE_TERMINAL, 1,
--      [Define if you have readline 4.0]),,$READLINE_LIBS)
--
--  # check for readline 4.2
--  AC_CHECK_LIB($LIBREADLINE, rl_completion_matches,
--    AC_DEFINE(HAVE_RL_COMPLETION_MATCHES, 1,
--      [Define if you have readline 4.2]),,$READLINE_LIBS)
--
--  # also in readline 4.2
--  AC_CHECK_DECL(rl_catch_signals,
--    AC_DEFINE(HAVE_RL_CATCH_SIGNAL, 1,
--      [Define if you can turn off readline's signal handling.]),,
--    [
--#include <stdio.h> /* Must be first for Gnu Readline */
--#ifdef WITH_EDITLINE
--# include <editline/readline.h>
--#else
--# include <readline/readline.h>
--#endif
-+    # also in 4.0
-+    AC_CACHE_CHECK([for rl_completion_display_matches_hook in -l$LIBREADLINE], [ac_cv_readline_rl_completion_display_matches_hook], [
-+      AC_LINK_IFELSE(
-+        [AC_LANG_PROGRAM([readline_includes], [void *x = rl_completion_display_matches_hook])],
-+        [ac_cv_readline_rl_completion_display_matches_hook=yes], [ac_cv_readline_rl_completion_display_matches_hook=no]
-+      )
-+    ])
-+    AS_VAR_IF([ac_cv_readline_rl_completion_display_matches_hook], [yes], [
-+      AC_DEFINE([HAVE_RL_COMPLETION_DISPLAY_MATCHES_HOOK], [1], [Define if you have readline 4.0])
-     ])
- 
--  AC_CHECK_LIB($LIBREADLINE, append_history,
--    AC_DEFINE(HAVE_RL_APPEND_HISTORY, 1,
--      [Define if readline supports append_history]),,$READLINE_LIBS)
--fi
-+    # also in 4.0, but not in editline
-+      AC_CACHE_CHECK([for rl_resize_terminal in -l$LIBREADLINE], [ac_cv_readline_rl_resize_terminal], [
-+      AC_LINK_IFELSE(
-+        [AC_LANG_PROGRAM([readline_includes], [void *x = rl_resize_terminal])],
-+        [ac_cv_readline_rl_resize_terminal=yes], [ac_cv_readline_rl_resize_terminal=no]
-+      )
-+    ])
-+    AS_VAR_IF([ac_cv_readline_rl_resize_terminal], [yes], [
-+      AC_DEFINE([HAVE_RL_RESIZE_TERMINAL], [1], [Define if you have readline 4.0])
-+    ])
- 
--# End of readline checks: restore LIBS
--LIBS=$LIBS_no_readline
-+    # check for readline 4.2
-+    AC_CACHE_CHECK([for rl_completion_matches in -l$LIBREADLINE], [ac_cv_readline_rl_completion_matches], [
-+      AC_LINK_IFELSE(
-+        [AC_LANG_PROGRAM([readline_includes], [void *x = rl_completion_matches])],
-+        [ac_cv_readline_rl_completion_matches=yes], [ac_cv_readline_rl_completion_matches=no]
-+      )
-+    ])
-+    AS_VAR_IF([ac_cv_readline_rl_completion_matches], [yes], [
-+      AC_DEFINE([HAVE_RL_COMPLETION_MATCHES], [1], [Define if you have readline 4.2])
-+    ])
-+
-+    # also in readline 4.2
-+    AC_CHECK_DECL([rl_catch_signals], [
-+      AC_DEFINE([HAVE_RL_CATCH_SIGNAL], [1], [Define if you can turn off readline's signal handling.])
-+    ], [], [readline_includes])
-+
-+    AC_CACHE_CHECK([for append_history in -l$LIBREADLINE], [ac_cv_readline_append_history], [
-+      AC_LINK_IFELSE(
-+        [AC_LANG_PROGRAM([readline_includes], [void *x = append_history])],
-+        [ac_cv_readline_append_history=yes], [ac_cv_readline_append_history=no]
-+      )
-+    ])
-+    AS_VAR_IF([ac_cv_readline_append_history], [yes], [
-+      AC_DEFINE([HAVE_RL_APPEND_HISTORY], [1], [Define if readline supports append_history])
-+    ])
-+
-+    m4_undefine([readline_includes])
-+  ])dnl WITH_SAVE_ENV()
-+])
- 
- AC_CACHE_CHECK([for broken nice()], [ac_cv_broken_nice], [
- AC_RUN_IFELSE([AC_LANG_SOURCE([[
-@@ -6056,14 +6098,124 @@ then
-   [Define if you have struct stat.st_mtimensec])
- fi
- 
-+dnl check for ncurses/ncursesw and panel/panelw
-+dnl NOTE: old curses is not detected.
-+dnl have_curses=[no, ncursesw, ncurses]
-+dnl have_panel=[no, panelw, panel]
-+have_curses=no
-+have_panel=no
-+
-+AH_TEMPLATE([HAVE_NCURSESW], [Define to 1 if you have the `ncursesw' library.])
-+AC_CHECK_HEADERS([curses.h ncurses.h])
-+
-+AS_VAR_IF([ac_cv_header_ncurses_h], [yes], [
-+  if test "$ac_sys_system" != "Darwin"; then
-+    dnl On macOS, there is no separate /usr/lib/libncursesw nor libpanelw.
-+    PKG_CHECK_MODULES([CURSES], [ncursesw], [
-+      have_curses=ncursesw
-+    ], [
-+      WITH_SAVE_ENV([
-+        AC_CHECK_LIB([ncursesw], [initscr], [
-+          AC_DEFINE([HAVE_NCURSESW], [1])
-+          have_curses=ncursesw
-+          CURSES_CFLAGS=${CURSES_CFLAGS-""}
-+          CURSES_LIBS=${CURSES_LIBS-"-lncursesw"}
-+        ])
-+      ])
-+    ])
-+  fi
-+
-+  AS_VAR_IF([have_curses], [no], [
-+    PKG_CHECK_MODULES([CURSES], [ncurses], [
-+      have_curses=ncurses
-+    ], [
-+      WITH_SAVE_ENV([
-+        AC_CHECK_LIB([ncurses], [initscr], [
-+          have_curses=ncurses
-+          CURSES_CFLAGS=${CURSES_CFLAGS-""}
-+          CURSES_LIBS=${CURSES_LIBS-"-lncurses"}
-+        ])
-+      ])
-+    ])
-+  ])
-+
-+])dnl ac_cv_header_ncurses_h = yes
-+
-+dnl remove _XOPEN_SOURCE macro from curses cflags. pyconfig.h sets
-+dnl the macro to 700.
-+CURSES_CFLAGS=$(echo $CURSES_CFLAGS | sed 's/-D_XOPEN_SOURCE=600//')
-+
-+if test "$have_curses" = no -a "$ac_sys_system" = "Darwin"; then
-+  dnl On macOS, there is no separate /usr/lib/libncursesw nor libpanelw.
-+  dnl If we are here, we found a locally-supplied version of libncursesw.
-+  dnl There should also be a libpanelw.
-+  dnl _XOPEN_SOURCE defines are usually excluded for macOS, but we need
-+  dnl _XOPEN_SOURCE_EXTENDED here for ncurses wide char support.
-+
-+  AS_VAR_APPEND([CURSES_CFLAGS], [" -D_XOPEN_SOURCE_EXTENDED=1"])
-+  AC_DEFINE([HAVE_NCURSESW], [1])
-+fi
-+
-+dnl TODO: detect "curses" and special cases tinfo, terminfo, or termcap
-+
-+AC_MSG_CHECKING([curses module flags])
-+AS_VAR_IF([have_curses], [no], [
-+  AC_MSG_RESULT([no])  
-+], [
-+  AC_MSG_RESULT([$have_curses (CFLAGS: $CURSES_CFLAGS, LIBS: $CURSES_LIBS)])
-+])
-+
-+dnl check for ncurses' panel/panelw library
-+AC_CHECK_HEADERS([panel.h])
-+
-+AS_VAR_IF([ac_cv_header_panel_h], [yes], [
-+
-+  if test "$ac_sys_system" != "Darwin"; then
-+    dnl On macOS, there is no separate /usr/lib/libncursesw nor libpanelw.
-+    AS_VAR_IF([have_curses], [ncursesw], [
-+      PKG_CHECK_MODULES([PANEL], [panelw], [
-+        have_panel=panelw
-+      ], [
-+        WITH_SAVE_ENV([
-+          AC_CHECK_LIB([panelw], [update_panels], [
-+            have_panel=panelw
-+            PANEL_CFLAGS=${PANEL_CFLAGS-""}
-+            PANEL_LIBS=${PANEL_LIBS-"-lpanelw"}
-+          ])
-+        ])
-+      ])
-+    ])
-+  fi
-+
-+  AS_VAR_IF([have_curses], [ncurses], [
-+    PKG_CHECK_MODULES([PANEL], [panel], [
-+      have_panel=panel
-+    ], [
-+      WITH_SAVE_ENV([
-+        AC_CHECK_LIB([panel], [update_panels], [
-+          have_panel=panel
-+          PANEL_CFLAGS=${PANEL_CFLAGS-""}
-+          PANEL_LIBS=${PANEL_LIBS-"-lpanel"}
-+        ])
-+      ])
-+    ])
-+  ])
-+
-+])dnl ac_cv_header_panel_h = yes
-+
-+AC_MSG_CHECKING([panel flags])
-+AS_VAR_IF([have_panel], [no], [
-+  AC_MSG_RESULT([no])  
-+], [
-+  AC_MSG_RESULT([$have_panel (CFLAGS: $PANEL_CFLAGS, LIBS: $PANEL_LIBS)])
-+])
-+
- # first curses header check
- ac_save_cppflags="$CPPFLAGS"
- if test "$cross_compiling" = no; then
-   CPPFLAGS="$CPPFLAGS -I/usr/include/ncursesw"
- fi
- 
--AC_CHECK_HEADERS(curses.h ncurses.h)
--
- # On Solaris, term.h requires curses.h
- AC_CHECK_HEADERS(term.h,,,[
- #ifdef HAVE_CURSES_H
-@@ -6985,8 +7137,14 @@ PY_STDLIB_MOD([_crypt],
- PY_STDLIB_MOD([_ctypes],
-   [], [test "$have_libffi" = yes],
-   [$LIBFFI_CFLAGS], [$LIBFFI_LIBS])
--dnl PY_STDLIB_MOD([_curses], [], [], [], [])
--dnl PY_STDLIB_MOD([_curses_panel], [], [], [], [])
-+PY_STDLIB_MOD([_curses],
-+  [], [test "$have_curses" != "no"],
-+  [$CURSES_CFLAGS], [$CURSES_LIBS]
-+)
-+PY_STDLIB_MOD([_curses_panel],
-+  [], [test "$have_panel" != "no"],
-+  [$PANEL_CFLAGS $CURSES_CFLAGS], [$PANEL_LIBS $CURSES_LIBS]
-+)
- PY_STDLIB_MOD([_decimal], [], [], [$LIBMPDEC_CFLAGS], [$LIBMPDEC_LDFLAGS])
- PY_STDLIB_MOD([_dbm],
-   [test -n "$with_dbmliborder"], [test "$have_dbm" != "no"],
-@@ -6997,7 +7155,9 @@ PY_STDLIB_MOD([_gdbm],
- PY_STDLIB_MOD([nis],
-   [], [test "$have_nis" = yes -a "$ac_cv_header_rpc_rpc_h" = yes],
-   [$LIBNSL_CFLAGS], [$LIBNSL_LIBS])
--dnl PY_STDLIB_MOD([readline], [], [], [], [])
-+ PY_STDLIB_MOD([readline],
-+  [], [test "$with_readline" != "no"],
-+  [$READLINE_CFLAGS], [$READLINE_LIBS])
- PY_STDLIB_MOD([_sqlite3],
-   [test "$have_sqlite3" = "yes"],
-   [test "$have_supported_sqlite3" = "yes"],
-diff --git a/pyconfig.h.in b/pyconfig.h.in
-index b05ddd41c2bba..aa9fc559fa251 100644
---- a/pyconfig.h.in
-+++ b/pyconfig.h.in
-@@ -290,6 +290,9 @@
- /* Defined when any dynamic module loading is enabled. */
- #undef HAVE_DYNAMIC_LOADING
- 
-+/* Define to 1 if you have the <editline/readline.h> header file. */
-+#undef HAVE_EDITLINE_READLINE_H
-+
- /* Define to 1 if you have the <endian.h> header file. */
- #undef HAVE_ENDIAN_H
- 
-@@ -646,9 +649,6 @@
- /* Define to 1 if you have the <libintl.h> header file. */
- #undef HAVE_LIBINTL_H
- 
--/* Define to build the readline module. */
--#undef HAVE_LIBREADLINE
--
- /* Define to 1 if you have the `resolv' library (-lresolv). */
- #undef HAVE_LIBRESOLV
- 
-@@ -784,6 +784,9 @@
- /* Define to 1 if you have the `nanosleep' function. */
- #undef HAVE_NANOSLEEP
- 
-+/* Define to 1 if you have the `ncursesw' library. */
-+#undef HAVE_NCURSESW
-+
- /* Define to 1 if you have the <ncurses.h> header file. */
- #undef HAVE_NCURSES_H
- 
-@@ -821,6 +824,9 @@
- /* Define to 1 if you have the `openpty' function. */
- #undef HAVE_OPENPTY
- 
-+/* Define to 1 if you have the <panel.h> header file. */
-+#undef HAVE_PANEL_H
-+
- /* Define to 1 if you have the `pathconf' function. */
- #undef HAVE_PATHCONF
- 
-@@ -905,6 +911,9 @@
- /* Define to 1 if you have the `pwritev2' function. */
- #undef HAVE_PWRITEV2
- 
-+/* Define to 1 if you have the <readline/readline.h> header file. */
-+#undef HAVE_READLINE_READLINE_H
-+
- /* Define to 1 if you have the `readlink' function. */
- #undef HAVE_READLINK
- 
-@@ -1662,7 +1671,7 @@
-    Dyld is necessary to support frameworks. */
- #undef WITH_DYLD
- 
--/* Define to build the readline module against Editline. */
-+/* Define to build the readline module against libedit. */
- #undef WITH_EDITLINE
- 
- /* Define if you want to compile in object freelists optimization */
-diff --git a/setup.py b/setup.py
-index cc11dedee1b2e..2edcb08b4fd7f 100644
---- a/setup.py
-+++ b/setup.py
-@@ -1027,146 +1027,9 @@
-         ))
- 
-     def detect_readline_curses(self):
--        # readline
--        readline_termcap_library = ""
--        curses_library = ""
--        # Cannot use os.popen here in py3k.
--        tmpfile = os.path.join(self.build_temp, 'readline_termcap_lib')
--        if not os.path.exists(self.build_temp):
--            os.makedirs(self.build_temp)
--        # Determine if readline is already linked against curses or tinfo.
--        if sysconfig.get_config_var('HAVE_LIBREADLINE'):
--            if sysconfig.get_config_var('WITH_EDITLINE'):
--                readline_lib = 'edit'
--            else:
--                readline_lib = 'readline'
--            do_readline = self.compiler.find_library_file(self.lib_dirs,
--                readline_lib)
--            if CROSS_COMPILING:
--                ret = run_command("%s -d %s | grep '(NEEDED)' > %s"
--                                % (sysconfig.get_config_var('READELF'),
--                                   do_readline, tmpfile))
--            elif find_executable('ldd'):
--                ret = run_command("ldd %s > %s" % (do_readline, tmpfile))
--            else:
--                ret = 1
--            if ret == 0:
--                with open(tmpfile) as fp:
--                    for ln in fp:
--                        if 'curses' in ln:
--                            readline_termcap_library = re.sub(
--                                r'.*lib(n?cursesw?)\.so.*', r'\1', ln
--                            ).rstrip()
--                            break
--                        # termcap interface split out from ncurses
--                        if 'tinfo' in ln:
--                            readline_termcap_library = 'tinfo'
--                            break
--            if os.path.exists(tmpfile):
--                os.unlink(tmpfile)
--        else:
--            do_readline = False
--        # Issue 7384: If readline is already linked against curses,
--        # use the same library for the readline and curses modules.
--        if 'curses' in readline_termcap_library:
--            curses_library = readline_termcap_library
--        elif self.compiler.find_library_file(self.lib_dirs, 'ncursesw'):
--            curses_library = 'ncursesw'
--        # Issue 36210: OSS provided ncurses does not link on AIX
--        # Use IBM supplied 'curses' for successful build of _curses
--        elif AIX and self.compiler.find_library_file(self.lib_dirs, 'curses'):
--            curses_library = 'curses'
--        elif self.compiler.find_library_file(self.lib_dirs, 'ncurses'):
--            curses_library = 'ncurses'
--        elif self.compiler.find_library_file(self.lib_dirs, 'curses'):
--            curses_library = 'curses'
--
--        if MACOS:
--            os_release = int(os.uname()[2].split('.')[0])
--            dep_target = sysconfig.get_config_var('MACOSX_DEPLOYMENT_TARGET')
--            if (dep_target and
--                    (tuple(int(n) for n in dep_target.split('.')[0:2])
--                        < (10, 5) ) ):
--                os_release = 8
--            if os_release < 9:
--                # MacOSX 10.4 has a broken readline. Don't try to build
--                # the readline module unless the user has installed a fixed
--                # readline package
--                if find_file('readline/rlconf.h', self.inc_dirs, []) is None:
--                    do_readline = False
--        if do_readline:
--            readline_libs = [readline_lib]
--            if readline_termcap_library:
--                pass # Issue 7384: Already linked against curses or tinfo.
--            elif curses_library:
--                readline_libs.append(curses_library)
--            elif self.compiler.find_library_file(self.lib_dirs, 'termcap'):
--                readline_libs.append('termcap')
--            self.add(Extension('readline', ['readline.c'],
--                               libraries=readline_libs))
--        else:
--            self.missing.append('readline')
--
--        # Curses support, requiring the System V version of curses, often
--        # provided by the ncurses library.
--        curses_defines = []
--        curses_includes = []
--        panel_library = 'panel'
--        if curses_library == 'ncursesw':
--            curses_defines.append(('HAVE_NCURSESW', '1'))
--            if not CROSS_COMPILING:
--                curses_includes.append('/usr/include/ncursesw')
--            # Bug 1464056: If _curses.so links with ncursesw,
--            # _curses_panel.so must link with panelw.
--            panel_library = 'panelw'
--            if MACOS:
--                # On OS X, there is no separate /usr/lib/libncursesw nor
--                # libpanelw.  If we are here, we found a locally-supplied
--                # version of libncursesw.  There should also be a
--                # libpanelw.  _XOPEN_SOURCE defines are usually excluded
--                # for OS X but we need _XOPEN_SOURCE_EXTENDED here for
--                # ncurses wide char support
--                curses_defines.append(('_XOPEN_SOURCE_EXTENDED', '1'))
--        elif MACOS and curses_library == 'ncurses':
--            # Building with the system-suppied combined libncurses/libpanel
--            curses_defines.append(('HAVE_NCURSESW', '1'))
--            curses_defines.append(('_XOPEN_SOURCE_EXTENDED', '1'))
--
--        curses_enabled = True
--        if curses_library.startswith('ncurses'):
--            curses_libs = [curses_library]
--            self.add(Extension('_curses', ['_cursesmodule.c'],
--                               include_dirs=curses_includes,
--                               define_macros=curses_defines,
--                               libraries=curses_libs))
--        elif curses_library == 'curses' and not MACOS:
--                # OSX has an old Berkeley curses, not good enough for
--                # the _curses module.
--            if (self.compiler.find_library_file(self.lib_dirs, 'terminfo')):
--                curses_libs = ['curses', 'terminfo']
--            elif (self.compiler.find_library_file(self.lib_dirs, 'termcap')):
--                curses_libs = ['curses', 'termcap']
--            else:
--                curses_libs = ['curses']
--
--            self.add(Extension('_curses', ['_cursesmodule.c'],
--                               define_macros=curses_defines,
--                               libraries=curses_libs))
--        else:
--            curses_enabled = False
--            self.missing.append('_curses')
--
--        # If the curses module is enabled, check for the panel module
--        # _curses_panel needs some form of ncurses
--        skip_curses_panel = True if AIX else False
--        if (curses_enabled and not skip_curses_panel and
--                self.compiler.find_library_file(self.lib_dirs, panel_library)):
--            self.add(Extension('_curses_panel', ['_curses_panel.c'],
--                           include_dirs=curses_includes,
--                           define_macros=curses_defines,
--                           libraries=[panel_library, *curses_libs]))
--        elif not skip_curses_panel:
--            self.missing.append('_curses_panel')
-+        self.addext(Extension('readline', ['readline.c']))
-+        self.addext(Extension('_curses', ['_cursesmodule.c']))
-+        self.addext(Extension('_curses_panel', ['_curses_panel.c']))
- 
-     def detect_crypt(self):
-         self.addext(Extension('_crypt', ['_cryptmodule.c']))
diff --git a/package/python3/python3.hash b/package/python3/python3.hash
index 39a16c1f71..debdedd413 100644
--- a/package/python3/python3.hash
+++ b/package/python3/python3.hash
@@ -1,5 +1,5 @@
-# From https://www.python.org/downloads/release/python-3116/
-md5  d0c5a1a31efe879723e51addf56dd206  Python-3.11.6.tar.xz
+# From https://www.python.org/downloads/release/python-3120/
+md5  f6f4616584b23254d165f4db90c247d6  Python-3.12.0.tar.xz
 # Locally computed
-sha256  0fab78fa7f133f4f38210c6260d90d7c0d5c7198446419ce057ec7ac2e6f5f38  Python-3.11.6.tar.xz
+sha256  795c34f44df45a0e9b9710c8c71c15c671871524cd412ca14def212e8ccb155d  Python-3.12.0.tar.xz
 sha256  3b2f81fe21d181c499c59a256c8e1968455d6689d269aa85373bfb6af41da3bf  LICENSE
diff --git a/package/python3/python3.mk b/package/python3/python3.mk
index 04105289d6..3df8f8c87a 100644
--- a/package/python3/python3.mk
+++ b/package/python3/python3.mk
@@ -4,8 +4,8 @@
 #
 ################################################################################
 
-PYTHON3_VERSION_MAJOR = 3.11
-PYTHON3_VERSION = $(PYTHON3_VERSION_MAJOR).6
+PYTHON3_VERSION_MAJOR = 3.12
+PYTHON3_VERSION = $(PYTHON3_VERSION_MAJOR).0
 PYTHON3_SOURCE = Python-$(PYTHON3_VERSION).tar.xz
 PYTHON3_SITE = https://python.org/ftp/python/$(PYTHON3_VERSION)
 PYTHON3_LICENSE = Python-2.0, others
-- 
2.41.0


_______________________________________________
buildroot mailing list
buildroot@buildroot.org
https://lists.buildroot.org/mailman/listinfo/buildroot


^ permalink raw reply related	[flat|nested] 54+ messages in thread

* Re: [Buildroot] [PATCH 19/30] package/libftdi: add patch to move from distutils to sysconfig
  2023-10-26  9:26 ` [Buildroot] [PATCH 19/30] package/libftdi: add patch to move from distutils to sysconfig Adam Duskett
@ 2023-10-26 11:49   ` Yegor Yefremov via buildroot
  2023-11-04 18:29   ` Arnout Vandecappelle via buildroot
  1 sibling, 0 replies; 54+ messages in thread
From: Yegor Yefremov via buildroot @ 2023-10-26 11:49 UTC (permalink / raw)
  To: Adam Duskett
  Cc: Andrey Smirnov, Julien Olivain, Asaf Kahlon, James Hilliard,
	Thomas Petazzoni, buildroot, Mauro Condarelli

Hi Adam,

On Thu, Oct 26, 2023 at 11:27 AM Adam Duskett
<adam.duskett@amarulasolutions.com> wrote:
>
> In preperation of python 3.12.0, distutils has been removed completely.
> This is a patch taken from the Fedora 39 RPM: libftdi-1.5-10.fc39.x86_64.
>
> Upstream commit: abd19b721f7e9b4d514ed319ece173ebc7b1ea72
>
> Signed-off-by: Adam Duskett <adam.duskett@amarulasolutions.com>
> ---
>  ...003-move-from-distutils-to-sysconfig.patch | 32 +++++++++++++++++++
>  1 file changed, 32 insertions(+)
>  create mode 100644 package/libftdi/0003-move-from-distutils-to-sysconfig.patch
>
> diff --git a/package/libftdi/0003-move-from-distutils-to-sysconfig.patch b/package/libftdi/0003-move-from-distutils-to-sysconfig.patch
> new file mode 100644
> index 0000000000..cb179c7cde
> --- /dev/null
> +++ b/package/libftdi/0003-move-from-distutils-to-sysconfig.patch
> @@ -0,0 +1,32 @@
> +From 872cd480990e6536d2cf48568627903ebf5acbeb Mon Sep 17 00:00:00 2001
> +From: Dan Hor <dan@danny.cz>
> +Date: Tue, 24 Oct 2023 10:36:09 +0200
> +Subject: [PATCH] move from distutils to sysconfig
> +
> +The distutils module was deprecated in Python 3.10, thus switch to the
> +sysconfig module instead.
> +
> +Upstream: committed abd19b721f7e9b4d514ed319ece173ebc7b1ea72
> +
> +Signed-off-by: Dan Hor <dan@danny.cz>
> +Signed-off-by: Adam Duskett <adam.duskett@amarulasolutions.com>
> +---
> + python/CMakeLists.txt | 2 +-
> + 1 file changed, 1 insertion(+), 1 deletion(-)
> +
> +diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt
> +index 5b6f420..19ce500 100644
> +--- a/python/CMakeLists.txt
> ++++ b/python/CMakeLists.txt
> +@@ -42,7 +42,7 @@ endif ()
> +
> + set_target_properties ( ${SWIG_MODULE_ftdi1_REAL_NAME} PROPERTIES NO_SONAME ON )
> +
> +-execute_process ( COMMAND ${PYTHON_EXECUTABLE} -c "from distutils import sysconfig; print( sysconfig.get_python_lib( plat_specific=True, prefix='${CMAKE_INSTALL_PREFIX}' ) )"
> ++execute_process ( COMMAND ${PYTHON_EXECUTABLE} -c "import sysconfig; print( sysconfig.get_path( 'platlib', vars={'platbase': '${CMAKE_INSTALL_PREFIX}'} ) )"
> +                   OUTPUT_VARIABLE _ABS_PYTHON_MODULE_PATH
> +                   OUTPUT_STRIP_TRAILING_WHITESPACE )

Do you mean libftdi1 package and not libftdi?

Regards,
Yegor
_______________________________________________
buildroot mailing list
buildroot@buildroot.org
https://lists.buildroot.org/mailman/listinfo/buildroot

^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: [Buildroot] [PATCH 28/30] package/python-pygame: drop package
  2023-10-26  9:26 ` [Buildroot] [PATCH 28/30] package/python-pygame: drop package Adam Duskett
@ 2023-10-26 14:13   ` Marcus Hoffmann via buildroot
  2023-11-09 21:55     ` Yann E. MORIN
  2023-11-10 14:11     ` Peter Korsgaard
  0 siblings, 2 replies; 54+ messages in thread
From: Marcus Hoffmann via buildroot @ 2023-10-26 14:13 UTC (permalink / raw)
  To: buildroot

Hi,

On 26.10.23 11:26, Adam Duskett wrote:
> The python-pygame package has not recieved any update since
> Sun May 1 22:15:17 2016 (commit: a9ec96e545102ae5ccd4280323d35360b0a5072d)
>
> As python 3.12.0 no longer supports distutils, drop the package as the package
> is clearly unmaintained.

I'd like to update the package to a recent version of pygame instead.
Updating the package should be fairly straightforward (famous last
words...) but I've struggled to find a platform (real hardware or
virtualized) where I can actually test this with actual graphical
output. :-/

Marcus

>
> Signed-off-by: Adam Duskett <adam.duskett@amarulasolutions.com>
> ---
>   Config.in.legacy                         |   7 ++
>   package/Config.in                        |   2 -
>   package/python-pygame/Config.in          |  50 ----------
>   package/python-pygame/python-pygame.hash |   3 -
>   package/python-pygame/python-pygame.mk   | 111 -----------------------
>   5 files changed, 7 insertions(+), 166 deletions(-)
>   delete mode 100644 package/python-pygame/Config.in
>   delete mode 100644 package/python-pygame/python-pygame.hash
>   delete mode 100644 package/python-pygame/python-pygame.mk
>
> diff --git a/Config.in.legacy b/Config.in.legacy
> index 8e406f2b13..f1d5f345c3 100644
> --- a/Config.in.legacy
> +++ b/Config.in.legacy
> @@ -153,6 +153,13 @@ config BR2_PACKAGE_PYTHON_PYXB
>         python-pyxb has been removed due to being abandoned and
>         distutils no longer being supported in python 3.12.0.
>
> +config BR2_PACKAGE_PYTHON_PYGAME
> +     bool "python-pygame removed"
> +     select BR2_LEGACY
> +     help
> +       python-pygame has been removed due to being abandoned and
> +       distutils no longer being supported in python 3.12.0.
> +
>   config BR2_KERNEL_HEADERS_6_4
>       bool "kernel headers version 6.4.x are no longer supported"
>       select BR2_LEGACY
> diff --git a/package/Config.in b/package/Config.in
> index 4e489c4706..d756094c4b 100644
> --- a/package/Config.in
> +++ b/package/Config.in
> @@ -1228,7 +1228,6 @@ menu "External python modules"
>       source "package/python-pydyf/Config.in"
>       source "package/python-pyelftools/Config.in"
>       source "package/python-pyftpdlib/Config.in"
> -     source "package/python-pygame/Config.in"
>       source "package/python-pygments/Config.in"
>       source "package/python-pyhamcrest/Config.in"
>       source "package/python-pyicu/Config.in"
> @@ -1267,7 +1266,6 @@ menu "External python modules"
>       source "package/python-pytz/Config.in"
>       source "package/python-pyudev/Config.in"
>       source "package/python-pyusb/Config.in"
> -     source "package/python-pyxb/Config.in"
>       source "package/python-pyyaml/Config.in"
>       source "package/python-pyzmq/Config.in"
>       source "package/python-qrcode/Config.in"
> diff --git a/package/python-pygame/Config.in b/package/python-pygame/Config.in
> deleted file mode 100644
> index 57eb020742..0000000000
> --- a/package/python-pygame/Config.in
> +++ /dev/null
> @@ -1,50 +0,0 @@
> -config BR2_PACKAGE_PYTHON_PYGAME
> -     bool "python-pygame"
> -     select BR2_PACKAGE_SDL
> -     help
> -       Pygame is a cross-platfrom library designed to make it easy
> -       to write multimedia software, such as games, in
> -       Python. Pygame requires the Python language and SDL
> -       multimedia library.
> -       It can also make use of several other popular libraries.
> -
> -       http://www.pygame.org/
> -
> -if BR2_PACKAGE_PYTHON_PYGAME
> -config BR2_PACKAGE_PYTHON_PYGAME_IMAGE
> -     bool "pygame.image"
> -     select BR2_PACKAGE_SDL_IMAGE
> -     select BR2_PACKAGE_SDL_IMAGE_PNG
> -     select BR2_PACKAGE_SDL_IMAGE_JPEG
> -     help
> -       pygame module for loading, saving and transfering images.
> -       Will autoselect sdl_image with png and jpeg support.
> -
> -config BR2_PACKAGE_PYTHON_PYGAME_EXAMPLES
> -     bool "pygame.examples"
> -     help
> -       Include examples.
> -       Selecting this option adds about 1.5 MB to the target file
> -       system.
> -
> -config BR2_PACKAGE_PYTHON_PYGAME_FONT
> -     bool "pygame.font"
> -     select BR2_PACKAGE_SDL_TTF
> -     help
> -       pygame module for loading and rendering fonts.
> -       Will autoselect sdl_ttf.
> -
> -config BR2_PACKAGE_PYTHON_PYGAME_MIXER
> -     bool "pygame.mixer"
> -     select BR2_PACKAGE_SDL_MIXER
> -     help
> -       pygame module for loading and playing sounds.
> -       Will autoselect sdl_mixer.
> -
> -config BR2_PACKAGE_PYTHON_PYGAME_SCRAP
> -     bool "pygame.scrap"
> -     depends on BR2_PACKAGE_SDL_X11
> -     help
> -       pygame module for clipboard support (X11 needed)
> -
> -endif
> diff --git a/package/python-pygame/python-pygame.hash b/package/python-pygame/python-pygame.hash
> deleted file mode 100644
> index c0496515e2..0000000000
> --- a/package/python-pygame/python-pygame.hash
> +++ /dev/null
> @@ -1,3 +0,0 @@
> -# Locally computed
> -sha256  f95a7dd68ea294d415e36e068d2f533c5a01c67773452d14a535c5c7455681fe  pygame-d61ea8eabd56.tar.gz
> -sha256  a190dc9c8043755d90f8b0a75fa66b9e42d4af4c980bf5ddc633f0124db3cee7  LGPL
> diff --git a/package/python-pygame/python-pygame.mk b/package/python-pygame/python-pygame.mk
> deleted file mode 100644
> index 600dd9e743..0000000000
> --- a/package/python-pygame/python-pygame.mk
> +++ /dev/null
> @@ -1,111 +0,0 @@
> -################################################################################
> -#
> -# python-pygame
> -#
> -################################################################################
> -
> -# stable 1.9.1 release requires V4L which has been wiped out of recent Linux
> -# kernels, so use latest mercurial revision until next stable release is out.
> -PYTHON_PYGAME_VERSION = d61ea8eabd56
> -PYTHON_PYGAME_SOURCE = pygame-$(PYTHON_PYGAME_VERSION).tar.gz
> -PYTHON_PYGAME_SITE = https://bitbucket.org/pygame/pygame
> -PYTHON_PYGAME_SITE_METHOD = hg
> -PYTHON_PYGAME_SETUP_TYPE = distutils
> -PYTHON_PYGAME_LICENSE = LGPL-2.1+
> -PYTHON_PYGAME_LICENSE_FILES = LGPL
> -
> -ifeq ($(BR2_PACKAGE_PYTHON_PYGAME_IMAGE),y)
> -PYTHON_PYGAME_OPT_DEPENDS += sdl_image
> -endif
> -
> -ifeq ($(BR2_PACKAGE_PYTHON_PYGAME_FONT),y)
> -PYTHON_PYGAME_OPT_DEPENDS += sdl_ttf
> -endif
> -
> -ifeq ($(BR2_PACKAGE_PYTHON_PYGAME_MIXER),y)
> -PYTHON_PYGAME_OPT_DEPENDS += sdl_mixer
> -endif
> -
> -PYTHON_PYGAME_DEPENDENCIES = sdl $(PYTHON_PYGAME_OPT_DEPENDS)
> -
> -ifneq ($(BR2_PACKAGE_PYTHON_PYGAME_IMAGE),y)
> -define PYTHON_PYGAME_UNCONFIGURE_IMAGE
> -     $(SED) 's/^imageext/#imageext/' $(@D)/Setup
> -endef
> -endif
> -
> -ifneq ($(BR2_PACKAGE_PYTHON_PYGAME_FONT),y)
> -define PYTHON_PYGAME_UNCONFIGURE_FONT
> -     $(SED) 's/^font/#font/' $(@D)/Setup
> -endef
> -endif
> -
> -ifneq ($(BR2_PACKAGE_PYTHON_PYGAME_MIXER),y)
> -define PYTHON_PYGAME_UNCONFIGURE_MIXER
> -     $(SED) 's/^mixer/#mixer/g' $(@D)/Setup
> -endef
> -endif
> -
> -# Both require numpy or numeric python module
> -define PYTHON_PYGAME_UNCONFIGURE_SNDARRAY
> -     $(SED) 's/^_numericsndarray/#_numericsndarray/' $(@D)/Setup
> -endef
> -
> -define PYTHON_PYGAME_UNCONFIGURE_SURFARRAY
> -     $(SED) 's/^_numericsurfarray/#_numericsurfarray/' $(@D)/Setup
> -endef
> -
> -# Requires smpeg
> -define PYTHON_PYGAME_UNCONFIGURE_MOVIE
> -     $(SED) 's/^movie/#movie/' $(@D)/Setup
> -endef
> -
> -ifneq ($(BR2_PACKAGE_PYTHON_PYGAME_SCRAP),y)
> -define PYTHON_PYGAME_UNCONFIGURE_SCRAP
> -     $(SED) 's/^scrap/#scrap/' $(@D)/Setup
> -endef
> -endif
> -
> -define PYTHON_PYGAME_UNCONFIGURE_FREETYPE
> -     $(SED) 's/^_freetype/#_freetype/' $(@D)/Setup
> -endef
> -
> -PYTHON_PYGAME_SDL_FLAGS = `$(STAGING_DIR)/usr/bin/sdl-config --cflags`
> -PYTHON_PYGAME_SDL_FLAGS += `$(STAGING_DIR)/usr/bin/sdl-config --libs`
> -
> -# Pygame needs a Setup file where options should be commented out if
> -# dependencies are not available
> -define PYTHON_PYGAME_CONFIGURE_CMDS
> -     cp -f $(@D)/Setup.in $(@D)/Setup
> -     $(SED) "s~^SDL = ~SDL = $(PYTHON_PYGAME_SDL_FLAGS) \n#~" $(@D)/Setup
> -     $(SED) 's/^pypm/#pypm/' $(@D)/Setup
> -     $(PYTHON_PYGAME_UNCONFIGURE_IMAGE)
> -     $(PYTHON_PYGAME_UNCONFIGURE_FONT)
> -     $(PYTHON_PYGAME_UNCONFIGURE_MIXER)
> -     $(PYTHON_PYGAME_UNCONFIGURE_SNDARRAY)
> -     $(PYTHON_PYGAME_UNCONFIGURE_SURFARRAY)
> -     $(PYTHON_PYGAME_UNCONFIGURE_MOVIE)
> -     $(PYTHON_PYGAME_UNCONFIGURE_SCRAP)
> -     $(PYTHON_PYGAME_UNCONFIGURE_FREETYPE)
> -endef
> -
> -define PYTHON_PYGAME_REMOVE_DOC
> -     rm -rf $(TARGET_DIR)/usr/lib/python*/site-packages/pygame/docs
> -endef
> -
> -PYTHON_PYGAME_POST_INSTALL_TARGET_HOOKS += PYTHON_PYGAME_REMOVE_DOC
> -
> -define PYTHON_PYGAME_REMOVE_TESTS
> -     rm -rf $(TARGET_DIR)/usr/lib/python*/site-packages/pygame/tests
> -endef
> -
> -PYTHON_PYGAME_POST_INSTALL_TARGET_HOOKS += PYTHON_PYGAME_REMOVE_TESTS
> -
> -ifneq ($(BR2_PACKAGE_PYTHON_PYGAME_EXAMPLES),y)
> -define PYTHON_PYGAME_REMOVE_EXAMPLES
> -     rm -rf $(TARGET_DIR)/usr/lib/python$(PYTHON3_VERSION_MAJOR)/site-packages/pygame/examples
> -endef
> -PYTHON_PYGAME_POST_INSTALL_TARGET_HOOKS += PYTHON_PYGAME_REMOVE_EXAMPLES
> -endif
> -
> -$(eval $(python-package))
________________________________

othermo GmbH | Sitz der Gesellschaft: Alzenau | Amtsgericht Aschaffenburg: HRB 14783 | USt-IdNr.: DE319977978 | Geschäftsführung: Dr. Dennis Metz.
_______________________________________________
buildroot mailing list
buildroot@buildroot.org
https://lists.buildroot.org/mailman/listinfo/buildroot

^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: [Buildroot] [PATCH 00/30] package/python3: bump version to 3.12.0
  2023-10-26  9:26 [Buildroot] [PATCH 00/30] package/python3: bump version to 3.12.0 Adam Duskett
                   ` (29 preceding siblings ...)
  2023-10-26  9:27 ` [Buildroot] [PATCH 30/30] package/python3: bump version to 3.12.0 Adam Duskett
@ 2023-11-04 17:41 ` Arnout Vandecappelle via buildroot
  30 siblings, 0 replies; 54+ messages in thread
From: Arnout Vandecappelle via buildroot @ 2023-11-04 17:41 UTC (permalink / raw)
  To: Adam Duskett, buildroot
  Cc: Andrey Smirnov, James Hilliard, Asaf Kahlon, Julien Olivain,
	Thomas Petazzoni, Mauro Condarelli

  Hi Adam,

On 26/10/2023 11:26, Adam Duskett wrote:
> Major changes:
> 
> Drop the following packages:
>    python-pygame
>    python-pyxb
>    python-crossbar
> 
> Remove distutils as an option
> 
> I tested every single python package test in buildroot currently
> available and every single one passes on Debian 11 and FC38/39.
> 
> Adam Duskett (16):
>    package/scons: bump version to 4.5.2
>    package/python-wsaccel: bump version to 0.6.6
>    package/python-systemd: bump version to 235
>    package/python-iptables: bump version to 1.0.1
>    package/libftdi: add patch to move from distutils to sysconfig
>    package/python-constantly: update versioneer to 0.29
>    package/python-magic-wormhole: update versioneer to 0.29
>    package/python-magic-wormhole-mailbox-server: update versioneer to
>      0.29
>    package/python-magic-wormhole-transit-relay: update versioneer to 0.29
>    package/python-spake2: update versioneer to 0.29
>    package/python-iptables: use sysconfig.get_path instead of
>      get_python_lib
>    package/python-pathtools: add 0001-replace-imp.patch

  I will try to apply these patches.

>    package/python-pyxb: Drop package
>    package/python-pygame: drop package
>    package/python-crossbar: drop package
>    package/python3: bump version to 3.12.0

  It was suggested to wait for 3.12.1, so let's do that.

> 
> Bernd Kuhls (14):
>    package/python3: use upstream build system to disable berkeleydb
>      module
>    package/python3: use upstream build system to disable uuid module
>    package/python3: use upstream build system to disable bzip2/zlib/xz
>      modules
>    package/python3: use upstream build system to disable curses/readline
>      modules
>    package/python3: use upstream build system to disable ssl module
>    package/python3: use upstream build system to disable ossaudiodev
>      module
>    package/python3: use upstream build system to disable unicodedata
>      module
>    package/python3: use upstream build system to disable nis module
>    package/python3: use upstream build system to disable decimal module
>    package/python3: use upstream build system to disable CJK codecs
>    package/python3: use upstream build system to disable pyexpat module
>    package/python3: use upstream build system to disable sqlite3 module

  I think all of these can be squashed into the patch that does the bump itself, 
no? That would make review quite a bit easier I think, and it also makes more 
sense IMHO (since we don't want to backport those patches to stable branches 
anyway, and if we revert the bump we also want to revert the "use upstream build 
system" bits).


  Regards,
  Arnout

>    package/python3: update patch and partly use upstream build system to
>      disable tk module
>    package/python3: Remove infrastructure to disable the build of certain
>      extensions
> 
>   .checkpackageignore                           |   14 -
>   Config.in.legacy                              |   30 +
>   package/Config.in                             |    3 -
>   ...003-move-from-distutils-to-sysconfig.patch |   32 +
>   package/pkg-python.mk                         |   44 +-
>   .../0001-Update-versioneer-to-0.29.patch      | 2619 +++++++++++++++++
>   .../0001-Avoid-intentional-syntax-error.patch |   29 -
>   ...s-min.txt-drop-indirect-dependencies.patch |   74 -
>   ...ice-wap-use-markupsafe-instead-of-we.patch |   53 -
>   package/python-crossbar/Config.in             |   71 -
>   package/python-crossbar/python-crossbar.hash  |    5 -
>   package/python-crossbar/python-crossbar.mk    |   14 -
>   ...g-get_path-instead-of-get_python_lib.patch |   40 +
>   package/python-iptables/python-iptables.hash  |    4 +-
>   package/python-iptables/python-iptables.mk    |    4 +-
>   .../0002-Update-versioneer-to-0.29.patch      | 2194 ++++++++++++++
>   .../0001-Update-versioneer-to-0.29.patch      | 2194 ++++++++++++++
>   .../0001-Update-versioneer-to-0.29.patch      | 2185 ++++++++++++++
>   .../python-pathtools/0001-replace-imp.patch   |   55 +
>   package/python-pygame/Config.in               |   50 -
>   package/python-pygame/python-pygame.hash      |    3 -
>   package/python-pygame/python-pygame.mk        |  111 -
>   package/python-pyxb/Config.in                 |    8 -
>   package/python-pyxb/python-pyxb.hash          |    4 -
>   package/python-pyxb/python-pyxb.mk            |   14 -
>   .../0001-Update-versioneer-to-0.29.patch      | 2194 ++++++++++++++
>   package/python-systemd/python-systemd.hash    |    4 +-
>   package/python-systemd/python-systemd.mk      |    6 +-
>   package/python-wsaccel/python-wsaccel.hash    |    4 +-
>   package/python-wsaccel/python-wsaccel.mk      |    4 +-
>   ...e-the-build-of-pyc-files-conditional.patch |   24 +-
>   ...taddrinfo-configure-test-when-cross-.patch |    2 +-
>   ...re-to-disable-the-build-of-certain-e.patch |  108 -
>   ...-header-paths-for-cross-compilation.patch} |   31 +-
>   ...tch => 0004-Serial-ioctl-workaround.patch} |    0
>   ...ook-in-usr-lib-termcap-for-libraries.patch |   31 -
>   ...g.sh.in-ensure-sed-invocations-only.patch} |    0
>   ...0006-Add-an-option-to-disable-pydoc.patch} |   50 +-
>   .../0006-Don-t-add-multiarch-paths.patch      |   37 -
>   .../0007-Abort-on-failed-module-build.patch   |   30 -
>   ...07-Add-an-option-to-disable-lib2to3.patch} |   74 +-
>   ... 0008-Add-an-option-to-disable-IDLE.patch} |   43 +-
>   ...e-shebang-of-Python-scripts-for-cros.patch |   35 -
>   ...hon-config.sh-don-t-reassign-prefix.patch} |    0
>   ...d-an-option-to-disable-the-tk-module.patch |   79 +
>   ...fix-building-on-older-distributions.patch} |   12 +-
>   ...p-CC-print-multiarch-output-for-mus.patch} |    2 +-
>   ...option-to-disable-the-sqlite3-module.patch |   62 -
>   ...ng-doesn-t-set-errno-when-encryptio.patch} |    0
>   ...d-an-option-to-disable-the-tk-module.patch |   77 -
>   ...-option-to-disable-the-curses-module.patch |   61 -
>   .../0016-Add-an-option-to-disable-expat.patch |   82 -
>   ...-Add-an-option-to-disable-CJK-codecs.patch |   30 -
>   .../0018-Add-an-option-to-disable-NIS.patch   |   33 -
>   ...Add-an-option-to-disable-unicodedata.patch |   30 -
>   ...021-Add-an-option-to-disable-decimal.patch |   54 -
>   ...on-to-disable-the-ossaudiodev-module.patch |   30 -
>   ...an-option-to-disable-openssl-support.patch |   30 -
>   ...ption-to-disable-the-readline-module.patch |   30 -
>   ...to-disable-zlib-bzip2-and-xz-modules.patch |   42 -
>   ...Add-an-option-to-disable-uuid-module.patch |   33 -
>   ...ion-to-disable-the-berkeleydb-module.patch |   30 -
>   package/python3/python3.hash                  |    6 +-
>   package/python3/python3.mk                    |   81 +-
>   package/scons/scons.hash                      |    4 +-
>   package/scons/scons.mk                        |    7 +-
>   .../tests/package/sample_python_crossbar.py   |    4 -
>   .../tests/package/test_python_crossbar.py     |   23 -
>   68 files changed, 11778 insertions(+), 1595 deletions(-)
>   create mode 100644 package/libftdi/0003-move-from-distutils-to-sysconfig.patch
>   create mode 100644 package/python-constantly/0001-Update-versioneer-to-0.29.patch
>   delete mode 100644 package/python-crossbar/0001-Avoid-intentional-syntax-error.patch
>   delete mode 100644 package/python-crossbar/0002-requirements-min.txt-drop-indirect-dependencies.patch
>   delete mode 100644 package/python-crossbar/0003-crossbar-webservice-wap-use-markupsafe-instead-of-we.patch
>   delete mode 100644 package/python-crossbar/Config.in
>   delete mode 100644 package/python-crossbar/python-crossbar.hash
>   delete mode 100644 package/python-crossbar/python-crossbar.mk
>   create mode 100644 package/python-iptables/0001-use-sysconfig-get_path-instead-of-get_python_lib.patch
>   create mode 100644 package/python-magic-wormhole-mailbox-server/0002-Update-versioneer-to-0.29.patch
>   create mode 100644 package/python-magic-wormhole-transit-relay/0001-Update-versioneer-to-0.29.patch
>   create mode 100644 package/python-magic-wormhole/0001-Update-versioneer-to-0.29.patch
>   create mode 100644 package/python-pathtools/0001-replace-imp.patch
>   delete mode 100644 package/python-pygame/Config.in
>   delete mode 100644 package/python-pygame/python-pygame.hash
>   delete mode 100644 package/python-pygame/python-pygame.mk
>   delete mode 100644 package/python-pyxb/Config.in
>   delete mode 100644 package/python-pyxb/python-pyxb.hash
>   delete mode 100644 package/python-pyxb/python-pyxb.mk
>   create mode 100644 package/python-spake2/0001-Update-versioneer-to-0.29.patch
>   delete mode 100644 package/python3/0003-Add-infrastructure-to-disable-the-build-of-certain-e.patch
>   rename package/python3/{0004-Adjust-library-header-paths-for-cross-compilation.patch => 0003-Adjust-library-header-paths-for-cross-compilation.patch} (64%)
>   rename package/python3/{0008-Serial-ioctl-workaround.patch => 0004-Serial-ioctl-workaround.patch} (100%)
>   delete mode 100644 package/python3/0005-Don-t-look-in-usr-lib-termcap-for-libraries.patch
>   rename package/python3/{0010-Misc-python-config.sh.in-ensure-sed-invocations-only.patch => 0005-Misc-python-config.sh.in-ensure-sed-invocations-only.patch} (100%)
>   rename package/python3/{0011-Add-an-option-to-disable-pydoc.patch => 0006-Add-an-option-to-disable-pydoc.patch} (54%)
>   delete mode 100644 package/python3/0006-Don-t-add-multiarch-paths.patch
>   delete mode 100644 package/python3/0007-Abort-on-failed-module-build.patch
>   rename package/python3/{0012-Add-an-option-to-disable-lib2to3.patch => 0007-Add-an-option-to-disable-lib2to3.patch} (59%)
>   rename package/python3/{0020-Add-an-option-to-disable-IDLE.patch => 0008-Add-an-option-to-disable-IDLE.patch} (57%)
>   delete mode 100644 package/python3/0009-Do-not-adjust-the-shebang-of-Python-scripts-for-cros.patch
>   rename package/python3/{0026-python-config.sh-don-t-reassign-prefix.patch => 0009-python-config.sh-don-t-reassign-prefix.patch} (100%)
>   create mode 100644 package/python3/0010-Add-an-option-to-disable-the-tk-module.patch
>   rename package/python3/{0028-fix-building-on-older-distributions.patch => 0011-fix-building-on-older-distributions.patch} (82%)
>   rename package/python3/{0029-configure.ac-fixup-CC-print-multiarch-output-for-mus.patch => 0012-configure.ac-fixup-CC-print-multiarch-output-for-mus.patch} (97%)
>   delete mode 100644 package/python3/0013-Add-option-to-disable-the-sqlite3-module.patch
>   rename package/python3/{0031-lib-crypt-uClibc-ng-doesn-t-set-errno-when-encryptio.patch => 0013-lib-crypt-uClibc-ng-doesn-t-set-errno-when-encryptio.patch} (100%)
>   delete mode 100644 package/python3/0014-Add-an-option-to-disable-the-tk-module.patch
>   delete mode 100644 package/python3/0015-Add-an-option-to-disable-the-curses-module.patch
>   delete mode 100644 package/python3/0016-Add-an-option-to-disable-expat.patch
>   delete mode 100644 package/python3/0017-Add-an-option-to-disable-CJK-codecs.patch
>   delete mode 100644 package/python3/0018-Add-an-option-to-disable-NIS.patch
>   delete mode 100644 package/python3/0019-Add-an-option-to-disable-unicodedata.patch
>   delete mode 100644 package/python3/0021-Add-an-option-to-disable-decimal.patch
>   delete mode 100644 package/python3/0022-Add-an-option-to-disable-the-ossaudiodev-module.patch
>   delete mode 100644 package/python3/0023-Add-an-option-to-disable-openssl-support.patch
>   delete mode 100644 package/python3/0024-Add-an-option-to-disable-the-readline-module.patch
>   delete mode 100644 package/python3/0025-Add-options-to-disable-zlib-bzip2-and-xz-modules.patch
>   delete mode 100644 package/python3/0027-Add-an-option-to-disable-uuid-module.patch
>   delete mode 100644 package/python3/0030-Add-an-option-to-disable-the-berkeleydb-module.patch
>   delete mode 100644 support/testing/tests/package/sample_python_crossbar.py
>   delete mode 100644 support/testing/tests/package/test_python_crossbar.py
> 
_______________________________________________
buildroot mailing list
buildroot@buildroot.org
https://lists.buildroot.org/mailman/listinfo/buildroot

^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: [Buildroot] [PATCH 13/30] package/python3: update patch and partly use upstream build system to disable tk module
  2023-10-26  9:26 ` [Buildroot] [PATCH 13/30] package/python3: update patch and partly use upstream build system to disable tk module Adam Duskett
@ 2023-11-04 17:47   ` Arnout Vandecappelle via buildroot
  0 siblings, 0 replies; 54+ messages in thread
From: Arnout Vandecappelle via buildroot @ 2023-11-04 17:47 UTC (permalink / raw)
  To: Adam Duskett, buildroot
  Cc: Andrey Smirnov, Bernd Kuhls, Julien Olivain, Asaf Kahlon,
	James Hilliard, Thomas Petazzoni, Mauro Condarelli

  Bernd, Adam,

On 26/10/2023 11:26, Adam Duskett wrote:
> From: Bernd Kuhls <bernd@kuhls.net>
> 
> Signed-off-by: Bernd Kuhls <bernd@kuhls.net>
> Signed-off-by: Adam Duskett <adam.duskett@amarulasolutions.com>
> ---
>   ...d-an-option-to-disable-the-tk-module.patch | 33 +++++++------------
>   package/python3/python3.mk                    |  2 ++
>   2 files changed, 14 insertions(+), 21 deletions(-)
> 
> diff --git a/package/python3/0014-Add-an-option-to-disable-the-tk-module.patch b/package/python3/0014-Add-an-option-to-disable-the-tk-module.patch
> index b89e1d27bc..1ed8959dba 100644
> --- a/package/python3/0014-Add-an-option-to-disable-the-tk-module.patch
> +++ b/package/python3/0014-Add-an-option-to-disable-the-tk-module.patch
> @@ -20,15 +20,15 @@ diff --git a/Makefile.pre.in b/Makefile.pre.in
>   index 9f4cdf14cf..4f83911200 100644
>   --- a/Makefile.pre.in
>   +++ b/Makefile.pre.in
> -@@ -1920,7 +1920,6 @@ LIBSUBDIRS=	asyncio \
> - 		multiprocessing multiprocessing/dummy \
> +@@ -1921,7 +1921,6 @@
>    		re \
>    		site-packages \
> + 		sqlite3 \
>   -		tkinter \
>    		tomllib \
>    		turtledemo \
>    		unittest \
> -@@ -2038,9 +2038,6 @@
> +@@ -2039,12 +2038,15 @@
>    		test/xmltestdata \
>    		test/xmltestdata/c14n-20 \
>    		test/ziptestdata \
> @@ -37,10 +37,6 @@ index 9f4cdf14cf..4f83911200 100644
>   -		tkinter/test/test_ttk \
>    		unittest/test \
>    		unittest/test/testmock
> - ifeq (@PYDOC@,yes)
> -@@ -2021,6 +2018,13 @@ ifeq (@SQLITE3@,yes)
> - LIBSUBDIRS += sqlite3
> - endif
>    
>   +ifeq (@TK@,yes)
>   +LIBSUBDIRS += tkinter
> @@ -48,30 +44,25 @@ index 9f4cdf14cf..4f83911200 100644
>   +	tkinter/test/test_ttk
>   +endif
>   +
> -+
> - TEST_MODULES=@TEST_MODULES@
> - libinstall:	all $(srcdir)/Modules/xxmodule.c
> - 	@for i in $(SCRIPTDIR) $(LIBDEST); \
> + ifeq (@PYDOC@,yes)
> + LIBSUBDIRS += pydoc_data
> + endif
>   diff --git a/configure.ac b/configure.ac
>   index 4cc0951ab9..f4ce506801 100644
>   --- a/configure.ac
>   +++ b/configure.ac
> -@@ -4180,6 +4180,15 @@ if test "$SQLITE3" = "no" ; then
> -    DISABLED_EXTENSIONS="${DISABLED_EXTENSIONS} _sqlite3"
> - fi
> +@@ -4202,6 +4202,11 @@
> + 	AS_HELP_STRING([--disable-pydoc], [disable pydoc]),
> + 	[ PYDOC="${enableval}" ], [ PYDOC=yes ])
>    
>   +AC_SUBST(TK)
>   +AC_ARG_ENABLE(tk,
>   +	AS_HELP_STRING([--disable-tk], [disable tk]),
>   +	[ TK="${enableval}" ], [ TK=yes ])
>   +
> -+if test "$TK" = "no"; then
> -+   DISABLED_EXTENSIONS="${DISABLED_EXTENSIONS} _tkinter"
> -+fi
> -+

  By doing the refresh in the same commit as the actual change to the patch, 
it's hard to see what really changed.

  After this change, it makes me wonder if the --disable-tk option is even 
relevant any more? AFAICS all it does is to avoid doing a test for tk, the 
result of which is anyway not going to be used.

  In that case, of course, the --disable-tk options also need to be removed from 
.mk.

> - AC_SUBST(PYDOC)
> -
> - AC_ARG_ENABLE(pydoc,
> + # Check for enable-ipv6
> + AH_TEMPLATE(ENABLE_IPV6, [Define if --enable-ipv6 is specified])
> + AC_MSG_CHECKING([if --enable-ipv6 is specified])
>   --
>   2.34.1
>   
> diff --git a/package/python3/python3.mk b/package/python3/python3.mk
> index 8b06c28fde..04105289d6 100644
> --- a/package/python3/python3.mk
> +++ b/package/python3/python3.mk
> @@ -43,6 +43,7 @@ HOST_PYTHON3_CONF_ENV += \
>   	py_cv_module__codecs_kr=n/a \
>   	py_cv_module__codecs_tw=n/a \
>   	py_cv_module__sqlite3=n/a \
> +	py_cv_module__tkinter=n/a \

  IIUC this applies to 3.11 already, so we could use this part of the patch 
without any other change?

  Regards,
  Arnout

>   	py_cv_module__uuid=n/a \
>   	ac_cv_prog_HAS_HG=/bin/false
>   
> @@ -176,6 +177,7 @@ PYTHON3_CONF_ENV += \
>   	ac_cv_file__dev_ptc=yes \
>   	ac_cv_working_tzset=yes \
>   	py_cv_module_nis=n/a \
> +	py_cv_module__tkinter=n/a \
>   	ac_cv_prog_HAS_HG=/bin/false
>   
>   # GCC is always compliant with IEEE754
_______________________________________________
buildroot mailing list
buildroot@buildroot.org
https://lists.buildroot.org/mailman/listinfo/buildroot

^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: [Buildroot] [PATCH 15/30] package/scons: bump version to 4.5.2
  2023-10-26  9:26 ` [Buildroot] [PATCH 15/30] package/scons: bump version to 4.5.2 Adam Duskett
@ 2023-11-04 17:54   ` Arnout Vandecappelle via buildroot
  2023-11-04 17:55     ` Arnout Vandecappelle via buildroot
  0 siblings, 1 reply; 54+ messages in thread
From: Arnout Vandecappelle via buildroot @ 2023-11-04 17:54 UTC (permalink / raw)
  To: Adam Duskett, buildroot
  Cc: Andrey Smirnov, James Hilliard, Asaf Kahlon, Julien Olivain,
	Thomas Petazzoni, Mauro Condarelli

  Hi Adam,

On 26/10/2023 11:26, Adam Duskett wrote:
> Scons is a setuptools package now.
> 
> Signed-off-by: Adam Duskett <adam.duskett@amarulasolutions.com>
> ---
>   package/scons/scons.hash | 4 ++--
>   package/scons/scons.mk   | 7 ++++---
>   2 files changed, 6 insertions(+), 5 deletions(-)
> 
> diff --git a/package/scons/scons.hash b/package/scons/scons.hash
> index a72fbaee5a..48b5077b2e 100644
> --- a/package/scons/scons.hash
> +++ b/package/scons/scons.hash
> @@ -1,3 +1,3 @@
>   # Locally computed:
> -sha256  7801f3f62f654528e272df780be10c0e9337e897650b62ddcee9f39fde13f8fb  scons-3.1.2.tar.gz
> -sha256  72ed889165fb28378cadac14552be4a959f1ebab6b148abb5dd2b49712c3c6f6  LICENSE.txt
> +sha256  ce26aac95d350a79a4192196b0beac3cb24f4ccabce0123eb28d3370f576f072  SCons-4.5.2.tar.gz
> +sha256  2f6ac9a1fc98394d18b80dba9bedb9d5626006d44db3fecf7cf3e21cff7e8b1c  LICENSE

  The license hash changed. Can you report in the commit message why it changed 
(i.e., did something change about the license?)

> diff --git a/package/scons/scons.mk b/package/scons/scons.mk
> index 6b75d3ddca..767cf64b26 100644
> --- a/package/scons/scons.mk
> +++ b/package/scons/scons.mk
> @@ -4,11 +4,12 @@
>   #
>   ################################################################################
>   
> -SCONS_VERSION = 3.1.2
> +SCONS_VERSION = 4.5.2

  That's a pretty big bump. Have you tested if the scons packages (benejson, 
gpsd, mongodb) still build?

  Regards,
  Arnout

> +SCONS_SOURCE = SCons-$(SCONS_VERSION).tar.gz
>   SCONS_SITE = http://downloads.sourceforge.net/project/scons/scons/$(SCONS_VERSION)
>   SCONS_LICENSE = MIT
> -SCONS_LICENSE_FILES = LICENSE.txt
> -SCONS_SETUP_TYPE = distutils
> +SCONS_LICENSE_FILES = LICENSE
> +SCONS_SETUP_TYPE = setuptools
>   
>   HOST_SCONS_INSTALL_OPTS = \
>   	--install-lib=$(HOST_DIR)/lib/scons-$(SCONS_VERSION)
_______________________________________________
buildroot mailing list
buildroot@buildroot.org
https://lists.buildroot.org/mailman/listinfo/buildroot

^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: [Buildroot] [PATCH 15/30] package/scons: bump version to 4.5.2
  2023-11-04 17:54   ` Arnout Vandecappelle via buildroot
@ 2023-11-04 17:55     ` Arnout Vandecappelle via buildroot
  0 siblings, 0 replies; 54+ messages in thread
From: Arnout Vandecappelle via buildroot @ 2023-11-04 17:55 UTC (permalink / raw)
  To: Adam Duskett, buildroot
  Cc: Andrey Smirnov, James Hilliard, Asaf Kahlon, Julien Olivain,
	Thomas Petazzoni, Mauro Condarelli


On 04/11/2023 18:54, Arnout Vandecappelle wrote:
>  Hi Adam,
>
> On 26/10/2023 11:26, Adam Duskett wrote:
>> Scons is a setuptools package now.
>>
>> Signed-off-by: Adam Duskett <adam.duskett@amarulasolutions.com>
>> ---
>>   package/scons/scons.hash | 4 ++--
>>   package/scons/scons.mk   | 7 ++++---
>>   2 files changed, 6 insertions(+), 5 deletions(-)
>>
>> diff --git a/package/scons/scons.hash b/package/scons/scons.hash
>> index a72fbaee5a..48b5077b2e 100644
>> --- a/package/scons/scons.hash
>> +++ b/package/scons/scons.hash
>> @@ -1,3 +1,3 @@
>>   # Locally computed:
>> -sha256 7801f3f62f654528e272df780be10c0e9337e897650b62ddcee9f39fde13f8fb 
>> scons-3.1.2.tar.gz
>> -sha256 72ed889165fb28378cadac14552be4a959f1ebab6b148abb5dd2b49712c3c6f6 
>> LICENSE.txt
>> +sha256 ce26aac95d350a79a4192196b0beac3cb24f4ccabce0123eb28d3370f576f072 
>> SCons-4.5.2.tar.gz
>> +sha256 2f6ac9a1fc98394d18b80dba9bedb9d5626006d44db3fecf7cf3e21cff7e8b1c LICENSE
>
>  The license hash changed. Can you report in the commit message why it changed 
> (i.e., did something change about the license?)

  Ideally with `diff -w` output.


  Regards,
  Arnout


>
>> diff --git a/package/scons/scons.mk b/package/scons/scons.mk
>> index 6b75d3ddca..767cf64b26 100644
>> --- a/package/scons/scons.mk
>> +++ b/package/scons/scons.mk
>> @@ -4,11 +4,12 @@
>>   #
>> ################################################################################
>>   -SCONS_VERSION = 3.1.2
>> +SCONS_VERSION = 4.5.2
>
>  That's a pretty big bump. Have you tested if the scons packages (benejson, 
> gpsd, mongodb) still build?
>
>  Regards,
>  Arnout
>
>> +SCONS_SOURCE = SCons-$(SCONS_VERSION).tar.gz
>>   SCONS_SITE = 
>> http://downloads.sourceforge.net/project/scons/scons/$(SCONS_VERSION)
>>   SCONS_LICENSE = MIT
>> -SCONS_LICENSE_FILES = LICENSE.txt
>> -SCONS_SETUP_TYPE = distutils
>> +SCONS_LICENSE_FILES = LICENSE
>> +SCONS_SETUP_TYPE = setuptools
>>     HOST_SCONS_INSTALL_OPTS = \
>>       --install-lib=$(HOST_DIR)/lib/scons-$(SCONS_VERSION)
_______________________________________________
buildroot mailing list
buildroot@buildroot.org
https://lists.buildroot.org/mailman/listinfo/buildroot

^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: [Buildroot] [PATCH 16/30] package/python-wsaccel: bump version to 0.6.6
  2023-10-26  9:26 ` [Buildroot] [PATCH 16/30] package/python-wsaccel: bump version to 0.6.6 Adam Duskett
@ 2023-11-04 17:56   ` Arnout Vandecappelle via buildroot
  0 siblings, 0 replies; 54+ messages in thread
From: Arnout Vandecappelle via buildroot @ 2023-11-04 17:56 UTC (permalink / raw)
  To: Adam Duskett, buildroot
  Cc: Andrey Smirnov, James Hilliard, Asaf Kahlon, Julien Olivain,
	Thomas Petazzoni, Mauro Condarelli



On 26/10/2023 11:26, Adam Duskett wrote:
> Signed-off-by: Adam Duskett <adam.duskett@amarulasolutions.com>

  Applied to master, thanks.

  Regards,
  Arnout

> ---
>   package/python-wsaccel/python-wsaccel.hash | 4 ++--
>   package/python-wsaccel/python-wsaccel.mk   | 4 ++--
>   2 files changed, 4 insertions(+), 4 deletions(-)
> 
> diff --git a/package/python-wsaccel/python-wsaccel.hash b/package/python-wsaccel/python-wsaccel.hash
> index b388c3cc7c..9991f8f454 100644
> --- a/package/python-wsaccel/python-wsaccel.hash
> +++ b/package/python-wsaccel/python-wsaccel.hash
> @@ -1,5 +1,5 @@
>   # md5, sha256 from https://pypi.org/pypi/wsaccel/json
> -md5  73bdc70035813ded14f9a4b81e82622a  wsaccel-0.6.4.tar.gz
> -sha256  cbf66a88bcaf6c6ad16d50ea29215891526b6e993c4bc7ed44b044ee6fe3ad3d  wsaccel-0.6.4.tar.gz
> +md5  b05eecfac9cb19326bfc3b538de495ac  wsaccel-0.6.6.tar.gz
> +sha256  18efec0a7182587ba97102b4cd8df7b4f665f45d7ca36f19783f5f081ea114ea  wsaccel-0.6.6.tar.gz
>   # Locally computed sha256 checksums
>   sha256  b6982974cb838b985b54b663d1780d280735086249c2e28015f25dd455df25da  LICENSE
> diff --git a/package/python-wsaccel/python-wsaccel.mk b/package/python-wsaccel/python-wsaccel.mk
> index ce41d431ff..1f63aa5c40 100644
> --- a/package/python-wsaccel/python-wsaccel.mk
> +++ b/package/python-wsaccel/python-wsaccel.mk
> @@ -4,9 +4,9 @@
>   #
>   ################################################################################
>   
> -PYTHON_WSACCEL_VERSION = 0.6.4
> +PYTHON_WSACCEL_VERSION = 0.6.6
>   PYTHON_WSACCEL_SOURCE = wsaccel-$(PYTHON_WSACCEL_VERSION).tar.gz
> -PYTHON_WSACCEL_SITE = https://files.pythonhosted.org/packages/77/0b/a44df15382a76b6768184630d483b8363d65b9f70d1aacf76153d496bbb9
> +PYTHON_WSACCEL_SITE = https://files.pythonhosted.org/packages/94/28/41c0e711b538f6031a247ab4ec5352267f12ed416e3a638b8d55fc33f609
>   PYTHON_WSACCEL_LICENSE = Apache-2.0
>   PYTHON_WSACCEL_LICENSE_FILES = LICENSE
>   PYTHON_WSACCEL_SETUP_TYPE = setuptools
_______________________________________________
buildroot mailing list
buildroot@buildroot.org
https://lists.buildroot.org/mailman/listinfo/buildroot

^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: [Buildroot] [PATCH 17/30] package/python-systemd: bump version to 235
  2023-10-26  9:26 ` [Buildroot] [PATCH 17/30] package/python-systemd: bump version to 235 Adam Duskett
@ 2023-11-04 17:59   ` Arnout Vandecappelle via buildroot
  2023-11-05 18:20     ` Adam Duskett
  0 siblings, 1 reply; 54+ messages in thread
From: Arnout Vandecappelle via buildroot @ 2023-11-04 17:59 UTC (permalink / raw)
  To: Adam Duskett, buildroot
  Cc: Andrey Smirnov, James Hilliard, Asaf Kahlon, Julien Olivain,
	Thomas Petazzoni, Mauro Condarelli



On 26/10/2023 11:26, Adam Duskett wrote:
> Signed-off-by: Adam Duskett <adam.duskett@amarulasolutions.com>
> ---
>   package/python-systemd/python-systemd.hash | 4 ++--
>   package/python-systemd/python-systemd.mk   | 6 +++---
>   2 files changed, 5 insertions(+), 5 deletions(-)
> 
> diff --git a/package/python-systemd/python-systemd.hash b/package/python-systemd/python-systemd.hash
> index 789b48590d..6cc6639218 100644
> --- a/package/python-systemd/python-systemd.hash
> +++ b/package/python-systemd/python-systemd.hash
> @@ -1,5 +1,5 @@
>   # md5 from https://pypi.python.org/pypi/systemd-python/
> -md5  5071ea5bcb976186e92a3f5e75df221d  systemd-python-234.tar.gz
> +md5  93f3ca09f35719ca6a4edd1d62d38dd4  systemd-python-235.tar.gz
>   # Locally computed
> -sha256  fd0e44bf70eadae45aadc292cb0a7eb5b0b6372cd1b391228047d33895db83e7  systemd-python-234.tar.gz
> +sha256  4e57f39797fd5d9e2d22b8806a252d7c0106c936039d1e71c8c6b8008e695c0a  systemd-python-235.tar.gz
>   sha256  dc626520dcd53a22f727af3ee42c770e56c97a64fe3adb063799d8ab032fe551  LICENSE.txt
> diff --git a/package/python-systemd/python-systemd.mk b/package/python-systemd/python-systemd.mk
> index 3ccfd57ce5..24ba84df76 100644
> --- a/package/python-systemd/python-systemd.mk
> +++ b/package/python-systemd/python-systemd.mk
> @@ -4,10 +4,10 @@
>   #
>   ################################################################################
>   
> -PYTHON_SYSTEMD_VERSION = 234 # Should be kept in sync with $(SYSTEMD_VERSION)
> +PYTHON_SYSTEMD_VERSION = 235 # Should be kept in sync with $(SYSTEMD_VERSION)

  SYSTEMD_VERSION is 254.5, not 235... What gives?

  (We should anyway probably put that comment in systemd.mk, not so much here...)

  Regards,
  Arnout

>   PYTHON_SYSTEMD_SOURCE = systemd-python-$(PYTHON_SYSTEMD_VERSION).tar.gz
> -PYTHON_SYSTEMD_SITE = https://pypi.python.org/packages/e8/a8/00ba0f605837a8f69523e6c3a4fb14675a6430c163f836540129c50b3aef
> -PYTHON_SYSTEMD_SETUP_TYPE = distutils
> +PYTHON_SYSTEMD_SITE = https://files.pythonhosted.org/packages/10/9e/ab4458e00367223bda2dd7ccf0849a72235ee3e29b36dce732685d9b7ad9
> +PYTHON_SYSTEMD_SETUP_TYPE = setuptools
>   PYTHON_SYSTEMD_LICENSE = LGPL-2.1
>   PYTHON_SYSTEMD_LICENSE_FILES = LICENSE.txt
>   PYTHON_SYSTEMD_DEPENDENCIES = systemd # To be able to link against libsystemd
_______________________________________________
buildroot mailing list
buildroot@buildroot.org
https://lists.buildroot.org/mailman/listinfo/buildroot

^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: [Buildroot] [PATCH 18/30] package/python-iptables: bump version to 1.0.1
  2023-10-26  9:26 ` [Buildroot] [PATCH 18/30] package/python-iptables: bump version to 1.0.1 Adam Duskett
@ 2023-11-04 18:14   ` Arnout Vandecappelle via buildroot
  0 siblings, 0 replies; 54+ messages in thread
From: Arnout Vandecappelle via buildroot @ 2023-11-04 18:14 UTC (permalink / raw)
  To: Adam Duskett, buildroot
  Cc: Andrey Smirnov, James Hilliard, Asaf Kahlon, Julien Olivain,
	Thomas Petazzoni, Mauro Condarelli



On 26/10/2023 11:26, Adam Duskett wrote:
> Signed-off-by: Adam Duskett <adam.duskett@amarulasolutions.com>

  Applied to master, thanks.

  Regards,
  Arnout

> ---
>   package/python-iptables/python-iptables.hash | 4 ++--
>   package/python-iptables/python-iptables.mk   | 4 ++--
>   2 files changed, 4 insertions(+), 4 deletions(-)
> 
> diff --git a/package/python-iptables/python-iptables.hash b/package/python-iptables/python-iptables.hash
> index ee5f765fba..1b7c9e9324 100644
> --- a/package/python-iptables/python-iptables.hash
> +++ b/package/python-iptables/python-iptables.hash
> @@ -1,6 +1,6 @@
>   # md5, sha256 from https://pypi.org/pypi/python-iptables/json
> -md5  3fb27da1107bdb62196850fa70e8b0d4  python-iptables-1.0.0.tar.gz
> -sha256  480470adb5f29bf84269b4e53dbad9623af91c79aa666cc0274dec199a555bc5  python-iptables-1.0.0.tar.gz
> +md5  bd6950c1ed9c6b48b7d552ff8e6766c5  python-iptables-1.0.1.tar.gz
> +sha256  1989f2b48598392c3574052a95f456985cb06fb4287b61bf8794e93ebc37eddb  python-iptables-1.0.1.tar.gz
>   
>   # Locally calculated
>   sha256  b827789c74144d9bb92595ed3bc568aef767a7e8d930fba61c2cdd9f6ec27599  NOTICE
> diff --git a/package/python-iptables/python-iptables.mk b/package/python-iptables/python-iptables.mk
> index 9cb4285ec3..d01545d8b2 100644
> --- a/package/python-iptables/python-iptables.mk
> +++ b/package/python-iptables/python-iptables.mk
> @@ -4,8 +4,8 @@
>   #
>   ################################################################################
>   
> -PYTHON_IPTABLES_VERSION = 1.0.0
> -PYTHON_IPTABLES_SITE = https://files.pythonhosted.org/packages/ca/6e/cba9c6f4b5a1963b7f5b015f5ed5e2eec7a94ac460570e3474177c4004d6
> +PYTHON_IPTABLES_VERSION = 1.0.1
> +PYTHON_IPTABLES_SITE = https://files.pythonhosted.org/packages/35/e4/33e639b9e153c2d798d73342a96715a4edca6f46431d763b275a34b3aeca
>   PYTHON_IPTABLES_SETUP_TYPE = setuptools
>   PYTHON_IPTABLES_LICENSE = Apache-2.0
>   PYTHON_IPTABLES_LICENSE_FILES = NOTICE
_______________________________________________
buildroot mailing list
buildroot@buildroot.org
https://lists.buildroot.org/mailman/listinfo/buildroot

^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: [Buildroot] [PATCH 19/30] package/libftdi: add patch to move from distutils to sysconfig
  2023-10-26  9:26 ` [Buildroot] [PATCH 19/30] package/libftdi: add patch to move from distutils to sysconfig Adam Duskett
  2023-10-26 11:49   ` Yegor Yefremov via buildroot
@ 2023-11-04 18:29   ` Arnout Vandecappelle via buildroot
  1 sibling, 0 replies; 54+ messages in thread
From: Arnout Vandecappelle via buildroot @ 2023-11-04 18:29 UTC (permalink / raw)
  To: Adam Duskett, buildroot
  Cc: Andrey Smirnov, James Hilliard, Asaf Kahlon, Julien Olivain,
	Thomas Petazzoni, Mauro Condarelli

  Hi Adam,

  Quite a few things went wrong with this patch... Other than it being the wrong 
package, as noted by Yegor, there are some more details:

On 26/10/2023 11:26, Adam Duskett wrote:
> In preperation of python 3.12.0, distutils has been removed completely.
> This is a patch taken from the Fedora 39 RPM: libftdi-1.5-10.fc39.x86_64.
> 
> Upstream commit: abd19b721f7e9b4d514ed319ece173ebc7b1ea72
> 
> Signed-off-by: Adam Duskett <adam.duskett@amarulasolutions.com>
> ---
>   ...003-move-from-distutils-to-sysconfig.patch | 32 +++++++++++++++++++
>   1 file changed, 32 insertions(+)
>   create mode 100644 package/libftdi/0003-move-from-distutils-to-sysconfig.patch
> 
> diff --git a/package/libftdi/0003-move-from-distutils-to-sysconfig.patch b/package/libftdi/0003-move-from-distutils-to-sysconfig.patch
> new file mode 100644
> index 0000000000..cb179c7cde
> --- /dev/null
> +++ b/package/libftdi/0003-move-from-distutils-to-sysconfig.patch
> @@ -0,0 +1,32 @@
> +From 872cd480990e6536d2cf48568627903ebf5acbeb Mon Sep 17 00:00:00 2001
> +From: Dan Hor <dan@danny.cz>

  The guy is called Dan Horák. I don't know how you managed to get this wrong - 
supposedly you took the patch from Fedore, but there it's recorded properly with 
=?UTF-8? stuff.

> +Date: Tue, 24 Oct 2023 10:36:09 +0200

  This is really weird... The patch is actually dated December 19, 2022. It got 
committed to libftdi1 on October 24, but at 17:33 +0200.

> +Subject: [PATCH] move from distutils to sysconfig
> +
> +The distutils module was deprecated in Python 3.10, thus switch to the
> +sysconfig module instead.

  The commit message was changed slightly when it was sent upstream.

> +
> +Upstream: committed abd19b721f7e9b4d514ed319ece173ebc7b1ea72

  It's easier for people if you put a URL.

  Also, I think this should be put after the author signoff. However...

> +
> +Signed-off-by: Dan Hor <dan@danny.cz>

  The author didn't actually add a signoff! Please don't add one if it doesn't 
exist.


  Anyway, I fixed all of that up and applied to master, thanks.

  Regards,
  Arnout

> +Signed-off-by: Adam Duskett <adam.duskett@amarulasolutions.com>
> +---
> + python/CMakeLists.txt | 2 +-
> + 1 file changed, 1 insertion(+), 1 deletion(-)
> +
> +diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt
> +index 5b6f420..19ce500 100644
> +--- a/python/CMakeLists.txt
> ++++ b/python/CMakeLists.txt
> +@@ -42,7 +42,7 @@ endif ()
> +
> + set_target_properties ( ${SWIG_MODULE_ftdi1_REAL_NAME} PROPERTIES NO_SONAME ON )
> +
> +-execute_process ( COMMAND ${PYTHON_EXECUTABLE} -c "from distutils import sysconfig; print( sysconfig.get_python_lib( plat_specific=True, prefix='${CMAKE_INSTALL_PREFIX}' ) )"
> ++execute_process ( COMMAND ${PYTHON_EXECUTABLE} -c "import sysconfig; print( sysconfig.get_path( 'platlib', vars={'platbase': '${CMAKE_INSTALL_PREFIX}'} ) )"
> +                   OUTPUT_VARIABLE _ABS_PYTHON_MODULE_PATH
> +                   OUTPUT_STRIP_TRAILING_WHITESPACE )
> +
> +--
> +2.41.0
> +
_______________________________________________
buildroot mailing list
buildroot@buildroot.org
https://lists.buildroot.org/mailman/listinfo/buildroot

^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: [Buildroot] [PATCH 20/30] package/python-constantly: update versioneer to 0.29
  2023-10-26  9:26 ` [Buildroot] [PATCH 20/30] package/python-constantly: update versioneer to 0.29 Adam Duskett
@ 2023-11-04 21:20   ` Arnout Vandecappelle via buildroot
  0 siblings, 0 replies; 54+ messages in thread
From: Arnout Vandecappelle via buildroot @ 2023-11-04 21:20 UTC (permalink / raw)
  To: Adam Duskett, buildroot
  Cc: Andrey Smirnov, James Hilliard, Asaf Kahlon, Julien Olivain,
	Thomas Petazzoni, Mauro Condarelli



On 26/10/2023 11:26, Adam Duskett wrote:
> Versioneer < 0.21 is incompatible with Python 3.12.0. Use the latest version
> which is 0.29 as of this commit.
> 
> Signed-off-by: Adam Duskett <adam.duskett@amarulasolutions.com>

  Upstream instead changed the infra to use versioneer as a dependency instead 
of bundling it [1]. Care to adapt accordingly?

  Regards,
  Arnout

[1] https://github.com/twisted/constantly/pull/35

> ---
>   .../0001-Update-versioneer-to-0.29.patch      | 2619 +++++++++++++++++
>   1 file changed, 2619 insertions(+)
_______________________________________________
buildroot mailing list
buildroot@buildroot.org
https://lists.buildroot.org/mailman/listinfo/buildroot

^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: [Buildroot] [PATCH 21/30] package/python-magic-wormhole: update versioneer to 0.29
  2023-10-26  9:26 ` [Buildroot] [PATCH 21/30] package/python-magic-wormhole: " Adam Duskett
@ 2023-11-04 21:30   ` Arnout Vandecappelle via buildroot
  0 siblings, 0 replies; 54+ messages in thread
From: Arnout Vandecappelle via buildroot @ 2023-11-04 21:30 UTC (permalink / raw)
  To: Adam Duskett, buildroot
  Cc: Andrey Smirnov, James Hilliard, Asaf Kahlon, Julien Olivain,
	Thomas Petazzoni, Mauro Condarelli



On 26/10/2023 11:26, Adam Duskett wrote:
> Versioneer < 0.21 is incompatible with Python 3.12.0. Use the latest version
> which is 0.29 as of this commit.
> 
> Signed-off-by: Adam Duskett <adam.duskett@amarulasolutions.com>

  Applied to master, thanks.

  Regards,
  Arnout

> ---
>   .../0001-Update-versioneer-to-0.29.patch      | 2185 +++++++++++++++++
>   1 file changed, 2185 insertions(+)
>   create mode 100644 package/python-magic-wormhole/0001-Update-versioneer-to-0.29.patch
> 
> diff --git a/package/python-magic-wormhole/0001-Update-versioneer-to-0.29.patch b/package/python-magic-wormhole/0001-Update-versioneer-to-0.29.patch
> new file mode 100644
> index 0000000000..54b5fe8b1d
> --- /dev/null
> +++ b/package/python-magic-wormhole/0001-Update-versioneer-to-0.29.patch
> @@ -0,0 +1,2185 @@
> +From b283ce50c6f32387d0f474c935ed6015585b986c Mon Sep 17 00:00:00 2001
> +From: Adam Duskett <adam.duskett@amarulasolutions.com>
> +Date: Tue, 24 Oct 2023 09:54:40 +0200
> +Subject: [PATCH] Update versioneer to 0.29
> +
> +Fixes builds against Python 3.12.0
> +
> +Upstream: https://github.com/magic-wormhole/magic-wormhole/pull/505
> +
> +Signed-off-by: Adam Duskett <adam.duskett@amarulasolutions.com>
> +---
> + versioneer.py | 1348 ++++++++++++++++++++++++++++++++++---------------
> + 1 file changed, 930 insertions(+), 418 deletions(-)
> +
> +diff --git a/versioneer.py b/versioneer.py
> +index ebe628e..de97d90 100644
> +--- a/versioneer.py
> ++++ b/versioneer.py
> +@@ -1,5 +1,4 @@
> +-
> +-# Version: 0.18
> ++# Version: 0.29
> +
> + """The Versioneer - like a rocketeer, but for versions.
> +
> +@@ -7,18 +6,14 @@ The Versioneer
> + ==============
> +
> + * like a rocketeer, but for versions!
> +-* https://github.com/warner/python-versioneer
> ++* https://github.com/python-versioneer/python-versioneer
> + * Brian Warner
> +-* License: Public Domain
> +-* Compatible With: python2.6, 2.7, 3.2, 3.3, 3.4, 3.5, 3.6, and pypy
> +-* [![Latest Version]
> +-(https://pypip.in/version/versioneer/badge.svg?style=flat)
> +-](https://pypi.python.org/pypi/versioneer/)
> +-* [![Build Status]
> +-(https://travis-ci.org/warner/python-versioneer.png?branch=master)
> +-](https://travis-ci.org/warner/python-versioneer)
> +-
> +-This is a tool for managing a recorded version number in distutils-based
> ++* License: Public Domain (Unlicense)
> ++* Compatible with: Python 3.7, 3.8, 3.9, 3.10, 3.11 and pypy3
> ++* [![Latest Version][pypi-image]][pypi-url]
> ++* [![Build Status][travis-image]][travis-url]
> ++
> ++This is a tool for managing a recorded version number in setuptools-based
> + python projects. The goal is to remove the tedious and error-prone "update
> + the embedded version string" step from your release process. Making a new
> + release should be as easy as recording a new tag in your version-control
> +@@ -27,9 +22,38 @@ system, and maybe making new tarballs.
> +
> + ## Quick Install
> +
> +-* `pip install versioneer` to somewhere to your $PATH
> +-* add a `[versioneer]` section to your setup.cfg (see below)
> +-* run `versioneer install` in your source tree, commit the results
> ++Versioneer provides two installation modes. The "classic" vendored mode installs
> ++a copy of versioneer into your repository. The experimental build-time dependency mode
> ++is intended to allow you to skip this step and simplify the process of upgrading.
> ++
> ++### Vendored mode
> ++
> ++* `pip install versioneer` to somewhere in your $PATH
> ++   * A [conda-forge recipe](https://github.com/conda-forge/versioneer-feedstock) is
> ++     available, so you can also use `conda install -c conda-forge versioneer`
> ++* add a `[tool.versioneer]` section to your `pyproject.toml` or a
> ++  `[versioneer]` section to your `setup.cfg` (see [Install](INSTALL.md))
> ++   * Note that you will need to add `tomli; python_version < "3.11"` to your
> ++     build-time dependencies if you use `pyproject.toml`
> ++* run `versioneer install --vendor` in your source tree, commit the results
> ++* verify version information with `python setup.py version`
> ++
> ++### Build-time dependency mode
> ++
> ++* `pip install versioneer` to somewhere in your $PATH
> ++   * A [conda-forge recipe](https://github.com/conda-forge/versioneer-feedstock) is
> ++     available, so you can also use `conda install -c conda-forge versioneer`
> ++* add a `[tool.versioneer]` section to your `pyproject.toml` or a
> ++  `[versioneer]` section to your `setup.cfg` (see [Install](INSTALL.md))
> ++* add `versioneer` (with `[toml]` extra, if configuring in `pyproject.toml`)
> ++  to the `requires` key of the `build-system` table in `pyproject.toml`:
> ++  ```toml
> ++  [build-system]
> ++  requires = ["setuptools", "versioneer[toml]"]
> ++  build-backend = "setuptools.build_meta"
> ++  ```
> ++* run `versioneer install --no-vendor` in your source tree, commit the results
> ++* verify version information with `python setup.py version`
> +
> + ## Version Identifiers
> +
> +@@ -61,7 +85,7 @@ version 1.3). Many VCS systems can report a description that captures this,
> + for example `git describe --tags --dirty --always` reports things like
> + "0.7-1-g574ab98-dirty" to indicate that the checkout is one revision past the
> + 0.7 tag, has a unique revision id of "574ab98", and is "dirty" (it has
> +-uncommitted changes.
> ++uncommitted changes).
> +
> + The version identifier is used for multiple purposes:
> +
> +@@ -166,7 +190,7 @@ which may help identify what went wrong).
> +
> + Some situations are known to cause problems for Versioneer. This details the
> + most significant ones. More can be found on Github
> +-[issues page](https://github.com/warner/python-versioneer/issues).
> ++[issues page](https://github.com/python-versioneer/python-versioneer/issues).
> +
> + ### Subprojects
> +
> +@@ -194,9 +218,9 @@ work too.
> + Pip-8.1.1 is known to have this problem, but hopefully it will get fixed in
> + some later version.
> +
> +-[Bug #38](https://github.com/warner/python-versioneer/issues/38) is tracking
> ++[Bug #38](https://github.com/python-versioneer/python-versioneer/issues/38) is tracking
> + this issue. The discussion in
> +-[PR #61](https://github.com/warner/python-versioneer/pull/61) describes the
> ++[PR #61](https://github.com/python-versioneer/python-versioneer/pull/61) describes the
> + issue from the Versioneer side in more detail.
> + [pip PR#3176](https://github.com/pypa/pip/pull/3176) and
> + [pip PR#3615](https://github.com/pypa/pip/pull/3615) contain work to improve
> +@@ -224,31 +248,20 @@ regenerated while a different version is checked out. Many setup.py commands
> + cause egg_info to be rebuilt (including `sdist`, `wheel`, and installing into
> + a different virtualenv), so this can be surprising.
> +
> +-[Bug #83](https://github.com/warner/python-versioneer/issues/83) describes
> ++[Bug #83](https://github.com/python-versioneer/python-versioneer/issues/83) describes
> + this one, but upgrading to a newer version of setuptools should probably
> + resolve it.
> +
> +-### Unicode version strings
> +-
> +-While Versioneer works (and is continually tested) with both Python 2 and
> +-Python 3, it is not entirely consistent with bytes-vs-unicode distinctions.
> +-Newer releases probably generate unicode version strings on py2. It's not
> +-clear that this is wrong, but it may be surprising for applications when then
> +-write these strings to a network connection or include them in bytes-oriented
> +-APIs like cryptographic checksums.
> +-
> +-[Bug #71](https://github.com/warner/python-versioneer/issues/71) investigates
> +-this question.
> +-
> +
> + ## Updating Versioneer
> +
> + To upgrade your project to a new release of Versioneer, do the following:
> +
> + * install the new Versioneer (`pip install -U versioneer` or equivalent)
> +-* edit `setup.cfg`, if necessary, to include any new configuration settings
> +-  indicated by the release notes. See [UPGRADING](./UPGRADING.md) for details.
> +-* re-run `versioneer install` in your source tree, to replace
> ++* edit `setup.cfg` and `pyproject.toml`, if necessary,
> ++  to include any new configuration settings indicated by the release notes.
> ++  See [UPGRADING](./UPGRADING.md) for details.
> ++* re-run `versioneer install --[no-]vendor` in your source tree, to replace
> +   `SRC/_version.py`
> + * commit any changed files
> +
> +@@ -265,35 +278,70 @@ installation by editing setup.py . Alternatively, it might go the other
> + direction and include code from all supported VCS systems, reducing the
> + number of intermediate scripts.
> +
> ++## Similar projects
> ++
> ++* [setuptools_scm](https://github.com/pypa/setuptools_scm/) - a non-vendored build-time
> ++  dependency
> ++* [minver](https://github.com/jbweston/miniver) - a lightweight reimplementation of
> ++  versioneer
> ++* [versioningit](https://github.com/jwodder/versioningit) - a PEP 518-based setuptools
> ++  plugin
> +
> + ## License
> +
> + To make Versioneer easier to embed, all its code is dedicated to the public
> + domain. The `_version.py` that it creates is also in the public domain.
> +-Specifically, both are released under the Creative Commons "Public Domain
> +-Dedication" license (CC0-1.0), as described in
> +-https://creativecommons.org/publicdomain/zero/1.0/ .
> ++Specifically, both are released under the "Unlicense", as described in
> ++https://unlicense.org/.
> ++
> ++[pypi-image]: https://img.shields.io/pypi/v/versioneer.svg
> ++[pypi-url]: https://pypi.python.org/pypi/versioneer/
> ++[travis-image]:
> ++https://img.shields.io/travis/com/python-versioneer/python-versioneer.svg
> ++[travis-url]: https://travis-ci.com/github/python-versioneer/python-versioneer
> +
> + """
> ++# pylint:disable=invalid-name,import-outside-toplevel,missing-function-docstring
> ++# pylint:disable=missing-class-docstring,too-many-branches,too-many-statements
> ++# pylint:disable=raise-missing-from,too-many-lines,too-many-locals,import-error
> ++# pylint:disable=too-few-public-methods,redefined-outer-name,consider-using-with
> ++# pylint:disable=attribute-defined-outside-init,too-many-arguments
> +
> +-from __future__ import print_function
> +-try:
> +-    import configparser
> +-except ImportError:
> +-    import ConfigParser as configparser
> ++import configparser
> + import errno
> + import json
> + import os
> + import re
> + import subprocess
> + import sys
> ++from pathlib import Path
> ++from typing import Any, Callable, cast, Dict, List, Optional, Tuple, Union
> ++from typing import NoReturn
> ++import functools
> ++
> ++have_tomllib = True
> ++if sys.version_info >= (3, 11):
> ++    import tomllib
> ++else:
> ++    try:
> ++        import tomli as tomllib
> ++    except ImportError:
> ++        have_tomllib = False
> +
> +
> + class VersioneerConfig:
> +     """Container for Versioneer configuration parameters."""
> +
> ++    VCS: str
> ++    style: str
> ++    tag_prefix: str
> ++    versionfile_source: str
> ++    versionfile_build: Optional[str]
> ++    parentdir_prefix: Optional[str]
> ++    verbose: Optional[bool]
> ++
> +
> +-def get_root():
> ++def get_root() -> str:
> +     """Get the project root directory.
> +
> +     We require that all commands are run from the project root, i.e. the
> +@@ -301,18 +349,30 @@ def get_root():
> +     """
> +     root = os.path.realpath(os.path.abspath(os.getcwd()))
> +     setup_py = os.path.join(root, "setup.py")
> ++    pyproject_toml = os.path.join(root, "pyproject.toml")
> +     versioneer_py = os.path.join(root, "versioneer.py")
> +-    if not (os.path.exists(setup_py) or os.path.exists(versioneer_py)):
> ++    if not (
> ++        os.path.exists(setup_py)
> ++        or os.path.exists(pyproject_toml)
> ++        or os.path.exists(versioneer_py)
> ++    ):
> +         # allow 'python path/to/setup.py COMMAND'
> +         root = os.path.dirname(os.path.realpath(os.path.abspath(sys.argv[0])))
> +         setup_py = os.path.join(root, "setup.py")
> ++        pyproject_toml = os.path.join(root, "pyproject.toml")
> +         versioneer_py = os.path.join(root, "versioneer.py")
> +-    if not (os.path.exists(setup_py) or os.path.exists(versioneer_py)):
> +-        err = ("Versioneer was unable to run the project root directory. "
> +-               "Versioneer requires setup.py to be executed from "
> +-               "its immediate directory (like 'python setup.py COMMAND'), "
> +-               "or in a way that lets it use sys.argv[0] to find the root "
> +-               "(like 'python path/to/setup.py COMMAND').")
> ++    if not (
> ++        os.path.exists(setup_py)
> ++        or os.path.exists(pyproject_toml)
> ++        or os.path.exists(versioneer_py)
> ++    ):
> ++        err = (
> ++            "Versioneer was unable to run the project root directory. "
> ++            "Versioneer requires setup.py to be executed from "
> ++            "its immediate directory (like 'python setup.py COMMAND'), "
> ++            "or in a way that lets it use sys.argv[0] to find the root "
> ++            "(like 'python path/to/setup.py COMMAND')."
> ++        )
> +         raise VersioneerBadRootError(err)
> +     try:
> +         # Certain runtime workflows (setup.py install/develop in a setuptools
> +@@ -321,43 +381,64 @@ def get_root():
> +         # module-import table will cache the first one. So we can't use
> +         # os.path.dirname(__file__), as that will find whichever
> +         # versioneer.py was first imported, even in later projects.
> +-        me = os.path.realpath(os.path.abspath(__file__))
> +-        me_dir = os.path.normcase(os.path.splitext(me)[0])
> ++        my_path = os.path.realpath(os.path.abspath(__file__))
> ++        me_dir = os.path.normcase(os.path.splitext(my_path)[0])
> +         vsr_dir = os.path.normcase(os.path.splitext(versioneer_py)[0])
> +-        if me_dir != vsr_dir:
> +-            print("Warning: build in %s is using versioneer.py from %s"
> +-                  % (os.path.dirname(me), versioneer_py))
> ++        if me_dir != vsr_dir and "VERSIONEER_PEP518" not in globals():
> ++            print(
> ++                "Warning: build in %s is using versioneer.py from %s"
> ++                % (os.path.dirname(my_path), versioneer_py)
> ++            )
> +     except NameError:
> +         pass
> +     return root
> +
> +
> +-def get_config_from_root(root):
> ++def get_config_from_root(root: str) -> VersioneerConfig:
> +     """Read the project setup.cfg file to determine Versioneer config."""
> +-    # This might raise EnvironmentError (if setup.cfg is missing), or
> ++    # This might raise OSError (if setup.cfg is missing), or
> +     # configparser.NoSectionError (if it lacks a [versioneer] section), or
> +     # configparser.NoOptionError (if it lacks "VCS="). See the docstring at
> +     # the top of versioneer.py for instructions on writing your setup.cfg .
> +-    setup_cfg = os.path.join(root, "setup.cfg")
> +-    parser = configparser.SafeConfigParser()
> +-    with open(setup_cfg, "r") as f:
> +-        parser.readfp(f)
> +-    VCS = parser.get("versioneer", "VCS")  # mandatory
> +-
> +-    def get(parser, name):
> +-        if parser.has_option("versioneer", name):
> +-            return parser.get("versioneer", name)
> +-        return None
> ++    root_pth = Path(root)
> ++    pyproject_toml = root_pth / "pyproject.toml"
> ++    setup_cfg = root_pth / "setup.cfg"
> ++    section: Union[Dict[str, Any], configparser.SectionProxy, None] = None
> ++    if pyproject_toml.exists() and have_tomllib:
> ++        try:
> ++            with open(pyproject_toml, "rb") as fobj:
> ++                pp = tomllib.load(fobj)
> ++            section = pp["tool"]["versioneer"]
> ++        except (tomllib.TOMLDecodeError, KeyError) as e:
> ++            print(f"Failed to load config from {pyproject_toml}: {e}")
> ++            print("Try to load it from setup.cfg")
> ++    if not section:
> ++        parser = configparser.ConfigParser()
> ++        with open(setup_cfg) as cfg_file:
> ++            parser.read_file(cfg_file)
> ++        parser.get("versioneer", "VCS")  # raise error if missing
> ++
> ++        section = parser["versioneer"]
> ++
> ++    # `cast`` really shouldn't be used, but its simplest for the
> ++    # common VersioneerConfig users at the moment. We verify against
> ++    # `None` values elsewhere where it matters
> ++
> +     cfg = VersioneerConfig()
> +-    cfg.VCS = VCS
> +-    cfg.style = get(parser, "style") or ""
> +-    cfg.versionfile_source = get(parser, "versionfile_source")
> +-    cfg.versionfile_build = get(parser, "versionfile_build")
> +-    cfg.tag_prefix = get(parser, "tag_prefix")
> +-    if cfg.tag_prefix in ("''", '""'):
> ++    cfg.VCS = section["VCS"]
> ++    cfg.style = section.get("style", "")
> ++    cfg.versionfile_source = cast(str, section.get("versionfile_source"))
> ++    cfg.versionfile_build = section.get("versionfile_build")
> ++    cfg.tag_prefix = cast(str, section.get("tag_prefix"))
> ++    if cfg.tag_prefix in ("''", '""', None):
> +         cfg.tag_prefix = ""
> +-    cfg.parentdir_prefix = get(parser, "parentdir_prefix")
> +-    cfg.verbose = get(parser, "verbose")
> ++    cfg.parentdir_prefix = section.get("parentdir_prefix")
> ++    if isinstance(section, configparser.SectionProxy):
> ++        # Make sure configparser translates to bool
> ++        cfg.verbose = section.getboolean("verbose")
> ++    else:
> ++        cfg.verbose = section.get("verbose")
> ++
> +     return cfg
> +
> +
> +@@ -366,37 +447,54 @@ class NotThisMethod(Exception):
> +
> +
> + # these dictionaries contain VCS-specific tools
> +-LONG_VERSION_PY = {}
> +-HANDLERS = {}
> ++LONG_VERSION_PY: Dict[str, str] = {}
> ++HANDLERS: Dict[str, Dict[str, Callable]] = {}
> +
> +
> +-def register_vcs_handler(vcs, method):  # decorator
> +-    """Decorator to mark a method as the handler for a particular VCS."""
> +-    def decorate(f):
> ++def register_vcs_handler(vcs: str, method: str) -> Callable:  # decorator
> ++    """Create decorator to mark a method as the handler of a VCS."""
> ++
> ++    def decorate(f: Callable) -> Callable:
> +         """Store f in HANDLERS[vcs][method]."""
> +-        if vcs not in HANDLERS:
> +-            HANDLERS[vcs] = {}
> +-        HANDLERS[vcs][method] = f
> ++        HANDLERS.setdefault(vcs, {})[method] = f
> +         return f
> ++
> +     return decorate
> +
> +
> +-def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False,
> +-                env=None):
> ++def run_command(
> ++    commands: List[str],
> ++    args: List[str],
> ++    cwd: Optional[str] = None,
> ++    verbose: bool = False,
> ++    hide_stderr: bool = False,
> ++    env: Optional[Dict[str, str]] = None,
> ++) -> Tuple[Optional[str], Optional[int]]:
> +     """Call the given command(s)."""
> +     assert isinstance(commands, list)
> +-    p = None
> +-    for c in commands:
> ++    process = None
> ++
> ++    popen_kwargs: Dict[str, Any] = {}
> ++    if sys.platform == "win32":
> ++        # This hides the console window if pythonw.exe is used
> ++        startupinfo = subprocess.STARTUPINFO()
> ++        startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
> ++        popen_kwargs["startupinfo"] = startupinfo
> ++
> ++    for command in commands:
> +         try:
> +-            dispcmd = str([c] + args)
> ++            dispcmd = str([command] + args)
> +             # remember shell=False, so use git.cmd on windows, not just git
> +-            p = subprocess.Popen([c] + args, cwd=cwd, env=env,
> +-                                 stdout=subprocess.PIPE,
> +-                                 stderr=(subprocess.PIPE if hide_stderr
> +-                                         else None))
> ++            process = subprocess.Popen(
> ++                [command] + args,
> ++                cwd=cwd,
> ++                env=env,
> ++                stdout=subprocess.PIPE,
> ++                stderr=(subprocess.PIPE if hide_stderr else None),
> ++                **popen_kwargs,
> ++            )
> +             break
> +-        except EnvironmentError:
> +-            e = sys.exc_info()[1]
> ++        except OSError as e:
> +             if e.errno == errno.ENOENT:
> +                 continue
> +             if verbose:
> +@@ -407,26 +505,27 @@ def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False,
> +         if verbose:
> +             print("unable to find command, tried %s" % (commands,))
> +         return None, None
> +-    stdout = p.communicate()[0].strip()
> +-    if sys.version_info[0] >= 3:
> +-        stdout = stdout.decode()
> +-    if p.returncode != 0:
> ++    stdout = process.communicate()[0].strip().decode()
> ++    if process.returncode != 0:
> +         if verbose:
> +             print("unable to run %s (error)" % dispcmd)
> +             print("stdout was %s" % stdout)
> +-        return None, p.returncode
> +-    return stdout, p.returncode
> ++        return None, process.returncode
> ++    return stdout, process.returncode
> +
> +
> +-LONG_VERSION_PY['git'] = '''
> ++LONG_VERSION_PY[
> ++    "git"
> ++] = r'''
> + # This file helps to compute a version number in source trees obtained from
> + # git-archive tarball (such as those provided by githubs download-from-tag
> + # feature). Distribution tarballs (built by setup.py sdist) and build
> + # directories (produced by setup.py build) will contain a much shorter file
> + # that just contains the computed version number.
> +
> +-# This file is released into the public domain. Generated by
> +-# versioneer-0.18 (https://github.com/warner/python-versioneer)
> ++# This file is released into the public domain.
> ++# Generated by versioneer-0.29
> ++# https://github.com/python-versioneer/python-versioneer
> +
> + """Git implementation of _version.py."""
> +
> +@@ -435,9 +534,11 @@ import os
> + import re
> + import subprocess
> + import sys
> ++from typing import Any, Callable, Dict, List, Optional, Tuple
> ++import functools
> +
> +
> +-def get_keywords():
> ++def get_keywords() -> Dict[str, str]:
> +     """Get the keywords needed to look up the version information."""
> +     # these strings will be replaced by git during git-archive.
> +     # setup.py/versioneer.py will grep for the variable names, so they must
> +@@ -453,8 +554,15 @@ def get_keywords():
> + class VersioneerConfig:
> +     """Container for Versioneer configuration parameters."""
> +
> ++    VCS: str
> ++    style: str
> ++    tag_prefix: str
> ++    parentdir_prefix: str
> ++    versionfile_source: str
> ++    verbose: bool
> ++
> +
> +-def get_config():
> ++def get_config() -> VersioneerConfig:
> +     """Create, populate and return the VersioneerConfig() object."""
> +     # these strings are filled in when 'setup.py versioneer' creates
> +     # _version.py
> +@@ -472,13 +580,13 @@ class NotThisMethod(Exception):
> +     """Exception raised if a method is not valid for the current scenario."""
> +
> +
> +-LONG_VERSION_PY = {}
> +-HANDLERS = {}
> ++LONG_VERSION_PY: Dict[str, str] = {}
> ++HANDLERS: Dict[str, Dict[str, Callable]] = {}
> +
> +
> +-def register_vcs_handler(vcs, method):  # decorator
> +-    """Decorator to mark a method as the handler for a particular VCS."""
> +-    def decorate(f):
> ++def register_vcs_handler(vcs: str, method: str) -> Callable:  # decorator
> ++    """Create decorator to mark a method as the handler of a VCS."""
> ++    def decorate(f: Callable) -> Callable:
> +         """Store f in HANDLERS[vcs][method]."""
> +         if vcs not in HANDLERS:
> +             HANDLERS[vcs] = {}
> +@@ -487,22 +595,35 @@ def register_vcs_handler(vcs, method):  # decorator
> +     return decorate
> +
> +
> +-def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False,
> +-                env=None):
> ++def run_command(
> ++    commands: List[str],
> ++    args: List[str],
> ++    cwd: Optional[str] = None,
> ++    verbose: bool = False,
> ++    hide_stderr: bool = False,
> ++    env: Optional[Dict[str, str]] = None,
> ++) -> Tuple[Optional[str], Optional[int]]:
> +     """Call the given command(s)."""
> +     assert isinstance(commands, list)
> +-    p = None
> +-    for c in commands:
> ++    process = None
> ++
> ++    popen_kwargs: Dict[str, Any] = {}
> ++    if sys.platform == "win32":
> ++        # This hides the console window if pythonw.exe is used
> ++        startupinfo = subprocess.STARTUPINFO()
> ++        startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
> ++        popen_kwargs["startupinfo"] = startupinfo
> ++
> ++    for command in commands:
> +         try:
> +-            dispcmd = str([c] + args)
> ++            dispcmd = str([command] + args)
> +             # remember shell=False, so use git.cmd on windows, not just git
> +-            p = subprocess.Popen([c] + args, cwd=cwd, env=env,
> +-                                 stdout=subprocess.PIPE,
> +-                                 stderr=(subprocess.PIPE if hide_stderr
> +-                                         else None))
> ++            process = subprocess.Popen([command] + args, cwd=cwd, env=env,
> ++                                       stdout=subprocess.PIPE,
> ++                                       stderr=(subprocess.PIPE if hide_stderr
> ++                                               else None), **popen_kwargs)
> +             break
> +-        except EnvironmentError:
> +-            e = sys.exc_info()[1]
> ++        except OSError as e:
> +             if e.errno == errno.ENOENT:
> +                 continue
> +             if verbose:
> +@@ -513,18 +634,20 @@ def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False,
> +         if verbose:
> +             print("unable to find command, tried %%s" %% (commands,))
> +         return None, None
> +-    stdout = p.communicate()[0].strip()
> +-    if sys.version_info[0] >= 3:
> +-        stdout = stdout.decode()
> +-    if p.returncode != 0:
> ++    stdout = process.communicate()[0].strip().decode()
> ++    if process.returncode != 0:
> +         if verbose:
> +             print("unable to run %%s (error)" %% dispcmd)
> +             print("stdout was %%s" %% stdout)
> +-        return None, p.returncode
> +-    return stdout, p.returncode
> ++        return None, process.returncode
> ++    return stdout, process.returncode
> +
> +
> +-def versions_from_parentdir(parentdir_prefix, root, verbose):
> ++def versions_from_parentdir(
> ++    parentdir_prefix: str,
> ++    root: str,
> ++    verbose: bool,
> ++) -> Dict[str, Any]:
> +     """Try to determine the version from the parent directory name.
> +
> +     Source tarballs conventionally unpack into a directory that includes both
> +@@ -533,15 +656,14 @@ def versions_from_parentdir(parentdir_prefix, root, verbose):
> +     """
> +     rootdirs = []
> +
> +-    for i in range(3):
> ++    for _ in range(3):
> +         dirname = os.path.basename(root)
> +         if dirname.startswith(parentdir_prefix):
> +             return {"version": dirname[len(parentdir_prefix):],
> +                     "full-revisionid": None,
> +                     "dirty": False, "error": None, "date": None}
> +-        else:
> +-            rootdirs.append(root)
> +-            root = os.path.dirname(root)  # up a level
> ++        rootdirs.append(root)
> ++        root = os.path.dirname(root)  # up a level
> +
> +     if verbose:
> +         print("Tried directories %%s but none started with prefix %%s" %%
> +@@ -550,41 +672,48 @@ def versions_from_parentdir(parentdir_prefix, root, verbose):
> +
> +
> + @register_vcs_handler("git", "get_keywords")
> +-def git_get_keywords(versionfile_abs):
> ++def git_get_keywords(versionfile_abs: str) -> Dict[str, str]:
> +     """Extract version information from the given file."""
> +     # the code embedded in _version.py can just fetch the value of these
> +     # keywords. When used from setup.py, we don't want to import _version.py,
> +     # so we do it with a regexp instead. This function is not used from
> +     # _version.py.
> +-    keywords = {}
> ++    keywords: Dict[str, str] = {}
> +     try:
> +-        f = open(versionfile_abs, "r")
> +-        for line in f.readlines():
> +-            if line.strip().startswith("git_refnames ="):
> +-                mo = re.search(r'=\s*"(.*)"', line)
> +-                if mo:
> +-                    keywords["refnames"] = mo.group(1)
> +-            if line.strip().startswith("git_full ="):
> +-                mo = re.search(r'=\s*"(.*)"', line)
> +-                if mo:
> +-                    keywords["full"] = mo.group(1)
> +-            if line.strip().startswith("git_date ="):
> +-                mo = re.search(r'=\s*"(.*)"', line)
> +-                if mo:
> +-                    keywords["date"] = mo.group(1)
> +-        f.close()
> +-    except EnvironmentError:
> ++        with open(versionfile_abs, "r") as fobj:
> ++            for line in fobj:
> ++                if line.strip().startswith("git_refnames ="):
> ++                    mo = re.search(r'=\s*"(.*)"', line)
> ++                    if mo:
> ++                        keywords["refnames"] = mo.group(1)
> ++                if line.strip().startswith("git_full ="):
> ++                    mo = re.search(r'=\s*"(.*)"', line)
> ++                    if mo:
> ++                        keywords["full"] = mo.group(1)
> ++                if line.strip().startswith("git_date ="):
> ++                    mo = re.search(r'=\s*"(.*)"', line)
> ++                    if mo:
> ++                        keywords["date"] = mo.group(1)
> ++    except OSError:
> +         pass
> +     return keywords
> +
> +
> + @register_vcs_handler("git", "keywords")
> +-def git_versions_from_keywords(keywords, tag_prefix, verbose):
> ++def git_versions_from_keywords(
> ++    keywords: Dict[str, str],
> ++    tag_prefix: str,
> ++    verbose: bool,
> ++) -> Dict[str, Any]:
> +     """Get version information from git keywords."""
> +-    if not keywords:
> +-        raise NotThisMethod("no keywords at all, weird")
> ++    if "refnames" not in keywords:
> ++        raise NotThisMethod("Short version file found")
> +     date = keywords.get("date")
> +     if date is not None:
> ++        # Use only the last line.  Previous lines may contain GPG signature
> ++        # information.
> ++        date = date.splitlines()[-1]
> ++
> +         # git-2.2.0 added "%%cI", which expands to an ISO-8601 -compliant
> +         # datestamp. However we prefer "%%ci" (which expands to an "ISO-8601
> +         # -like" string, which we must then edit to make compliant), because
> +@@ -597,11 +726,11 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose):
> +         if verbose:
> +             print("keywords are unexpanded, not using")
> +         raise NotThisMethod("unexpanded keywords, not a git-archive tarball")
> +-    refs = set([r.strip() for r in refnames.strip("()").split(",")])
> ++    refs = {r.strip() for r in refnames.strip("()").split(",")}
> +     # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of
> +     # just "foo-1.0". If we see a "tag: " prefix, prefer those.
> +     TAG = "tag: "
> +-    tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)])
> ++    tags = {r[len(TAG):] for r in refs if r.startswith(TAG)}
> +     if not tags:
> +         # Either we're using git < 1.8.3, or there really are no tags. We use
> +         # a heuristic: assume all version tags have a digit. The old git %%d
> +@@ -610,7 +739,7 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose):
> +         # between branches and tags. By ignoring refnames without digits, we
> +         # filter out many common branch names like "release" and
> +         # "stabilization", as well as "HEAD" and "master".
> +-        tags = set([r for r in refs if re.search(r'\d', r)])
> ++        tags = {r for r in refs if re.search(r'\d', r)}
> +         if verbose:
> +             print("discarding '%%s', no digits" %% ",".join(refs - tags))
> +     if verbose:
> +@@ -619,6 +748,11 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose):
> +         # sorting will prefer e.g. "2.0" over "2.0rc1"
> +         if ref.startswith(tag_prefix):
> +             r = ref[len(tag_prefix):]
> ++            # Filter out refs that exactly match prefix or that don't start
> ++            # with a number once the prefix is stripped (mostly a concern
> ++            # when prefix is '')
> ++            if not re.match(r'\d', r):
> ++                continue
> +             if verbose:
> +                 print("picking %%s" %% r)
> +             return {"version": r,
> +@@ -634,7 +768,12 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose):
> +
> +
> + @register_vcs_handler("git", "pieces_from_vcs")
> +-def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
> ++def git_pieces_from_vcs(
> ++    tag_prefix: str,
> ++    root: str,
> ++    verbose: bool,
> ++    runner: Callable = run_command
> ++) -> Dict[str, Any]:
> +     """Get version from 'git describe' in the root of the source tree.
> +
> +     This only gets called if the git-archive 'subst' keywords were *not*
> +@@ -645,8 +784,15 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
> +     if sys.platform == "win32":
> +         GITS = ["git.cmd", "git.exe"]
> +
> +-    out, rc = run_command(GITS, ["rev-parse", "--git-dir"], cwd=root,
> +-                          hide_stderr=True)
> ++    # GIT_DIR can interfere with correct operation of Versioneer.
> ++    # It may be intended to be passed to the Versioneer-versioned project,
> ++    # but that should not change where we get our version from.
> ++    env = os.environ.copy()
> ++    env.pop("GIT_DIR", None)
> ++    runner = functools.partial(runner, env=env)
> ++
> ++    _, rc = runner(GITS, ["rev-parse", "--git-dir"], cwd=root,
> ++                   hide_stderr=not verbose)
> +     if rc != 0:
> +         if verbose:
> +             print("Directory %%s not under git control" %% root)
> +@@ -654,24 +800,57 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
> +
> +     # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty]
> +     # if there isn't one, this yields HEX[-dirty] (no NUM)
> +-    describe_out, rc = run_command(GITS, ["describe", "--tags", "--dirty",
> +-                                          "--always", "--long",
> +-                                          "--match", "%%s*" %% tag_prefix],
> +-                                   cwd=root)
> ++    describe_out, rc = runner(GITS, [
> ++        "describe", "--tags", "--dirty", "--always", "--long",
> ++        "--match", f"{tag_prefix}[[:digit:]]*"
> ++    ], cwd=root)
> +     # --long was added in git-1.5.5
> +     if describe_out is None:
> +         raise NotThisMethod("'git describe' failed")
> +     describe_out = describe_out.strip()
> +-    full_out, rc = run_command(GITS, ["rev-parse", "HEAD"], cwd=root)
> ++    full_out, rc = runner(GITS, ["rev-parse", "HEAD"], cwd=root)
> +     if full_out is None:
> +         raise NotThisMethod("'git rev-parse' failed")
> +     full_out = full_out.strip()
> +
> +-    pieces = {}
> ++    pieces: Dict[str, Any] = {}
> +     pieces["long"] = full_out
> +     pieces["short"] = full_out[:7]  # maybe improved later
> +     pieces["error"] = None
> +
> ++    branch_name, rc = runner(GITS, ["rev-parse", "--abbrev-ref", "HEAD"],
> ++                             cwd=root)
> ++    # --abbrev-ref was added in git-1.6.3
> ++    if rc != 0 or branch_name is None:
> ++        raise NotThisMethod("'git rev-parse --abbrev-ref' returned error")
> ++    branch_name = branch_name.strip()
> ++
> ++    if branch_name == "HEAD":
> ++        # If we aren't exactly on a branch, pick a branch which represents
> ++        # the current commit. If all else fails, we are on a branchless
> ++        # commit.
> ++        branches, rc = runner(GITS, ["branch", "--contains"], cwd=root)
> ++        # --contains was added in git-1.5.4
> ++        if rc != 0 or branches is None:
> ++            raise NotThisMethod("'git branch --contains' returned error")
> ++        branches = branches.split("\n")
> ++
> ++        # Remove the first line if we're running detached
> ++        if "(" in branches[0]:
> ++            branches.pop(0)
> ++
> ++        # Strip off the leading "* " from the list of branches.
> ++        branches = [branch[2:] for branch in branches]
> ++        if "master" in branches:
> ++            branch_name = "master"
> ++        elif not branches:
> ++            branch_name = None
> ++        else:
> ++            # Pick the first branch that is returned. Good or bad.
> ++            branch_name = branches[0]
> ++
> ++    pieces["branch"] = branch_name
> ++
> +     # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty]
> +     # TAG might have hyphens.
> +     git_describe = describe_out
> +@@ -688,7 +867,7 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
> +         # TAG-NUM-gHEX
> +         mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe)
> +         if not mo:
> +-            # unparseable. Maybe git-describe is misbehaving?
> ++            # unparsable. Maybe git-describe is misbehaving?
> +             pieces["error"] = ("unable to parse git-describe output: '%%s'"
> +                                %% describe_out)
> +             return pieces
> +@@ -713,26 +892,27 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
> +     else:
> +         # HEX: no tags
> +         pieces["closest-tag"] = None
> +-        count_out, rc = run_command(GITS, ["rev-list", "HEAD", "--count"],
> +-                                    cwd=root)
> +-        pieces["distance"] = int(count_out)  # total number of commits
> ++        out, rc = runner(GITS, ["rev-list", "HEAD", "--left-right"], cwd=root)
> ++        pieces["distance"] = len(out.split())  # total number of commits
> +
> +     # commit date: see ISO-8601 comment in git_versions_from_keywords()
> +-    date = run_command(GITS, ["show", "-s", "--format=%%ci", "HEAD"],
> +-                       cwd=root)[0].strip()
> ++    date = runner(GITS, ["show", "-s", "--format=%%ci", "HEAD"], cwd=root)[0].strip()
> ++    # Use only the last line.  Previous lines may contain GPG signature
> ++    # information.
> ++    date = date.splitlines()[-1]
> +     pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1)
> +
> +     return pieces
> +
> +
> +-def plus_or_dot(pieces):
> ++def plus_or_dot(pieces: Dict[str, Any]) -> str:
> +     """Return a + if we don't already have one, else return a ."""
> +     if "+" in pieces.get("closest-tag", ""):
> +         return "."
> +     return "+"
> +
> +
> +-def render_pep440(pieces):
> ++def render_pep440(pieces: Dict[str, Any]) -> str:
> +     """Build up version string, with post-release "local version identifier".
> +
> +     Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you
> +@@ -757,23 +937,71 @@ def render_pep440(pieces):
> +     return rendered
> +
> +
> +-def render_pep440_pre(pieces):
> +-    """TAG[.post.devDISTANCE] -- No -dirty.
> ++def render_pep440_branch(pieces: Dict[str, Any]) -> str:
> ++    """TAG[[.dev0]+DISTANCE.gHEX[.dirty]] .
> ++
> ++    The ".dev0" means not master branch. Note that .dev0 sorts backwards
> ++    (a feature branch will appear "older" than the master branch).
> +
> +     Exceptions:
> +-    1: no tags. 0.post.devDISTANCE
> ++    1: no tags. 0[.dev0]+untagged.DISTANCE.gHEX[.dirty]
> +     """
> +     if pieces["closest-tag"]:
> +         rendered = pieces["closest-tag"]
> ++        if pieces["distance"] or pieces["dirty"]:
> ++            if pieces["branch"] != "master":
> ++                rendered += ".dev0"
> ++            rendered += plus_or_dot(pieces)
> ++            rendered += "%%d.g%%s" %% (pieces["distance"], pieces["short"])
> ++            if pieces["dirty"]:
> ++                rendered += ".dirty"
> ++    else:
> ++        # exception #1
> ++        rendered = "0"
> ++        if pieces["branch"] != "master":
> ++            rendered += ".dev0"
> ++        rendered += "+untagged.%%d.g%%s" %% (pieces["distance"],
> ++                                          pieces["short"])
> ++        if pieces["dirty"]:
> ++            rendered += ".dirty"
> ++    return rendered
> ++
> ++
> ++def pep440_split_post(ver: str) -> Tuple[str, Optional[int]]:
> ++    """Split pep440 version string at the post-release segment.
> ++
> ++    Returns the release segments before the post-release and the
> ++    post-release version number (or -1 if no post-release segment is present).
> ++    """
> ++    vc = str.split(ver, ".post")
> ++    return vc[0], int(vc[1] or 0) if len(vc) == 2 else None
> ++
> ++
> ++def render_pep440_pre(pieces: Dict[str, Any]) -> str:
> ++    """TAG[.postN.devDISTANCE] -- No -dirty.
> ++
> ++    Exceptions:
> ++    1: no tags. 0.post0.devDISTANCE
> ++    """
> ++    if pieces["closest-tag"]:
> +         if pieces["distance"]:
> +-            rendered += ".post.dev%%d" %% pieces["distance"]
> ++            # update the post release segment
> ++            tag_version, post_version = pep440_split_post(pieces["closest-tag"])
> ++            rendered = tag_version
> ++            if post_version is not None:
> ++                rendered += ".post%%d.dev%%d" %% (post_version + 1, pieces["distance"])
> ++            else:
> ++                rendered += ".post0.dev%%d" %% (pieces["distance"])
> ++        else:
> ++            # no commits, use the tag as the version
> ++            rendered = pieces["closest-tag"]
> +     else:
> +         # exception #1
> +-        rendered = "0.post.dev%%d" %% pieces["distance"]
> ++        rendered = "0.post0.dev%%d" %% pieces["distance"]
> +     return rendered
> +
> +
> +-def render_pep440_post(pieces):
> ++def render_pep440_post(pieces: Dict[str, Any]) -> str:
> +     """TAG[.postDISTANCE[.dev0]+gHEX] .
> +
> +     The ".dev0" means dirty. Note that .dev0 sorts backwards
> +@@ -800,12 +1028,41 @@ def render_pep440_post(pieces):
> +     return rendered
> +
> +
> +-def render_pep440_old(pieces):
> ++def render_pep440_post_branch(pieces: Dict[str, Any]) -> str:
> ++    """TAG[.postDISTANCE[.dev0]+gHEX[.dirty]] .
> ++
> ++    The ".dev0" means not master branch.
> ++
> ++    Exceptions:
> ++    1: no tags. 0.postDISTANCE[.dev0]+gHEX[.dirty]
> ++    """
> ++    if pieces["closest-tag"]:
> ++        rendered = pieces["closest-tag"]
> ++        if pieces["distance"] or pieces["dirty"]:
> ++            rendered += ".post%%d" %% pieces["distance"]
> ++            if pieces["branch"] != "master":
> ++                rendered += ".dev0"
> ++            rendered += plus_or_dot(pieces)
> ++            rendered += "g%%s" %% pieces["short"]
> ++            if pieces["dirty"]:
> ++                rendered += ".dirty"
> ++    else:
> ++        # exception #1
> ++        rendered = "0.post%%d" %% pieces["distance"]
> ++        if pieces["branch"] != "master":
> ++            rendered += ".dev0"
> ++        rendered += "+g%%s" %% pieces["short"]
> ++        if pieces["dirty"]:
> ++            rendered += ".dirty"
> ++    return rendered
> ++
> ++
> ++def render_pep440_old(pieces: Dict[str, Any]) -> str:
> +     """TAG[.postDISTANCE[.dev0]] .
> +
> +     The ".dev0" means dirty.
> +
> +-    Eexceptions:
> ++    Exceptions:
> +     1: no tags. 0.postDISTANCE[.dev0]
> +     """
> +     if pieces["closest-tag"]:
> +@@ -822,7 +1079,7 @@ def render_pep440_old(pieces):
> +     return rendered
> +
> +
> +-def render_git_describe(pieces):
> ++def render_git_describe(pieces: Dict[str, Any]) -> str:
> +     """TAG[-DISTANCE-gHEX][-dirty].
> +
> +     Like 'git describe --tags --dirty --always'.
> +@@ -842,7 +1099,7 @@ def render_git_describe(pieces):
> +     return rendered
> +
> +
> +-def render_git_describe_long(pieces):
> ++def render_git_describe_long(pieces: Dict[str, Any]) -> str:
> +     """TAG-DISTANCE-gHEX[-dirty].
> +
> +     Like 'git describe --tags --dirty --always -long'.
> +@@ -862,7 +1119,7 @@ def render_git_describe_long(pieces):
> +     return rendered
> +
> +
> +-def render(pieces, style):
> ++def render(pieces: Dict[str, Any], style: str) -> Dict[str, Any]:
> +     """Render the given version pieces into the requested style."""
> +     if pieces["error"]:
> +         return {"version": "unknown",
> +@@ -876,10 +1133,14 @@ def render(pieces, style):
> +
> +     if style == "pep440":
> +         rendered = render_pep440(pieces)
> ++    elif style == "pep440-branch":
> ++        rendered = render_pep440_branch(pieces)
> +     elif style == "pep440-pre":
> +         rendered = render_pep440_pre(pieces)
> +     elif style == "pep440-post":
> +         rendered = render_pep440_post(pieces)
> ++    elif style == "pep440-post-branch":
> ++        rendered = render_pep440_post_branch(pieces)
> +     elif style == "pep440-old":
> +         rendered = render_pep440_old(pieces)
> +     elif style == "git-describe":
> +@@ -894,7 +1155,7 @@ def render(pieces, style):
> +             "date": pieces.get("date")}
> +
> +
> +-def get_versions():
> ++def get_versions() -> Dict[str, Any]:
> +     """Get version information or return default if unable to do so."""
> +     # I am in _version.py, which lives at ROOT/VERSIONFILE_SOURCE. If we have
> +     # __file__, we can work backwards from there to the root. Some
> +@@ -915,7 +1176,7 @@ def get_versions():
> +         # versionfile_source is the relative path from the top of the source
> +         # tree (where the .git directory might live) to this file. Invert
> +         # this to find the root from __file__.
> +-        for i in cfg.versionfile_source.split('/'):
> ++        for _ in cfg.versionfile_source.split('/'):
> +             root = os.path.dirname(root)
> +     except NameError:
> +         return {"version": "0+unknown", "full-revisionid": None,
> +@@ -942,41 +1203,48 @@ def get_versions():
> +
> +
> + @register_vcs_handler("git", "get_keywords")
> +-def git_get_keywords(versionfile_abs):
> ++def git_get_keywords(versionfile_abs: str) -> Dict[str, str]:
> +     """Extract version information from the given file."""
> +     # the code embedded in _version.py can just fetch the value of these
> +     # keywords. When used from setup.py, we don't want to import _version.py,
> +     # so we do it with a regexp instead. This function is not used from
> +     # _version.py.
> +-    keywords = {}
> ++    keywords: Dict[str, str] = {}
> +     try:
> +-        f = open(versionfile_abs, "r")
> +-        for line in f.readlines():
> +-            if line.strip().startswith("git_refnames ="):
> +-                mo = re.search(r'=\s*"(.*)"', line)
> +-                if mo:
> +-                    keywords["refnames"] = mo.group(1)
> +-            if line.strip().startswith("git_full ="):
> +-                mo = re.search(r'=\s*"(.*)"', line)
> +-                if mo:
> +-                    keywords["full"] = mo.group(1)
> +-            if line.strip().startswith("git_date ="):
> +-                mo = re.search(r'=\s*"(.*)"', line)
> +-                if mo:
> +-                    keywords["date"] = mo.group(1)
> +-        f.close()
> +-    except EnvironmentError:
> ++        with open(versionfile_abs, "r") as fobj:
> ++            for line in fobj:
> ++                if line.strip().startswith("git_refnames ="):
> ++                    mo = re.search(r'=\s*"(.*)"', line)
> ++                    if mo:
> ++                        keywords["refnames"] = mo.group(1)
> ++                if line.strip().startswith("git_full ="):
> ++                    mo = re.search(r'=\s*"(.*)"', line)
> ++                    if mo:
> ++                        keywords["full"] = mo.group(1)
> ++                if line.strip().startswith("git_date ="):
> ++                    mo = re.search(r'=\s*"(.*)"', line)
> ++                    if mo:
> ++                        keywords["date"] = mo.group(1)
> ++    except OSError:
> +         pass
> +     return keywords
> +
> +
> + @register_vcs_handler("git", "keywords")
> +-def git_versions_from_keywords(keywords, tag_prefix, verbose):
> ++def git_versions_from_keywords(
> ++    keywords: Dict[str, str],
> ++    tag_prefix: str,
> ++    verbose: bool,
> ++) -> Dict[str, Any]:
> +     """Get version information from git keywords."""
> +-    if not keywords:
> +-        raise NotThisMethod("no keywords at all, weird")
> ++    if "refnames" not in keywords:
> ++        raise NotThisMethod("Short version file found")
> +     date = keywords.get("date")
> +     if date is not None:
> ++        # Use only the last line.  Previous lines may contain GPG signature
> ++        # information.
> ++        date = date.splitlines()[-1]
> ++
> +         # git-2.2.0 added "%cI", which expands to an ISO-8601 -compliant
> +         # datestamp. However we prefer "%ci" (which expands to an "ISO-8601
> +         # -like" string, which we must then edit to make compliant), because
> +@@ -989,11 +1257,11 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose):
> +         if verbose:
> +             print("keywords are unexpanded, not using")
> +         raise NotThisMethod("unexpanded keywords, not a git-archive tarball")
> +-    refs = set([r.strip() for r in refnames.strip("()").split(",")])
> ++    refs = {r.strip() for r in refnames.strip("()").split(",")}
> +     # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of
> +     # just "foo-1.0". If we see a "tag: " prefix, prefer those.
> +     TAG = "tag: "
> +-    tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)])
> ++    tags = {r[len(TAG) :] for r in refs if r.startswith(TAG)}
> +     if not tags:
> +         # Either we're using git < 1.8.3, or there really are no tags. We use
> +         # a heuristic: assume all version tags have a digit. The old git %d
> +@@ -1002,7 +1270,7 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose):
> +         # between branches and tags. By ignoring refnames without digits, we
> +         # filter out many common branch names like "release" and
> +         # "stabilization", as well as "HEAD" and "master".
> +-        tags = set([r for r in refs if re.search(r'\d', r)])
> ++        tags = {r for r in refs if re.search(r"\d", r)}
> +         if verbose:
> +             print("discarding '%s', no digits" % ",".join(refs - tags))
> +     if verbose:
> +@@ -1010,23 +1278,37 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose):
> +     for ref in sorted(tags):
> +         # sorting will prefer e.g. "2.0" over "2.0rc1"
> +         if ref.startswith(tag_prefix):
> +-            r = ref[len(tag_prefix):]
> ++            r = ref[len(tag_prefix) :]
> ++            # Filter out refs that exactly match prefix or that don't start
> ++            # with a number once the prefix is stripped (mostly a concern
> ++            # when prefix is '')
> ++            if not re.match(r"\d", r):
> ++                continue
> +             if verbose:
> +                 print("picking %s" % r)
> +-            return {"version": r,
> +-                    "full-revisionid": keywords["full"].strip(),
> +-                    "dirty": False, "error": None,
> +-                    "date": date}
> ++            return {
> ++                "version": r,
> ++                "full-revisionid": keywords["full"].strip(),
> ++                "dirty": False,
> ++                "error": None,
> ++                "date": date,
> ++            }
> +     # no suitable tags, so version is "0+unknown", but full hex is still there
> +     if verbose:
> +         print("no suitable tags, using unknown + full revision id")
> +-    return {"version": "0+unknown",
> +-            "full-revisionid": keywords["full"].strip(),
> +-            "dirty": False, "error": "no suitable tags", "date": None}
> ++    return {
> ++        "version": "0+unknown",
> ++        "full-revisionid": keywords["full"].strip(),
> ++        "dirty": False,
> ++        "error": "no suitable tags",
> ++        "date": None,
> ++    }
> +
> +
> + @register_vcs_handler("git", "pieces_from_vcs")
> +-def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
> ++def git_pieces_from_vcs(
> ++    tag_prefix: str, root: str, verbose: bool, runner: Callable = run_command
> ++) -> Dict[str, Any]:
> +     """Get version from 'git describe' in the root of the source tree.
> +
> +     This only gets called if the git-archive 'subst' keywords were *not*
> +@@ -1037,8 +1319,14 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
> +     if sys.platform == "win32":
> +         GITS = ["git.cmd", "git.exe"]
> +
> +-    out, rc = run_command(GITS, ["rev-parse", "--git-dir"], cwd=root,
> +-                          hide_stderr=True)
> ++    # GIT_DIR can interfere with correct operation of Versioneer.
> ++    # It may be intended to be passed to the Versioneer-versioned project,
> ++    # but that should not change where we get our version from.
> ++    env = os.environ.copy()
> ++    env.pop("GIT_DIR", None)
> ++    runner = functools.partial(runner, env=env)
> ++
> ++    _, rc = runner(GITS, ["rev-parse", "--git-dir"], cwd=root, hide_stderr=not verbose)
> +     if rc != 0:
> +         if verbose:
> +             print("Directory %s not under git control" % root)
> +@@ -1046,24 +1334,65 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
> +
> +     # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty]
> +     # if there isn't one, this yields HEX[-dirty] (no NUM)
> +-    describe_out, rc = run_command(GITS, ["describe", "--tags", "--dirty",
> +-                                          "--always", "--long",
> +-                                          "--match", "%s*" % tag_prefix],
> +-                                   cwd=root)
> ++    describe_out, rc = runner(
> ++        GITS,
> ++        [
> ++            "describe",
> ++            "--tags",
> ++            "--dirty",
> ++            "--always",
> ++            "--long",
> ++            "--match",
> ++            f"{tag_prefix}[[:digit:]]*",
> ++        ],
> ++        cwd=root,
> ++    )
> +     # --long was added in git-1.5.5
> +     if describe_out is None:
> +         raise NotThisMethod("'git describe' failed")
> +     describe_out = describe_out.strip()
> +-    full_out, rc = run_command(GITS, ["rev-parse", "HEAD"], cwd=root)
> ++    full_out, rc = runner(GITS, ["rev-parse", "HEAD"], cwd=root)
> +     if full_out is None:
> +         raise NotThisMethod("'git rev-parse' failed")
> +     full_out = full_out.strip()
> +
> +-    pieces = {}
> ++    pieces: Dict[str, Any] = {}
> +     pieces["long"] = full_out
> +     pieces["short"] = full_out[:7]  # maybe improved later
> +     pieces["error"] = None
> +
> ++    branch_name, rc = runner(GITS, ["rev-parse", "--abbrev-ref", "HEAD"], cwd=root)
> ++    # --abbrev-ref was added in git-1.6.3
> ++    if rc != 0 or branch_name is None:
> ++        raise NotThisMethod("'git rev-parse --abbrev-ref' returned error")
> ++    branch_name = branch_name.strip()
> ++
> ++    if branch_name == "HEAD":
> ++        # If we aren't exactly on a branch, pick a branch which represents
> ++        # the current commit. If all else fails, we are on a branchless
> ++        # commit.
> ++        branches, rc = runner(GITS, ["branch", "--contains"], cwd=root)
> ++        # --contains was added in git-1.5.4
> ++        if rc != 0 or branches is None:
> ++            raise NotThisMethod("'git branch --contains' returned error")
> ++        branches = branches.split("\n")
> ++
> ++        # Remove the first line if we're running detached
> ++        if "(" in branches[0]:
> ++            branches.pop(0)
> ++
> ++        # Strip off the leading "* " from the list of branches.
> ++        branches = [branch[2:] for branch in branches]
> ++        if "master" in branches:
> ++            branch_name = "master"
> ++        elif not branches:
> ++            branch_name = None
> ++        else:
> ++            # Pick the first branch that is returned. Good or bad.
> ++            branch_name = branches[0]
> ++
> ++    pieces["branch"] = branch_name
> ++
> +     # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty]
> +     # TAG might have hyphens.
> +     git_describe = describe_out
> +@@ -1072,17 +1401,16 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
> +     dirty = git_describe.endswith("-dirty")
> +     pieces["dirty"] = dirty
> +     if dirty:
> +-        git_describe = git_describe[:git_describe.rindex("-dirty")]
> ++        git_describe = git_describe[: git_describe.rindex("-dirty")]
> +
> +     # now we have TAG-NUM-gHEX or HEX
> +
> +     if "-" in git_describe:
> +         # TAG-NUM-gHEX
> +-        mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe)
> ++        mo = re.search(r"^(.+)-(\d+)-g([0-9a-f]+)$", git_describe)
> +         if not mo:
> +-            # unparseable. Maybe git-describe is misbehaving?
> +-            pieces["error"] = ("unable to parse git-describe output: '%s'"
> +-                               % describe_out)
> ++            # unparsable. Maybe git-describe is misbehaving?
> ++            pieces["error"] = "unable to parse git-describe output: '%s'" % describe_out
> +             return pieces
> +
> +         # tag
> +@@ -1091,10 +1419,12 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
> +             if verbose:
> +                 fmt = "tag '%s' doesn't start with prefix '%s'"
> +                 print(fmt % (full_tag, tag_prefix))
> +-            pieces["error"] = ("tag '%s' doesn't start with prefix '%s'"
> +-                               % (full_tag, tag_prefix))
> ++            pieces["error"] = "tag '%s' doesn't start with prefix '%s'" % (
> ++                full_tag,
> ++                tag_prefix,
> ++            )
> +             return pieces
> +-        pieces["closest-tag"] = full_tag[len(tag_prefix):]
> ++        pieces["closest-tag"] = full_tag[len(tag_prefix) :]
> +
> +         # distance: number of commits since tag
> +         pieces["distance"] = int(mo.group(2))
> +@@ -1105,19 +1435,20 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
> +     else:
> +         # HEX: no tags
> +         pieces["closest-tag"] = None
> +-        count_out, rc = run_command(GITS, ["rev-list", "HEAD", "--count"],
> +-                                    cwd=root)
> +-        pieces["distance"] = int(count_out)  # total number of commits
> ++        out, rc = runner(GITS, ["rev-list", "HEAD", "--left-right"], cwd=root)
> ++        pieces["distance"] = len(out.split())  # total number of commits
> +
> +     # commit date: see ISO-8601 comment in git_versions_from_keywords()
> +-    date = run_command(GITS, ["show", "-s", "--format=%ci", "HEAD"],
> +-                       cwd=root)[0].strip()
> ++    date = runner(GITS, ["show", "-s", "--format=%ci", "HEAD"], cwd=root)[0].strip()
> ++    # Use only the last line.  Previous lines may contain GPG signature
> ++    # information.
> ++    date = date.splitlines()[-1]
> +     pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1)
> +
> +     return pieces
> +
> +
> +-def do_vcs_install(manifest_in, versionfile_source, ipy):
> ++def do_vcs_install(versionfile_source: str, ipy: Optional[str]) -> None:
> +     """Git-specific installation logic for Versioneer.
> +
> +     For Git, this means creating/changing .gitattributes to mark _version.py
> +@@ -1126,36 +1457,40 @@ def do_vcs_install(manifest_in, versionfile_source, ipy):
> +     GITS = ["git"]
> +     if sys.platform == "win32":
> +         GITS = ["git.cmd", "git.exe"]
> +-    files = [manifest_in, versionfile_source]
> ++    files = [versionfile_source]
> +     if ipy:
> +         files.append(ipy)
> +-    try:
> +-        me = __file__
> +-        if me.endswith(".pyc") or me.endswith(".pyo"):
> +-            me = os.path.splitext(me)[0] + ".py"
> +-        versioneer_file = os.path.relpath(me)
> +-    except NameError:
> +-        versioneer_file = "versioneer.py"
> +-    files.append(versioneer_file)
> ++    if "VERSIONEER_PEP518" not in globals():
> ++        try:
> ++            my_path = __file__
> ++            if my_path.endswith((".pyc", ".pyo")):
> ++                my_path = os.path.splitext(my_path)[0] + ".py"
> ++            versioneer_file = os.path.relpath(my_path)
> ++        except NameError:
> ++            versioneer_file = "versioneer.py"
> ++        files.append(versioneer_file)
> +     present = False
> +     try:
> +-        f = open(".gitattributes", "r")
> +-        for line in f.readlines():
> +-            if line.strip().startswith(versionfile_source):
> +-                if "export-subst" in line.strip().split()[1:]:
> +-                    present = True
> +-        f.close()
> +-    except EnvironmentError:
> ++        with open(".gitattributes", "r") as fobj:
> ++            for line in fobj:
> ++                if line.strip().startswith(versionfile_source):
> ++                    if "export-subst" in line.strip().split()[1:]:
> ++                        present = True
> ++                        break
> ++    except OSError:
> +         pass
> +     if not present:
> +-        f = open(".gitattributes", "a+")
> +-        f.write("%s export-subst\n" % versionfile_source)
> +-        f.close()
> ++        with open(".gitattributes", "a+") as fobj:
> ++            fobj.write(f"{versionfile_source} export-subst\n")
> +         files.append(".gitattributes")
> +     run_command(GITS, ["add", "--"] + files)
> +
> +
> +-def versions_from_parentdir(parentdir_prefix, root, verbose):
> ++def versions_from_parentdir(
> ++    parentdir_prefix: str,
> ++    root: str,
> ++    verbose: bool,
> ++) -> Dict[str, Any]:
> +     """Try to determine the version from the parent directory name.
> +
> +     Source tarballs conventionally unpack into a directory that includes both
> +@@ -1164,24 +1499,29 @@ def versions_from_parentdir(parentdir_prefix, root, verbose):
> +     """
> +     rootdirs = []
> +
> +-    for i in range(3):
> ++    for _ in range(3):
> +         dirname = os.path.basename(root)
> +         if dirname.startswith(parentdir_prefix):
> +-            return {"version": dirname[len(parentdir_prefix):],
> +-                    "full-revisionid": None,
> +-                    "dirty": False, "error": None, "date": None}
> +-        else:
> +-            rootdirs.append(root)
> +-            root = os.path.dirname(root)  # up a level
> ++            return {
> ++                "version": dirname[len(parentdir_prefix) :],
> ++                "full-revisionid": None,
> ++                "dirty": False,
> ++                "error": None,
> ++                "date": None,
> ++            }
> ++        rootdirs.append(root)
> ++        root = os.path.dirname(root)  # up a level
> +
> +     if verbose:
> +-        print("Tried directories %s but none started with prefix %s" %
> +-              (str(rootdirs), parentdir_prefix))
> ++        print(
> ++            "Tried directories %s but none started with prefix %s"
> ++            % (str(rootdirs), parentdir_prefix)
> ++        )
> +     raise NotThisMethod("rootdir doesn't start with parentdir_prefix")
> +
> +
> + SHORT_VERSION_PY = """
> +-# This file was generated by 'versioneer.py' (0.18) from
> ++# This file was generated by 'versioneer.py' (0.29) from
> + # revision-control system data, or from the parent directory name of an
> + # unpacked source archive. Distribution tarballs contain a pre-generated copy
> + # of this file.
> +@@ -1198,42 +1538,42 @@ def get_versions():
> + """
> +
> +
> +-def versions_from_file(filename):
> ++def versions_from_file(filename: str) -> Dict[str, Any]:
> +     """Try to determine the version from _version.py if present."""
> +     try:
> +         with open(filename) as f:
> +             contents = f.read()
> +-    except EnvironmentError:
> ++    except OSError:
> +         raise NotThisMethod("unable to read _version.py")
> +-    mo = re.search(r"version_json = '''\n(.*)'''  # END VERSION_JSON",
> +-                   contents, re.M | re.S)
> ++    mo = re.search(
> ++        r"version_json = '''\n(.*)'''  # END VERSION_JSON", contents, re.M | re.S
> ++    )
> +     if not mo:
> +-        mo = re.search(r"version_json = '''\r\n(.*)'''  # END VERSION_JSON",
> +-                       contents, re.M | re.S)
> ++        mo = re.search(
> ++            r"version_json = '''\r\n(.*)'''  # END VERSION_JSON", contents, re.M | re.S
> ++        )
> +     if not mo:
> +         raise NotThisMethod("no version_json in _version.py")
> +     return json.loads(mo.group(1))
> +
> +
> +-def write_to_version_file(filename, versions):
> ++def write_to_version_file(filename: str, versions: Dict[str, Any]) -> None:
> +     """Write the given version number to the given _version.py file."""
> +-    os.unlink(filename)
> +-    contents = json.dumps(versions, sort_keys=True,
> +-                          indent=1, separators=(",", ": "))
> ++    contents = json.dumps(versions, sort_keys=True, indent=1, separators=(",", ": "))
> +     with open(filename, "w") as f:
> +         f.write(SHORT_VERSION_PY % contents)
> +
> +     print("set %s to '%s'" % (filename, versions["version"]))
> +
> +
> +-def plus_or_dot(pieces):
> ++def plus_or_dot(pieces: Dict[str, Any]) -> str:
> +     """Return a + if we don't already have one, else return a ."""
> +     if "+" in pieces.get("closest-tag", ""):
> +         return "."
> +     return "+"
> +
> +
> +-def render_pep440(pieces):
> ++def render_pep440(pieces: Dict[str, Any]) -> str:
> +     """Build up version string, with post-release "local version identifier".
> +
> +     Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you
> +@@ -1251,30 +1591,76 @@ def render_pep440(pieces):
> +                 rendered += ".dirty"
> +     else:
> +         # exception #1
> +-        rendered = "0+untagged.%d.g%s" % (pieces["distance"],
> +-                                          pieces["short"])
> ++        rendered = "0+untagged.%d.g%s" % (pieces["distance"], pieces["short"])
> +         if pieces["dirty"]:
> +             rendered += ".dirty"
> +     return rendered
> +
> +
> +-def render_pep440_pre(pieces):
> +-    """TAG[.post.devDISTANCE] -- No -dirty.
> ++def render_pep440_branch(pieces: Dict[str, Any]) -> str:
> ++    """TAG[[.dev0]+DISTANCE.gHEX[.dirty]] .
> ++
> ++    The ".dev0" means not master branch. Note that .dev0 sorts backwards
> ++    (a feature branch will appear "older" than the master branch).
> +
> +     Exceptions:
> +-    1: no tags. 0.post.devDISTANCE
> ++    1: no tags. 0[.dev0]+untagged.DISTANCE.gHEX[.dirty]
> +     """
> +     if pieces["closest-tag"]:
> +         rendered = pieces["closest-tag"]
> ++        if pieces["distance"] or pieces["dirty"]:
> ++            if pieces["branch"] != "master":
> ++                rendered += ".dev0"
> ++            rendered += plus_or_dot(pieces)
> ++            rendered += "%d.g%s" % (pieces["distance"], pieces["short"])
> ++            if pieces["dirty"]:
> ++                rendered += ".dirty"
> ++    else:
> ++        # exception #1
> ++        rendered = "0"
> ++        if pieces["branch"] != "master":
> ++            rendered += ".dev0"
> ++        rendered += "+untagged.%d.g%s" % (pieces["distance"], pieces["short"])
> ++        if pieces["dirty"]:
> ++            rendered += ".dirty"
> ++    return rendered
> ++
> ++
> ++def pep440_split_post(ver: str) -> Tuple[str, Optional[int]]:
> ++    """Split pep440 version string at the post-release segment.
> ++
> ++    Returns the release segments before the post-release and the
> ++    post-release version number (or -1 if no post-release segment is present).
> ++    """
> ++    vc = str.split(ver, ".post")
> ++    return vc[0], int(vc[1] or 0) if len(vc) == 2 else None
> ++
> ++
> ++def render_pep440_pre(pieces: Dict[str, Any]) -> str:
> ++    """TAG[.postN.devDISTANCE] -- No -dirty.
> ++
> ++    Exceptions:
> ++    1: no tags. 0.post0.devDISTANCE
> ++    """
> ++    if pieces["closest-tag"]:
> +         if pieces["distance"]:
> +-            rendered += ".post.dev%d" % pieces["distance"]
> ++            # update the post release segment
> ++            tag_version, post_version = pep440_split_post(pieces["closest-tag"])
> ++            rendered = tag_version
> ++            if post_version is not None:
> ++                rendered += ".post%d.dev%d" % (post_version + 1, pieces["distance"])
> ++            else:
> ++                rendered += ".post0.dev%d" % (pieces["distance"])
> ++        else:
> ++            # no commits, use the tag as the version
> ++            rendered = pieces["closest-tag"]
> +     else:
> +         # exception #1
> +-        rendered = "0.post.dev%d" % pieces["distance"]
> ++        rendered = "0.post0.dev%d" % pieces["distance"]
> +     return rendered
> +
> +
> +-def render_pep440_post(pieces):
> ++def render_pep440_post(pieces: Dict[str, Any]) -> str:
> +     """TAG[.postDISTANCE[.dev0]+gHEX] .
> +
> +     The ".dev0" means dirty. Note that .dev0 sorts backwards
> +@@ -1301,12 +1687,41 @@ def render_pep440_post(pieces):
> +     return rendered
> +
> +
> +-def render_pep440_old(pieces):
> ++def render_pep440_post_branch(pieces: Dict[str, Any]) -> str:
> ++    """TAG[.postDISTANCE[.dev0]+gHEX[.dirty]] .
> ++
> ++    The ".dev0" means not master branch.
> ++
> ++    Exceptions:
> ++    1: no tags. 0.postDISTANCE[.dev0]+gHEX[.dirty]
> ++    """
> ++    if pieces["closest-tag"]:
> ++        rendered = pieces["closest-tag"]
> ++        if pieces["distance"] or pieces["dirty"]:
> ++            rendered += ".post%d" % pieces["distance"]
> ++            if pieces["branch"] != "master":
> ++                rendered += ".dev0"
> ++            rendered += plus_or_dot(pieces)
> ++            rendered += "g%s" % pieces["short"]
> ++            if pieces["dirty"]:
> ++                rendered += ".dirty"
> ++    else:
> ++        # exception #1
> ++        rendered = "0.post%d" % pieces["distance"]
> ++        if pieces["branch"] != "master":
> ++            rendered += ".dev0"
> ++        rendered += "+g%s" % pieces["short"]
> ++        if pieces["dirty"]:
> ++            rendered += ".dirty"
> ++    return rendered
> ++
> ++
> ++def render_pep440_old(pieces: Dict[str, Any]) -> str:
> +     """TAG[.postDISTANCE[.dev0]] .
> +
> +     The ".dev0" means dirty.
> +
> +-    Eexceptions:
> ++    Exceptions:
> +     1: no tags. 0.postDISTANCE[.dev0]
> +     """
> +     if pieces["closest-tag"]:
> +@@ -1323,7 +1738,7 @@ def render_pep440_old(pieces):
> +     return rendered
> +
> +
> +-def render_git_describe(pieces):
> ++def render_git_describe(pieces: Dict[str, Any]) -> str:
> +     """TAG[-DISTANCE-gHEX][-dirty].
> +
> +     Like 'git describe --tags --dirty --always'.
> +@@ -1343,7 +1758,7 @@ def render_git_describe(pieces):
> +     return rendered
> +
> +
> +-def render_git_describe_long(pieces):
> ++def render_git_describe_long(pieces: Dict[str, Any]) -> str:
> +     """TAG-DISTANCE-gHEX[-dirty].
> +
> +     Like 'git describe --tags --dirty --always -long'.
> +@@ -1363,24 +1778,30 @@ def render_git_describe_long(pieces):
> +     return rendered
> +
> +
> +-def render(pieces, style):
> ++def render(pieces: Dict[str, Any], style: str) -> Dict[str, Any]:
> +     """Render the given version pieces into the requested style."""
> +     if pieces["error"]:
> +-        return {"version": "unknown",
> +-                "full-revisionid": pieces.get("long"),
> +-                "dirty": None,
> +-                "error": pieces["error"],
> +-                "date": None}
> ++        return {
> ++            "version": "unknown",
> ++            "full-revisionid": pieces.get("long"),
> ++            "dirty": None,
> ++            "error": pieces["error"],
> ++            "date": None,
> ++        }
> +
> +     if not style or style == "default":
> +         style = "pep440"  # the default
> +
> +     if style == "pep440":
> +         rendered = render_pep440(pieces)
> ++    elif style == "pep440-branch":
> ++        rendered = render_pep440_branch(pieces)
> +     elif style == "pep440-pre":
> +         rendered = render_pep440_pre(pieces)
> +     elif style == "pep440-post":
> +         rendered = render_pep440_post(pieces)
> ++    elif style == "pep440-post-branch":
> ++        rendered = render_pep440_post_branch(pieces)
> +     elif style == "pep440-old":
> +         rendered = render_pep440_old(pieces)
> +     elif style == "git-describe":
> +@@ -1390,16 +1811,20 @@ def render(pieces, style):
> +     else:
> +         raise ValueError("unknown style '%s'" % style)
> +
> +-    return {"version": rendered, "full-revisionid": pieces["long"],
> +-            "dirty": pieces["dirty"], "error": None,
> +-            "date": pieces.get("date")}
> ++    return {
> ++        "version": rendered,
> ++        "full-revisionid": pieces["long"],
> ++        "dirty": pieces["dirty"],
> ++        "error": None,
> ++        "date": pieces.get("date"),
> ++    }
> +
> +
> + class VersioneerBadRootError(Exception):
> +     """The project root directory is unknown or missing key files."""
> +
> +
> +-def get_versions(verbose=False):
> ++def get_versions(verbose: bool = False) -> Dict[str, Any]:
> +     """Get the project version from whatever source is available.
> +
> +     Returns dict with two keys: 'version' and 'full'.
> +@@ -1414,9 +1839,10 @@ def get_versions(verbose=False):
> +     assert cfg.VCS is not None, "please set [versioneer]VCS= in setup.cfg"
> +     handlers = HANDLERS.get(cfg.VCS)
> +     assert handlers, "unrecognized VCS '%s'" % cfg.VCS
> +-    verbose = verbose or cfg.verbose
> +-    assert cfg.versionfile_source is not None, \
> +-        "please set versioneer.versionfile_source"
> ++    verbose = verbose or bool(cfg.verbose)  # `bool()` used to avoid `None`
> ++    assert (
> ++        cfg.versionfile_source is not None
> ++    ), "please set versioneer.versionfile_source"
> +     assert cfg.tag_prefix is not None, "please set versioneer.tag_prefix"
> +
> +     versionfile_abs = os.path.join(root, cfg.versionfile_source)
> +@@ -1470,18 +1896,26 @@ def get_versions(verbose=False):
> +     if verbose:
> +         print("unable to compute version")
> +
> +-    return {"version": "0+unknown", "full-revisionid": None,
> +-            "dirty": None, "error": "unable to compute version",
> +-            "date": None}
> ++    return {
> ++        "version": "0+unknown",
> ++        "full-revisionid": None,
> ++        "dirty": None,
> ++        "error": "unable to compute version",
> ++        "date": None,
> ++    }
> +
> +
> +-def get_version():
> ++def get_version() -> str:
> +     """Get the short version string for this project."""
> +     return get_versions()["version"]
> +
> +
> +-def get_cmdclass():
> +-    """Get the custom setuptools/distutils subclasses used by Versioneer."""
> ++def get_cmdclass(cmdclass: Optional[Dict[str, Any]] = None):
> ++    """Get the custom setuptools subclasses used by Versioneer.
> ++
> ++    If the package uses a different cmdclass (e.g. one from numpy), it
> ++    should be provide as an argument.
> ++    """
> +     if "versioneer" in sys.modules:
> +         del sys.modules["versioneer"]
> +         # this fixes the "python setup.py develop" case (also 'install' and
> +@@ -1495,25 +1929,25 @@ def get_cmdclass():
> +         # parent is protected against the child's "import versioneer". By
> +         # removing ourselves from sys.modules here, before the child build
> +         # happens, we protect the child from the parent's versioneer too.
> +-        # Also see https://github.com/warner/python-versioneer/issues/52
> ++        # Also see https://github.com/python-versioneer/python-versioneer/issues/52
> +
> +-    cmds = {}
> ++    cmds = {} if cmdclass is None else cmdclass.copy()
> +
> +-    # we add "version" to both distutils and setuptools
> +-    from distutils.core import Command
> ++    # we add "version" to setuptools
> ++    from setuptools import Command
> +
> +     class cmd_version(Command):
> +         description = "report generated version string"
> +-        user_options = []
> +-        boolean_options = []
> ++        user_options: List[Tuple[str, str, str]] = []
> ++        boolean_options: List[str] = []
> +
> +-        def initialize_options(self):
> ++        def initialize_options(self) -> None:
> +             pass
> +
> +-        def finalize_options(self):
> ++        def finalize_options(self) -> None:
> +             pass
> +
> +-        def run(self):
> ++        def run(self) -> None:
> +             vers = get_versions(verbose=True)
> +             print("Version: %s" % vers["version"])
> +             print(" full-revisionid: %s" % vers.get("full-revisionid"))
> +@@ -1521,9 +1955,10 @@ def get_cmdclass():
> +             print(" date: %s" % vers.get("date"))
> +             if vers["error"]:
> +                 print(" error: %s" % vers["error"])
> ++
> +     cmds["version"] = cmd_version
> +
> +-    # we override "build_py" in both distutils and setuptools
> ++    # we override "build_py" in setuptools
> +     #
> +     # most invocation pathways end up running build_py:
> +     #  distutils/build -> build_py
> +@@ -1538,29 +1973,71 @@ def get_cmdclass():
> +     #   then does setup.py bdist_wheel, or sometimes setup.py install
> +     #  setup.py egg_info -> ?
> +
> ++    # pip install -e . and setuptool/editable_wheel will invoke build_py
> ++    # but the build_py command is not expected to copy any files.
> ++
> +     # we override different "build_py" commands for both environments
> +-    if "setuptools" in sys.modules:
> +-        from setuptools.command.build_py import build_py as _build_py
> ++    if "build_py" in cmds:
> ++        _build_py: Any = cmds["build_py"]
> +     else:
> +-        from distutils.command.build_py import build_py as _build_py
> ++        from setuptools.command.build_py import build_py as _build_py
> +
> +     class cmd_build_py(_build_py):
> +-        def run(self):
> ++        def run(self) -> None:
> +             root = get_root()
> +             cfg = get_config_from_root(root)
> +             versions = get_versions()
> +             _build_py.run(self)
> ++            if getattr(self, "editable_mode", False):
> ++                # During editable installs `.py` and data files are
> ++                # not copied to build_lib
> ++                return
> +             # now locate _version.py in the new build/ directory and replace
> +             # it with an updated value
> +             if cfg.versionfile_build:
> +-                target_versionfile = os.path.join(self.build_lib,
> +-                                                  cfg.versionfile_build)
> ++                target_versionfile = os.path.join(self.build_lib, cfg.versionfile_build)
> +                 print("UPDATING %s" % target_versionfile)
> +                 write_to_version_file(target_versionfile, versions)
> ++
> +     cmds["build_py"] = cmd_build_py
> +
> ++    if "build_ext" in cmds:
> ++        _build_ext: Any = cmds["build_ext"]
> ++    else:
> ++        from setuptools.command.build_ext import build_ext as _build_ext
> ++
> ++    class cmd_build_ext(_build_ext):
> ++        def run(self) -> None:
> ++            root = get_root()
> ++            cfg = get_config_from_root(root)
> ++            versions = get_versions()
> ++            _build_ext.run(self)
> ++            if self.inplace:
> ++                # build_ext --inplace will only build extensions in
> ++                # build/lib<..> dir with no _version.py to write to.
> ++                # As in place builds will already have a _version.py
> ++                # in the module dir, we do not need to write one.
> ++                return
> ++            # now locate _version.py in the new build/ directory and replace
> ++            # it with an updated value
> ++            if not cfg.versionfile_build:
> ++                return
> ++            target_versionfile = os.path.join(self.build_lib, cfg.versionfile_build)
> ++            if not os.path.exists(target_versionfile):
> ++                print(
> ++                    f"Warning: {target_versionfile} does not exist, skipping "
> ++                    "version update. This can happen if you are running build_ext "
> ++                    "without first running build_py."
> ++                )
> ++                return
> ++            print("UPDATING %s" % target_versionfile)
> ++            write_to_version_file(target_versionfile, versions)
> ++
> ++    cmds["build_ext"] = cmd_build_ext
> ++
> +     if "cx_Freeze" in sys.modules:  # cx_freeze enabled?
> +-        from cx_Freeze.dist import build_exe as _build_exe
> ++        from cx_Freeze.dist import build_exe as _build_exe  # type: ignore
> ++
> +         # nczeczulin reports that py2exe won't like the pep440-style string
> +         # as FILEVERSION, but it can be used for PRODUCTVERSION, e.g.
> +         # setup(console=[{
> +@@ -1569,7 +2046,7 @@ def get_cmdclass():
> +         #   ...
> +
> +         class cmd_build_exe(_build_exe):
> +-            def run(self):
> ++            def run(self) -> None:
> +                 root = get_root()
> +                 cfg = get_config_from_root(root)
> +                 versions = get_versions()
> +@@ -1581,24 +2058,28 @@ def get_cmdclass():
> +                 os.unlink(target_versionfile)
> +                 with open(cfg.versionfile_source, "w") as f:
> +                     LONG = LONG_VERSION_PY[cfg.VCS]
> +-                    f.write(LONG %
> +-                            {"DOLLAR": "$",
> +-                             "STYLE": cfg.style,
> +-                             "TAG_PREFIX": cfg.tag_prefix,
> +-                             "PARENTDIR_PREFIX": cfg.parentdir_prefix,
> +-                             "VERSIONFILE_SOURCE": cfg.versionfile_source,
> +-                             })
> ++                    f.write(
> ++                        LONG
> ++                        % {
> ++                            "DOLLAR": "$",
> ++                            "STYLE": cfg.style,
> ++                            "TAG_PREFIX": cfg.tag_prefix,
> ++                            "PARENTDIR_PREFIX": cfg.parentdir_prefix,
> ++                            "VERSIONFILE_SOURCE": cfg.versionfile_source,
> ++                        }
> ++                    )
> ++
> +         cmds["build_exe"] = cmd_build_exe
> +         del cmds["build_py"]
> +
> +-    if 'py2exe' in sys.modules:  # py2exe enabled?
> ++    if "py2exe" in sys.modules:  # py2exe enabled?
> +         try:
> +-            from py2exe.distutils_buildexe import py2exe as _py2exe  # py3
> ++            from py2exe.setuptools_buildexe import py2exe as _py2exe  # type: ignore
> +         except ImportError:
> +-            from py2exe.build_exe import py2exe as _py2exe  # py2
> ++            from py2exe.distutils_buildexe import py2exe as _py2exe  # type: ignore
> +
> +         class cmd_py2exe(_py2exe):
> +-            def run(self):
> ++            def run(self) -> None:
> +                 root = get_root()
> +                 cfg = get_config_from_root(root)
> +                 versions = get_versions()
> +@@ -1610,23 +2091,67 @@ def get_cmdclass():
> +                 os.unlink(target_versionfile)
> +                 with open(cfg.versionfile_source, "w") as f:
> +                     LONG = LONG_VERSION_PY[cfg.VCS]
> +-                    f.write(LONG %
> +-                            {"DOLLAR": "$",
> +-                             "STYLE": cfg.style,
> +-                             "TAG_PREFIX": cfg.tag_prefix,
> +-                             "PARENTDIR_PREFIX": cfg.parentdir_prefix,
> +-                             "VERSIONFILE_SOURCE": cfg.versionfile_source,
> +-                             })
> ++                    f.write(
> ++                        LONG
> ++                        % {
> ++                            "DOLLAR": "$",
> ++                            "STYLE": cfg.style,
> ++                            "TAG_PREFIX": cfg.tag_prefix,
> ++                            "PARENTDIR_PREFIX": cfg.parentdir_prefix,
> ++                            "VERSIONFILE_SOURCE": cfg.versionfile_source,
> ++                        }
> ++                    )
> ++
> +         cmds["py2exe"] = cmd_py2exe
> +
> ++    # sdist farms its file list building out to egg_info
> ++    if "egg_info" in cmds:
> ++        _egg_info: Any = cmds["egg_info"]
> ++    else:
> ++        from setuptools.command.egg_info import egg_info as _egg_info
> ++
> ++    class cmd_egg_info(_egg_info):
> ++        def find_sources(self) -> None:
> ++            # egg_info.find_sources builds the manifest list and writes it
> ++            # in one shot
> ++            super().find_sources()
> ++
> ++            # Modify the filelist and normalize it
> ++            root = get_root()
> ++            cfg = get_config_from_root(root)
> ++            self.filelist.append("versioneer.py")
> ++            if cfg.versionfile_source:
> ++                # There are rare cases where versionfile_source might not be
> ++                # included by default, so we must be explicit
> ++                self.filelist.append(cfg.versionfile_source)
> ++            self.filelist.sort()
> ++            self.filelist.remove_duplicates()
> ++
> ++            # The write method is hidden in the manifest_maker instance that
> ++            # generated the filelist and was thrown away
> ++            # We will instead replicate their final normalization (to unicode,
> ++            # and POSIX-style paths)
> ++            from setuptools import unicode_utils
> ++
> ++            normalized = [
> ++                unicode_utils.filesys_decode(f).replace(os.sep, "/")
> ++                for f in self.filelist.files
> ++            ]
> ++
> ++            manifest_filename = os.path.join(self.egg_info, "SOURCES.txt")
> ++            with open(manifest_filename, "w") as fobj:
> ++                fobj.write("\n".join(normalized))
> ++
> ++    cmds["egg_info"] = cmd_egg_info
> ++
> +     # we override different "sdist" commands for both environments
> +-    if "setuptools" in sys.modules:
> +-        from setuptools.command.sdist import sdist as _sdist
> ++    if "sdist" in cmds:
> ++        _sdist: Any = cmds["sdist"]
> +     else:
> +-        from distutils.command.sdist import sdist as _sdist
> ++        from setuptools.command.sdist import sdist as _sdist
> +
> +     class cmd_sdist(_sdist):
> +-        def run(self):
> ++        def run(self) -> None:
> +             versions = get_versions()
> +             self._versioneer_generated_versions = versions
> +             # unless we update this, the command will keep using the old
> +@@ -1634,7 +2159,7 @@ def get_cmdclass():
> +             self.distribution.metadata.version = versions["version"]
> +             return _sdist.run(self)
> +
> +-        def make_release_tree(self, base_dir, files):
> ++        def make_release_tree(self, base_dir: str, files: List[str]) -> None:
> +             root = get_root()
> +             cfg = get_config_from_root(root)
> +             _sdist.make_release_tree(self, base_dir, files)
> +@@ -1643,8 +2168,10 @@ def get_cmdclass():
> +             # updated value
> +             target_versionfile = os.path.join(base_dir, cfg.versionfile_source)
> +             print("UPDATING %s" % target_versionfile)
> +-            write_to_version_file(target_versionfile,
> +-                                  self._versioneer_generated_versions)
> ++            write_to_version_file(
> ++                target_versionfile, self._versioneer_generated_versions
> ++            )
> ++
> +     cmds["sdist"] = cmd_sdist
> +
> +     return cmds
> +@@ -1687,23 +2214,26 @@ SAMPLE_CONFIG = """
> +
> + """
> +
> +-INIT_PY_SNIPPET = """
> ++OLD_SNIPPET = """
> + from ._version import get_versions
> + __version__ = get_versions()['version']
> + del get_versions
> + """
> +
> ++INIT_PY_SNIPPET = """
> ++from . import {0}
> ++__version__ = {0}.get_versions()['version']
> ++"""
> ++
> +
> +-def do_setup():
> +-    """Main VCS-independent setup function for installing Versioneer."""
> ++def do_setup() -> int:
> ++    """Do main VCS-independent setup function for installing Versioneer."""
> +     root = get_root()
> +     try:
> +         cfg = get_config_from_root(root)
> +-    except (EnvironmentError, configparser.NoSectionError,
> +-            configparser.NoOptionError) as e:
> +-        if isinstance(e, (EnvironmentError, configparser.NoSectionError)):
> +-            print("Adding sample versioneer config to setup.cfg",
> +-                  file=sys.stderr)
> ++    except (OSError, configparser.NoSectionError, configparser.NoOptionError) as e:
> ++        if isinstance(e, (OSError, configparser.NoSectionError)):
> ++            print("Adding sample versioneer config to setup.cfg", file=sys.stderr)
> +             with open(os.path.join(root, "setup.cfg"), "a") as f:
> +                 f.write(SAMPLE_CONFIG)
> +         print(CONFIG_ERROR, file=sys.stderr)
> +@@ -1712,71 +2242,49 @@ def do_setup():
> +     print(" creating %s" % cfg.versionfile_source)
> +     with open(cfg.versionfile_source, "w") as f:
> +         LONG = LONG_VERSION_PY[cfg.VCS]
> +-        f.write(LONG % {"DOLLAR": "$",
> +-                        "STYLE": cfg.style,
> +-                        "TAG_PREFIX": cfg.tag_prefix,
> +-                        "PARENTDIR_PREFIX": cfg.parentdir_prefix,
> +-                        "VERSIONFILE_SOURCE": cfg.versionfile_source,
> +-                        })
> +-
> +-    ipy = os.path.join(os.path.dirname(cfg.versionfile_source),
> +-                       "__init__.py")
> ++        f.write(
> ++            LONG
> ++            % {
> ++                "DOLLAR": "$",
> ++                "STYLE": cfg.style,
> ++                "TAG_PREFIX": cfg.tag_prefix,
> ++                "PARENTDIR_PREFIX": cfg.parentdir_prefix,
> ++                "VERSIONFILE_SOURCE": cfg.versionfile_source,
> ++            }
> ++        )
> ++
> ++    ipy = os.path.join(os.path.dirname(cfg.versionfile_source), "__init__.py")
> ++    maybe_ipy: Optional[str] = ipy
> +     if os.path.exists(ipy):
> +         try:
> +             with open(ipy, "r") as f:
> +                 old = f.read()
> +-        except EnvironmentError:
> ++        except OSError:
> +             old = ""
> +-        if INIT_PY_SNIPPET not in old:
> ++        module = os.path.splitext(os.path.basename(cfg.versionfile_source))[0]
> ++        snippet = INIT_PY_SNIPPET.format(module)
> ++        if OLD_SNIPPET in old:
> ++            print(" replacing boilerplate in %s" % ipy)
> ++            with open(ipy, "w") as f:
> ++                f.write(old.replace(OLD_SNIPPET, snippet))
> ++        elif snippet not in old:
> +             print(" appending to %s" % ipy)
> +             with open(ipy, "a") as f:
> +-                f.write(INIT_PY_SNIPPET)
> ++                f.write(snippet)
> +         else:
> +             print(" %s unmodified" % ipy)
> +     else:
> +         print(" %s doesn't exist, ok" % ipy)
> +-        ipy = None
> +-
> +-    # Make sure both the top-level "versioneer.py" and versionfile_source
> +-    # (PKG/_version.py, used by runtime code) are in MANIFEST.in, so
> +-    # they'll be copied into source distributions. Pip won't be able to
> +-    # install the package without this.
> +-    manifest_in = os.path.join(root, "MANIFEST.in")
> +-    simple_includes = set()
> +-    try:
> +-        with open(manifest_in, "r") as f:
> +-            for line in f:
> +-                if line.startswith("include "):
> +-                    for include in line.split()[1:]:
> +-                        simple_includes.add(include)
> +-    except EnvironmentError:
> +-        pass
> +-    # That doesn't cover everything MANIFEST.in can do
> +-    # (http://docs.python.org/2/distutils/sourcedist.html#commands), so
> +-    # it might give some false negatives. Appending redundant 'include'
> +-    # lines is safe, though.
> +-    if "versioneer.py" not in simple_includes:
> +-        print(" appending 'versioneer.py' to MANIFEST.in")
> +-        with open(manifest_in, "a") as f:
> +-            f.write("include versioneer.py\n")
> +-    else:
> +-        print(" 'versioneer.py' already in MANIFEST.in")
> +-    if cfg.versionfile_source not in simple_includes:
> +-        print(" appending versionfile_source ('%s') to MANIFEST.in" %
> +-              cfg.versionfile_source)
> +-        with open(manifest_in, "a") as f:
> +-            f.write("include %s\n" % cfg.versionfile_source)
> +-    else:
> +-        print(" versionfile_source already in MANIFEST.in")
> ++        maybe_ipy = None
> +
> +     # Make VCS-specific changes. For git, this means creating/changing
> +     # .gitattributes to mark _version.py for export-subst keyword
> +     # substitution.
> +-    do_vcs_install(manifest_in, cfg.versionfile_source, ipy)
> ++    do_vcs_install(cfg.versionfile_source, maybe_ipy)
> +     return 0
> +
> +
> +-def scan_setup_py():
> ++def scan_setup_py() -> int:
> +     """Validate the contents of setup.py against Versioneer's expectations."""
> +     found = set()
> +     setters = False
> +@@ -1813,10 +2321,14 @@ def scan_setup_py():
> +     return errors
> +
> +
> ++def setup_command() -> NoReturn:
> ++    """Set up Versioneer and exit with appropriate error code."""
> ++    errors = do_setup()
> ++    errors += scan_setup_py()
> ++    sys.exit(1 if errors else 0)
> ++
> ++
> + if __name__ == "__main__":
> +     cmd = sys.argv[1]
> +     if cmd == "setup":
> +-        errors = do_setup()
> +-        errors += scan_setup_py()
> +-        if errors:
> +-            sys.exit(1)
> ++        setup_command()
> +--
> +2.41.0
> +

_______________________________________________
buildroot mailing list
buildroot@buildroot.org
https://lists.buildroot.org/mailman/listinfo/buildroot


^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: [Buildroot] [PATCH 22/30] package/python-magic-wormhole-mailbox-server: update versioneer to 0.29
  2023-10-26  9:26 ` [Buildroot] [PATCH 22/30] package/python-magic-wormhole-mailbox-server: " Adam Duskett
@ 2023-11-04 21:31   ` Arnout Vandecappelle via buildroot
  0 siblings, 0 replies; 54+ messages in thread
From: Arnout Vandecappelle via buildroot @ 2023-11-04 21:31 UTC (permalink / raw)
  To: Adam Duskett, buildroot
  Cc: Andrey Smirnov, James Hilliard, Asaf Kahlon, Julien Olivain,
	Thomas Petazzoni, Mauro Condarelli



On 26/10/2023 11:26, Adam Duskett wrote:
> Versioneer < 0.21 is incompatible with Python 3.12.0. Use the latest version
> which is 0.29 as of this commit.
> 
> Signed-off-by: Adam Duskett <adam.duskett@amarulasolutions.com>

  Applied to master, thanks.

  Regards,
  Arnout

> ---
>   .../0002-Update-versioneer-to-0.29.patch      | 2194 +++++++++++++++++
>   1 file changed, 2194 insertions(+)
>   create mode 100644 package/python-magic-wormhole-mailbox-server/0002-Update-versioneer-to-0.29.patch
> 
> diff --git a/package/python-magic-wormhole-mailbox-server/0002-Update-versioneer-to-0.29.patch b/package/python-magic-wormhole-mailbox-server/0002-Update-versioneer-to-0.29.patch
> new file mode 100644
> index 0000000000..3152f1aae7
> --- /dev/null
> +++ b/package/python-magic-wormhole-mailbox-server/0002-Update-versioneer-to-0.29.patch
> @@ -0,0 +1,2194 @@
> +From 76b422b7f53dbc41195a184d966230106f6ddc7d Mon Sep 17 00:00:00 2001
> +From: Adam Duskett <adam.duskett@amarulasolutions.com>
> +Date: Tue, 24 Oct 2023 09:50:41 +0200
> +Subject: [PATCH] Update versioneer to 0.29
> +
> +Fixes builds against Python 3.12.0
> +
> +Upstream: https://github.com/magic-wormhole/magic-wormhole-mailbox-server/pull/40
> +
> +Signed-off-by: Adam Duskett <adam.duskett@amarulasolutions.com>
> +---
> + versioneer.py | 1350 ++++++++++++++++++++++++++++++++++---------------
> + 1 file changed, 931 insertions(+), 419 deletions(-)
> +
> +diff --git a/versioneer.py b/versioneer.py
> +index 64fea1c..de97d90 100644
> +--- a/versioneer.py
> ++++ b/versioneer.py
> +@@ -1,5 +1,4 @@
> +-
> +-# Version: 0.18
> ++# Version: 0.29
> +
> + """The Versioneer - like a rocketeer, but for versions.
> +
> +@@ -7,18 +6,14 @@ The Versioneer
> + ==============
> +
> + * like a rocketeer, but for versions!
> +-* https://github.com/warner/python-versioneer
> ++* https://github.com/python-versioneer/python-versioneer
> + * Brian Warner
> +-* License: Public Domain
> +-* Compatible With: python2.6, 2.7, 3.2, 3.3, 3.4, 3.5, 3.6, and pypy
> +-* [![Latest Version]
> +-(https://pypip.in/version/versioneer/badge.svg?style=flat)
> +-](https://pypi.python.org/pypi/versioneer/)
> +-* [![Build Status]
> +-(https://travis-ci.org/warner/python-versioneer.png?branch=master)
> +-](https://travis-ci.org/warner/python-versioneer)
> +-
> +-This is a tool for managing a recorded version number in distutils-based
> ++* License: Public Domain (Unlicense)
> ++* Compatible with: Python 3.7, 3.8, 3.9, 3.10, 3.11 and pypy3
> ++* [![Latest Version][pypi-image]][pypi-url]
> ++* [![Build Status][travis-image]][travis-url]
> ++
> ++This is a tool for managing a recorded version number in setuptools-based
> + python projects. The goal is to remove the tedious and error-prone "update
> + the embedded version string" step from your release process. Making a new
> + release should be as easy as recording a new tag in your version-control
> +@@ -27,9 +22,38 @@ system, and maybe making new tarballs.
> +
> + ## Quick Install
> +
> +-* `pip install versioneer` to somewhere to your $PATH
> +-* add a `[versioneer]` section to your setup.cfg (see below)
> +-* run `versioneer install` in your source tree, commit the results
> ++Versioneer provides two installation modes. The "classic" vendored mode installs
> ++a copy of versioneer into your repository. The experimental build-time dependency mode
> ++is intended to allow you to skip this step and simplify the process of upgrading.
> ++
> ++### Vendored mode
> ++
> ++* `pip install versioneer` to somewhere in your $PATH
> ++   * A [conda-forge recipe](https://github.com/conda-forge/versioneer-feedstock) is
> ++     available, so you can also use `conda install -c conda-forge versioneer`
> ++* add a `[tool.versioneer]` section to your `pyproject.toml` or a
> ++  `[versioneer]` section to your `setup.cfg` (see [Install](INSTALL.md))
> ++   * Note that you will need to add `tomli; python_version < "3.11"` to your
> ++     build-time dependencies if you use `pyproject.toml`
> ++* run `versioneer install --vendor` in your source tree, commit the results
> ++* verify version information with `python setup.py version`
> ++
> ++### Build-time dependency mode
> ++
> ++* `pip install versioneer` to somewhere in your $PATH
> ++   * A [conda-forge recipe](https://github.com/conda-forge/versioneer-feedstock) is
> ++     available, so you can also use `conda install -c conda-forge versioneer`
> ++* add a `[tool.versioneer]` section to your `pyproject.toml` or a
> ++  `[versioneer]` section to your `setup.cfg` (see [Install](INSTALL.md))
> ++* add `versioneer` (with `[toml]` extra, if configuring in `pyproject.toml`)
> ++  to the `requires` key of the `build-system` table in `pyproject.toml`:
> ++  ```toml
> ++  [build-system]
> ++  requires = ["setuptools", "versioneer[toml]"]
> ++  build-backend = "setuptools.build_meta"
> ++  ```
> ++* run `versioneer install --no-vendor` in your source tree, commit the results
> ++* verify version information with `python setup.py version`
> +
> + ## Version Identifiers
> +
> +@@ -61,7 +85,7 @@ version 1.3). Many VCS systems can report a description that captures this,
> + for example `git describe --tags --dirty --always` reports things like
> + "0.7-1-g574ab98-dirty" to indicate that the checkout is one revision past the
> + 0.7 tag, has a unique revision id of "574ab98", and is "dirty" (it has
> +-uncommitted changes.
> ++uncommitted changes).
> +
> + The version identifier is used for multiple purposes:
> +
> +@@ -166,7 +190,7 @@ which may help identify what went wrong).
> +
> + Some situations are known to cause problems for Versioneer. This details the
> + most significant ones. More can be found on Github
> +-[issues page](https://github.com/warner/python-versioneer/issues).
> ++[issues page](https://github.com/python-versioneer/python-versioneer/issues).
> +
> + ### Subprojects
> +
> +@@ -180,7 +204,7 @@ two common reasons why `setup.py` might not be in the root:
> +   `setup.cfg`, and `tox.ini`. Projects like these produce multiple PyPI
> +   distributions (and upload multiple independently-installable tarballs).
> + * Source trees whose main purpose is to contain a C library, but which also
> +-  provide bindings to Python (and perhaps other langauges) in subdirectories.
> ++  provide bindings to Python (and perhaps other languages) in subdirectories.
> +
> + Versioneer will look for `.git` in parent directories, and most operations
> + should get the right version string. However `pip` and `setuptools` have bugs
> +@@ -194,9 +218,9 @@ work too.
> + Pip-8.1.1 is known to have this problem, but hopefully it will get fixed in
> + some later version.
> +
> +-[Bug #38](https://github.com/warner/python-versioneer/issues/38) is tracking
> ++[Bug #38](https://github.com/python-versioneer/python-versioneer/issues/38) is tracking
> + this issue. The discussion in
> +-[PR #61](https://github.com/warner/python-versioneer/pull/61) describes the
> ++[PR #61](https://github.com/python-versioneer/python-versioneer/pull/61) describes the
> + issue from the Versioneer side in more detail.
> + [pip PR#3176](https://github.com/pypa/pip/pull/3176) and
> + [pip PR#3615](https://github.com/pypa/pip/pull/3615) contain work to improve
> +@@ -224,31 +248,20 @@ regenerated while a different version is checked out. Many setup.py commands
> + cause egg_info to be rebuilt (including `sdist`, `wheel`, and installing into
> + a different virtualenv), so this can be surprising.
> +
> +-[Bug #83](https://github.com/warner/python-versioneer/issues/83) describes
> ++[Bug #83](https://github.com/python-versioneer/python-versioneer/issues/83) describes
> + this one, but upgrading to a newer version of setuptools should probably
> + resolve it.
> +
> +-### Unicode version strings
> +-
> +-While Versioneer works (and is continually tested) with both Python 2 and
> +-Python 3, it is not entirely consistent with bytes-vs-unicode distinctions.
> +-Newer releases probably generate unicode version strings on py2. It's not
> +-clear that this is wrong, but it may be surprising for applications when then
> +-write these strings to a network connection or include them in bytes-oriented
> +-APIs like cryptographic checksums.
> +-
> +-[Bug #71](https://github.com/warner/python-versioneer/issues/71) investigates
> +-this question.
> +-
> +
> + ## Updating Versioneer
> +
> + To upgrade your project to a new release of Versioneer, do the following:
> +
> + * install the new Versioneer (`pip install -U versioneer` or equivalent)
> +-* edit `setup.cfg`, if necessary, to include any new configuration settings
> +-  indicated by the release notes. See [UPGRADING](./UPGRADING.md) for details.
> +-* re-run `versioneer install` in your source tree, to replace
> ++* edit `setup.cfg` and `pyproject.toml`, if necessary,
> ++  to include any new configuration settings indicated by the release notes.
> ++  See [UPGRADING](./UPGRADING.md) for details.
> ++* re-run `versioneer install --[no-]vendor` in your source tree, to replace
> +   `SRC/_version.py`
> + * commit any changed files
> +
> +@@ -265,35 +278,70 @@ installation by editing setup.py . Alternatively, it might go the other
> + direction and include code from all supported VCS systems, reducing the
> + number of intermediate scripts.
> +
> ++## Similar projects
> ++
> ++* [setuptools_scm](https://github.com/pypa/setuptools_scm/) - a non-vendored build-time
> ++  dependency
> ++* [minver](https://github.com/jbweston/miniver) - a lightweight reimplementation of
> ++  versioneer
> ++* [versioningit](https://github.com/jwodder/versioningit) - a PEP 518-based setuptools
> ++  plugin
> +
> + ## License
> +
> + To make Versioneer easier to embed, all its code is dedicated to the public
> + domain. The `_version.py` that it creates is also in the public domain.
> +-Specifically, both are released under the Creative Commons "Public Domain
> +-Dedication" license (CC0-1.0), as described in
> +-https://creativecommons.org/publicdomain/zero/1.0/ .
> ++Specifically, both are released under the "Unlicense", as described in
> ++https://unlicense.org/.
> ++
> ++[pypi-image]: https://img.shields.io/pypi/v/versioneer.svg
> ++[pypi-url]: https://pypi.python.org/pypi/versioneer/
> ++[travis-image]:
> ++https://img.shields.io/travis/com/python-versioneer/python-versioneer.svg
> ++[travis-url]: https://travis-ci.com/github/python-versioneer/python-versioneer
> +
> + """
> ++# pylint:disable=invalid-name,import-outside-toplevel,missing-function-docstring
> ++# pylint:disable=missing-class-docstring,too-many-branches,too-many-statements
> ++# pylint:disable=raise-missing-from,too-many-lines,too-many-locals,import-error
> ++# pylint:disable=too-few-public-methods,redefined-outer-name,consider-using-with
> ++# pylint:disable=attribute-defined-outside-init,too-many-arguments
> +
> +-from __future__ import print_function
> +-try:
> +-    import configparser
> +-except ImportError:
> +-    import ConfigParser as configparser
> ++import configparser
> + import errno
> + import json
> + import os
> + import re
> + import subprocess
> + import sys
> ++from pathlib import Path
> ++from typing import Any, Callable, cast, Dict, List, Optional, Tuple, Union
> ++from typing import NoReturn
> ++import functools
> ++
> ++have_tomllib = True
> ++if sys.version_info >= (3, 11):
> ++    import tomllib
> ++else:
> ++    try:
> ++        import tomli as tomllib
> ++    except ImportError:
> ++        have_tomllib = False
> +
> +
> + class VersioneerConfig:
> +     """Container for Versioneer configuration parameters."""
> +
> ++    VCS: str
> ++    style: str
> ++    tag_prefix: str
> ++    versionfile_source: str
> ++    versionfile_build: Optional[str]
> ++    parentdir_prefix: Optional[str]
> ++    verbose: Optional[bool]
> ++
> +
> +-def get_root():
> ++def get_root() -> str:
> +     """Get the project root directory.
> +
> +     We require that all commands are run from the project root, i.e. the
> +@@ -301,18 +349,30 @@ def get_root():
> +     """
> +     root = os.path.realpath(os.path.abspath(os.getcwd()))
> +     setup_py = os.path.join(root, "setup.py")
> ++    pyproject_toml = os.path.join(root, "pyproject.toml")
> +     versioneer_py = os.path.join(root, "versioneer.py")
> +-    if not (os.path.exists(setup_py) or os.path.exists(versioneer_py)):
> ++    if not (
> ++        os.path.exists(setup_py)
> ++        or os.path.exists(pyproject_toml)
> ++        or os.path.exists(versioneer_py)
> ++    ):
> +         # allow 'python path/to/setup.py COMMAND'
> +         root = os.path.dirname(os.path.realpath(os.path.abspath(sys.argv[0])))
> +         setup_py = os.path.join(root, "setup.py")
> ++        pyproject_toml = os.path.join(root, "pyproject.toml")
> +         versioneer_py = os.path.join(root, "versioneer.py")
> +-    if not (os.path.exists(setup_py) or os.path.exists(versioneer_py)):
> +-        err = ("Versioneer was unable to run the project root directory. "
> +-               "Versioneer requires setup.py to be executed from "
> +-               "its immediate directory (like 'python setup.py COMMAND'), "
> +-               "or in a way that lets it use sys.argv[0] to find the root "
> +-               "(like 'python path/to/setup.py COMMAND').")
> ++    if not (
> ++        os.path.exists(setup_py)
> ++        or os.path.exists(pyproject_toml)
> ++        or os.path.exists(versioneer_py)
> ++    ):
> ++        err = (
> ++            "Versioneer was unable to run the project root directory. "
> ++            "Versioneer requires setup.py to be executed from "
> ++            "its immediate directory (like 'python setup.py COMMAND'), "
> ++            "or in a way that lets it use sys.argv[0] to find the root "
> ++            "(like 'python path/to/setup.py COMMAND')."
> ++        )
> +         raise VersioneerBadRootError(err)
> +     try:
> +         # Certain runtime workflows (setup.py install/develop in a setuptools
> +@@ -321,43 +381,64 @@ def get_root():
> +         # module-import table will cache the first one. So we can't use
> +         # os.path.dirname(__file__), as that will find whichever
> +         # versioneer.py was first imported, even in later projects.
> +-        me = os.path.realpath(os.path.abspath(__file__))
> +-        me_dir = os.path.normcase(os.path.splitext(me)[0])
> ++        my_path = os.path.realpath(os.path.abspath(__file__))
> ++        me_dir = os.path.normcase(os.path.splitext(my_path)[0])
> +         vsr_dir = os.path.normcase(os.path.splitext(versioneer_py)[0])
> +-        if me_dir != vsr_dir:
> +-            print("Warning: build in %s is using versioneer.py from %s"
> +-                  % (os.path.dirname(me), versioneer_py))
> ++        if me_dir != vsr_dir and "VERSIONEER_PEP518" not in globals():
> ++            print(
> ++                "Warning: build in %s is using versioneer.py from %s"
> ++                % (os.path.dirname(my_path), versioneer_py)
> ++            )
> +     except NameError:
> +         pass
> +     return root
> +
> +
> +-def get_config_from_root(root):
> ++def get_config_from_root(root: str) -> VersioneerConfig:
> +     """Read the project setup.cfg file to determine Versioneer config."""
> +-    # This might raise EnvironmentError (if setup.cfg is missing), or
> ++    # This might raise OSError (if setup.cfg is missing), or
> +     # configparser.NoSectionError (if it lacks a [versioneer] section), or
> +     # configparser.NoOptionError (if it lacks "VCS="). See the docstring at
> +     # the top of versioneer.py for instructions on writing your setup.cfg .
> +-    setup_cfg = os.path.join(root, "setup.cfg")
> +-    parser = configparser.SafeConfigParser()
> +-    with open(setup_cfg, "r") as f:
> +-        parser.readfp(f)
> +-    VCS = parser.get("versioneer", "VCS")  # mandatory
> +-
> +-    def get(parser, name):
> +-        if parser.has_option("versioneer", name):
> +-            return parser.get("versioneer", name)
> +-        return None
> ++    root_pth = Path(root)
> ++    pyproject_toml = root_pth / "pyproject.toml"
> ++    setup_cfg = root_pth / "setup.cfg"
> ++    section: Union[Dict[str, Any], configparser.SectionProxy, None] = None
> ++    if pyproject_toml.exists() and have_tomllib:
> ++        try:
> ++            with open(pyproject_toml, "rb") as fobj:
> ++                pp = tomllib.load(fobj)
> ++            section = pp["tool"]["versioneer"]
> ++        except (tomllib.TOMLDecodeError, KeyError) as e:
> ++            print(f"Failed to load config from {pyproject_toml}: {e}")
> ++            print("Try to load it from setup.cfg")
> ++    if not section:
> ++        parser = configparser.ConfigParser()
> ++        with open(setup_cfg) as cfg_file:
> ++            parser.read_file(cfg_file)
> ++        parser.get("versioneer", "VCS")  # raise error if missing
> ++
> ++        section = parser["versioneer"]
> ++
> ++    # `cast`` really shouldn't be used, but its simplest for the
> ++    # common VersioneerConfig users at the moment. We verify against
> ++    # `None` values elsewhere where it matters
> ++
> +     cfg = VersioneerConfig()
> +-    cfg.VCS = VCS
> +-    cfg.style = get(parser, "style") or ""
> +-    cfg.versionfile_source = get(parser, "versionfile_source")
> +-    cfg.versionfile_build = get(parser, "versionfile_build")
> +-    cfg.tag_prefix = get(parser, "tag_prefix")
> +-    if cfg.tag_prefix in ("''", '""'):
> ++    cfg.VCS = section["VCS"]
> ++    cfg.style = section.get("style", "")
> ++    cfg.versionfile_source = cast(str, section.get("versionfile_source"))
> ++    cfg.versionfile_build = section.get("versionfile_build")
> ++    cfg.tag_prefix = cast(str, section.get("tag_prefix"))
> ++    if cfg.tag_prefix in ("''", '""', None):
> +         cfg.tag_prefix = ""
> +-    cfg.parentdir_prefix = get(parser, "parentdir_prefix")
> +-    cfg.verbose = get(parser, "verbose")
> ++    cfg.parentdir_prefix = section.get("parentdir_prefix")
> ++    if isinstance(section, configparser.SectionProxy):
> ++        # Make sure configparser translates to bool
> ++        cfg.verbose = section.getboolean("verbose")
> ++    else:
> ++        cfg.verbose = section.get("verbose")
> ++
> +     return cfg
> +
> +
> +@@ -366,37 +447,54 @@ class NotThisMethod(Exception):
> +
> +
> + # these dictionaries contain VCS-specific tools
> +-LONG_VERSION_PY = {}
> +-HANDLERS = {}
> ++LONG_VERSION_PY: Dict[str, str] = {}
> ++HANDLERS: Dict[str, Dict[str, Callable]] = {}
> +
> +
> +-def register_vcs_handler(vcs, method):  # decorator
> +-    """Decorator to mark a method as the handler for a particular VCS."""
> +-    def decorate(f):
> ++def register_vcs_handler(vcs: str, method: str) -> Callable:  # decorator
> ++    """Create decorator to mark a method as the handler of a VCS."""
> ++
> ++    def decorate(f: Callable) -> Callable:
> +         """Store f in HANDLERS[vcs][method]."""
> +-        if vcs not in HANDLERS:
> +-            HANDLERS[vcs] = {}
> +-        HANDLERS[vcs][method] = f
> ++        HANDLERS.setdefault(vcs, {})[method] = f
> +         return f
> ++
> +     return decorate
> +
> +
> +-def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False,
> +-                env=None):
> ++def run_command(
> ++    commands: List[str],
> ++    args: List[str],
> ++    cwd: Optional[str] = None,
> ++    verbose: bool = False,
> ++    hide_stderr: bool = False,
> ++    env: Optional[Dict[str, str]] = None,
> ++) -> Tuple[Optional[str], Optional[int]]:
> +     """Call the given command(s)."""
> +     assert isinstance(commands, list)
> +-    p = None
> +-    for c in commands:
> ++    process = None
> ++
> ++    popen_kwargs: Dict[str, Any] = {}
> ++    if sys.platform == "win32":
> ++        # This hides the console window if pythonw.exe is used
> ++        startupinfo = subprocess.STARTUPINFO()
> ++        startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
> ++        popen_kwargs["startupinfo"] = startupinfo
> ++
> ++    for command in commands:
> +         try:
> +-            dispcmd = str([c] + args)
> ++            dispcmd = str([command] + args)
> +             # remember shell=False, so use git.cmd on windows, not just git
> +-            p = subprocess.Popen([c] + args, cwd=cwd, env=env,
> +-                                 stdout=subprocess.PIPE,
> +-                                 stderr=(subprocess.PIPE if hide_stderr
> +-                                         else None))
> ++            process = subprocess.Popen(
> ++                [command] + args,
> ++                cwd=cwd,
> ++                env=env,
> ++                stdout=subprocess.PIPE,
> ++                stderr=(subprocess.PIPE if hide_stderr else None),
> ++                **popen_kwargs,
> ++            )
> +             break
> +-        except EnvironmentError:
> +-            e = sys.exc_info()[1]
> ++        except OSError as e:
> +             if e.errno == errno.ENOENT:
> +                 continue
> +             if verbose:
> +@@ -407,26 +505,27 @@ def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False,
> +         if verbose:
> +             print("unable to find command, tried %s" % (commands,))
> +         return None, None
> +-    stdout = p.communicate()[0].strip()
> +-    if sys.version_info[0] >= 3:
> +-        stdout = stdout.decode()
> +-    if p.returncode != 0:
> ++    stdout = process.communicate()[0].strip().decode()
> ++    if process.returncode != 0:
> +         if verbose:
> +             print("unable to run %s (error)" % dispcmd)
> +             print("stdout was %s" % stdout)
> +-        return None, p.returncode
> +-    return stdout, p.returncode
> ++        return None, process.returncode
> ++    return stdout, process.returncode
> +
> +
> +-LONG_VERSION_PY['git'] = '''
> ++LONG_VERSION_PY[
> ++    "git"
> ++] = r'''
> + # This file helps to compute a version number in source trees obtained from
> + # git-archive tarball (such as those provided by githubs download-from-tag
> + # feature). Distribution tarballs (built by setup.py sdist) and build
> + # directories (produced by setup.py build) will contain a much shorter file
> + # that just contains the computed version number.
> +
> +-# This file is released into the public domain. Generated by
> +-# versioneer-0.18 (https://github.com/warner/python-versioneer)
> ++# This file is released into the public domain.
> ++# Generated by versioneer-0.29
> ++# https://github.com/python-versioneer/python-versioneer
> +
> + """Git implementation of _version.py."""
> +
> +@@ -435,9 +534,11 @@ import os
> + import re
> + import subprocess
> + import sys
> ++from typing import Any, Callable, Dict, List, Optional, Tuple
> ++import functools
> +
> +
> +-def get_keywords():
> ++def get_keywords() -> Dict[str, str]:
> +     """Get the keywords needed to look up the version information."""
> +     # these strings will be replaced by git during git-archive.
> +     # setup.py/versioneer.py will grep for the variable names, so they must
> +@@ -453,8 +554,15 @@ def get_keywords():
> + class VersioneerConfig:
> +     """Container for Versioneer configuration parameters."""
> +
> ++    VCS: str
> ++    style: str
> ++    tag_prefix: str
> ++    parentdir_prefix: str
> ++    versionfile_source: str
> ++    verbose: bool
> ++
> +
> +-def get_config():
> ++def get_config() -> VersioneerConfig:
> +     """Create, populate and return the VersioneerConfig() object."""
> +     # these strings are filled in when 'setup.py versioneer' creates
> +     # _version.py
> +@@ -472,13 +580,13 @@ class NotThisMethod(Exception):
> +     """Exception raised if a method is not valid for the current scenario."""
> +
> +
> +-LONG_VERSION_PY = {}
> +-HANDLERS = {}
> ++LONG_VERSION_PY: Dict[str, str] = {}
> ++HANDLERS: Dict[str, Dict[str, Callable]] = {}
> +
> +
> +-def register_vcs_handler(vcs, method):  # decorator
> +-    """Decorator to mark a method as the handler for a particular VCS."""
> +-    def decorate(f):
> ++def register_vcs_handler(vcs: str, method: str) -> Callable:  # decorator
> ++    """Create decorator to mark a method as the handler of a VCS."""
> ++    def decorate(f: Callable) -> Callable:
> +         """Store f in HANDLERS[vcs][method]."""
> +         if vcs not in HANDLERS:
> +             HANDLERS[vcs] = {}
> +@@ -487,22 +595,35 @@ def register_vcs_handler(vcs, method):  # decorator
> +     return decorate
> +
> +
> +-def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False,
> +-                env=None):
> ++def run_command(
> ++    commands: List[str],
> ++    args: List[str],
> ++    cwd: Optional[str] = None,
> ++    verbose: bool = False,
> ++    hide_stderr: bool = False,
> ++    env: Optional[Dict[str, str]] = None,
> ++) -> Tuple[Optional[str], Optional[int]]:
> +     """Call the given command(s)."""
> +     assert isinstance(commands, list)
> +-    p = None
> +-    for c in commands:
> ++    process = None
> ++
> ++    popen_kwargs: Dict[str, Any] = {}
> ++    if sys.platform == "win32":
> ++        # This hides the console window if pythonw.exe is used
> ++        startupinfo = subprocess.STARTUPINFO()
> ++        startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
> ++        popen_kwargs["startupinfo"] = startupinfo
> ++
> ++    for command in commands:
> +         try:
> +-            dispcmd = str([c] + args)
> ++            dispcmd = str([command] + args)
> +             # remember shell=False, so use git.cmd on windows, not just git
> +-            p = subprocess.Popen([c] + args, cwd=cwd, env=env,
> +-                                 stdout=subprocess.PIPE,
> +-                                 stderr=(subprocess.PIPE if hide_stderr
> +-                                         else None))
> ++            process = subprocess.Popen([command] + args, cwd=cwd, env=env,
> ++                                       stdout=subprocess.PIPE,
> ++                                       stderr=(subprocess.PIPE if hide_stderr
> ++                                               else None), **popen_kwargs)
> +             break
> +-        except EnvironmentError:
> +-            e = sys.exc_info()[1]
> ++        except OSError as e:
> +             if e.errno == errno.ENOENT:
> +                 continue
> +             if verbose:
> +@@ -513,18 +634,20 @@ def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False,
> +         if verbose:
> +             print("unable to find command, tried %%s" %% (commands,))
> +         return None, None
> +-    stdout = p.communicate()[0].strip()
> +-    if sys.version_info[0] >= 3:
> +-        stdout = stdout.decode()
> +-    if p.returncode != 0:
> ++    stdout = process.communicate()[0].strip().decode()
> ++    if process.returncode != 0:
> +         if verbose:
> +             print("unable to run %%s (error)" %% dispcmd)
> +             print("stdout was %%s" %% stdout)
> +-        return None, p.returncode
> +-    return stdout, p.returncode
> ++        return None, process.returncode
> ++    return stdout, process.returncode
> +
> +
> +-def versions_from_parentdir(parentdir_prefix, root, verbose):
> ++def versions_from_parentdir(
> ++    parentdir_prefix: str,
> ++    root: str,
> ++    verbose: bool,
> ++) -> Dict[str, Any]:
> +     """Try to determine the version from the parent directory name.
> +
> +     Source tarballs conventionally unpack into a directory that includes both
> +@@ -533,15 +656,14 @@ def versions_from_parentdir(parentdir_prefix, root, verbose):
> +     """
> +     rootdirs = []
> +
> +-    for i in range(3):
> ++    for _ in range(3):
> +         dirname = os.path.basename(root)
> +         if dirname.startswith(parentdir_prefix):
> +             return {"version": dirname[len(parentdir_prefix):],
> +                     "full-revisionid": None,
> +                     "dirty": False, "error": None, "date": None}
> +-        else:
> +-            rootdirs.append(root)
> +-            root = os.path.dirname(root)  # up a level
> ++        rootdirs.append(root)
> ++        root = os.path.dirname(root)  # up a level
> +
> +     if verbose:
> +         print("Tried directories %%s but none started with prefix %%s" %%
> +@@ -550,41 +672,48 @@ def versions_from_parentdir(parentdir_prefix, root, verbose):
> +
> +
> + @register_vcs_handler("git", "get_keywords")
> +-def git_get_keywords(versionfile_abs):
> ++def git_get_keywords(versionfile_abs: str) -> Dict[str, str]:
> +     """Extract version information from the given file."""
> +     # the code embedded in _version.py can just fetch the value of these
> +     # keywords. When used from setup.py, we don't want to import _version.py,
> +     # so we do it with a regexp instead. This function is not used from
> +     # _version.py.
> +-    keywords = {}
> ++    keywords: Dict[str, str] = {}
> +     try:
> +-        f = open(versionfile_abs, "r")
> +-        for line in f.readlines():
> +-            if line.strip().startswith("git_refnames ="):
> +-                mo = re.search(r'=\s*"(.*)"', line)
> +-                if mo:
> +-                    keywords["refnames"] = mo.group(1)
> +-            if line.strip().startswith("git_full ="):
> +-                mo = re.search(r'=\s*"(.*)"', line)
> +-                if mo:
> +-                    keywords["full"] = mo.group(1)
> +-            if line.strip().startswith("git_date ="):
> +-                mo = re.search(r'=\s*"(.*)"', line)
> +-                if mo:
> +-                    keywords["date"] = mo.group(1)
> +-        f.close()
> +-    except EnvironmentError:
> ++        with open(versionfile_abs, "r") as fobj:
> ++            for line in fobj:
> ++                if line.strip().startswith("git_refnames ="):
> ++                    mo = re.search(r'=\s*"(.*)"', line)
> ++                    if mo:
> ++                        keywords["refnames"] = mo.group(1)
> ++                if line.strip().startswith("git_full ="):
> ++                    mo = re.search(r'=\s*"(.*)"', line)
> ++                    if mo:
> ++                        keywords["full"] = mo.group(1)
> ++                if line.strip().startswith("git_date ="):
> ++                    mo = re.search(r'=\s*"(.*)"', line)
> ++                    if mo:
> ++                        keywords["date"] = mo.group(1)
> ++    except OSError:
> +         pass
> +     return keywords
> +
> +
> + @register_vcs_handler("git", "keywords")
> +-def git_versions_from_keywords(keywords, tag_prefix, verbose):
> ++def git_versions_from_keywords(
> ++    keywords: Dict[str, str],
> ++    tag_prefix: str,
> ++    verbose: bool,
> ++) -> Dict[str, Any]:
> +     """Get version information from git keywords."""
> +-    if not keywords:
> +-        raise NotThisMethod("no keywords at all, weird")
> ++    if "refnames" not in keywords:
> ++        raise NotThisMethod("Short version file found")
> +     date = keywords.get("date")
> +     if date is not None:
> ++        # Use only the last line.  Previous lines may contain GPG signature
> ++        # information.
> ++        date = date.splitlines()[-1]
> ++
> +         # git-2.2.0 added "%%cI", which expands to an ISO-8601 -compliant
> +         # datestamp. However we prefer "%%ci" (which expands to an "ISO-8601
> +         # -like" string, which we must then edit to make compliant), because
> +@@ -597,11 +726,11 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose):
> +         if verbose:
> +             print("keywords are unexpanded, not using")
> +         raise NotThisMethod("unexpanded keywords, not a git-archive tarball")
> +-    refs = set([r.strip() for r in refnames.strip("()").split(",")])
> ++    refs = {r.strip() for r in refnames.strip("()").split(",")}
> +     # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of
> +     # just "foo-1.0". If we see a "tag: " prefix, prefer those.
> +     TAG = "tag: "
> +-    tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)])
> ++    tags = {r[len(TAG):] for r in refs if r.startswith(TAG)}
> +     if not tags:
> +         # Either we're using git < 1.8.3, or there really are no tags. We use
> +         # a heuristic: assume all version tags have a digit. The old git %%d
> +@@ -610,7 +739,7 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose):
> +         # between branches and tags. By ignoring refnames without digits, we
> +         # filter out many common branch names like "release" and
> +         # "stabilization", as well as "HEAD" and "master".
> +-        tags = set([r for r in refs if re.search(r'\d', r)])
> ++        tags = {r for r in refs if re.search(r'\d', r)}
> +         if verbose:
> +             print("discarding '%%s', no digits" %% ",".join(refs - tags))
> +     if verbose:
> +@@ -619,6 +748,11 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose):
> +         # sorting will prefer e.g. "2.0" over "2.0rc1"
> +         if ref.startswith(tag_prefix):
> +             r = ref[len(tag_prefix):]
> ++            # Filter out refs that exactly match prefix or that don't start
> ++            # with a number once the prefix is stripped (mostly a concern
> ++            # when prefix is '')
> ++            if not re.match(r'\d', r):
> ++                continue
> +             if verbose:
> +                 print("picking %%s" %% r)
> +             return {"version": r,
> +@@ -634,7 +768,12 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose):
> +
> +
> + @register_vcs_handler("git", "pieces_from_vcs")
> +-def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
> ++def git_pieces_from_vcs(
> ++    tag_prefix: str,
> ++    root: str,
> ++    verbose: bool,
> ++    runner: Callable = run_command
> ++) -> Dict[str, Any]:
> +     """Get version from 'git describe' in the root of the source tree.
> +
> +     This only gets called if the git-archive 'subst' keywords were *not*
> +@@ -645,8 +784,15 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
> +     if sys.platform == "win32":
> +         GITS = ["git.cmd", "git.exe"]
> +
> +-    out, rc = run_command(GITS, ["rev-parse", "--git-dir"], cwd=root,
> +-                          hide_stderr=True)
> ++    # GIT_DIR can interfere with correct operation of Versioneer.
> ++    # It may be intended to be passed to the Versioneer-versioned project,
> ++    # but that should not change where we get our version from.
> ++    env = os.environ.copy()
> ++    env.pop("GIT_DIR", None)
> ++    runner = functools.partial(runner, env=env)
> ++
> ++    _, rc = runner(GITS, ["rev-parse", "--git-dir"], cwd=root,
> ++                   hide_stderr=not verbose)
> +     if rc != 0:
> +         if verbose:
> +             print("Directory %%s not under git control" %% root)
> +@@ -654,24 +800,57 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
> +
> +     # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty]
> +     # if there isn't one, this yields HEX[-dirty] (no NUM)
> +-    describe_out, rc = run_command(GITS, ["describe", "--tags", "--dirty",
> +-                                          "--always", "--long",
> +-                                          "--match", "%%s*" %% tag_prefix],
> +-                                   cwd=root)
> ++    describe_out, rc = runner(GITS, [
> ++        "describe", "--tags", "--dirty", "--always", "--long",
> ++        "--match", f"{tag_prefix}[[:digit:]]*"
> ++    ], cwd=root)
> +     # --long was added in git-1.5.5
> +     if describe_out is None:
> +         raise NotThisMethod("'git describe' failed")
> +     describe_out = describe_out.strip()
> +-    full_out, rc = run_command(GITS, ["rev-parse", "HEAD"], cwd=root)
> ++    full_out, rc = runner(GITS, ["rev-parse", "HEAD"], cwd=root)
> +     if full_out is None:
> +         raise NotThisMethod("'git rev-parse' failed")
> +     full_out = full_out.strip()
> +
> +-    pieces = {}
> ++    pieces: Dict[str, Any] = {}
> +     pieces["long"] = full_out
> +     pieces["short"] = full_out[:7]  # maybe improved later
> +     pieces["error"] = None
> +
> ++    branch_name, rc = runner(GITS, ["rev-parse", "--abbrev-ref", "HEAD"],
> ++                             cwd=root)
> ++    # --abbrev-ref was added in git-1.6.3
> ++    if rc != 0 or branch_name is None:
> ++        raise NotThisMethod("'git rev-parse --abbrev-ref' returned error")
> ++    branch_name = branch_name.strip()
> ++
> ++    if branch_name == "HEAD":
> ++        # If we aren't exactly on a branch, pick a branch which represents
> ++        # the current commit. If all else fails, we are on a branchless
> ++        # commit.
> ++        branches, rc = runner(GITS, ["branch", "--contains"], cwd=root)
> ++        # --contains was added in git-1.5.4
> ++        if rc != 0 or branches is None:
> ++            raise NotThisMethod("'git branch --contains' returned error")
> ++        branches = branches.split("\n")
> ++
> ++        # Remove the first line if we're running detached
> ++        if "(" in branches[0]:
> ++            branches.pop(0)
> ++
> ++        # Strip off the leading "* " from the list of branches.
> ++        branches = [branch[2:] for branch in branches]
> ++        if "master" in branches:
> ++            branch_name = "master"
> ++        elif not branches:
> ++            branch_name = None
> ++        else:
> ++            # Pick the first branch that is returned. Good or bad.
> ++            branch_name = branches[0]
> ++
> ++    pieces["branch"] = branch_name
> ++
> +     # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty]
> +     # TAG might have hyphens.
> +     git_describe = describe_out
> +@@ -688,7 +867,7 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
> +         # TAG-NUM-gHEX
> +         mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe)
> +         if not mo:
> +-            # unparseable. Maybe git-describe is misbehaving?
> ++            # unparsable. Maybe git-describe is misbehaving?
> +             pieces["error"] = ("unable to parse git-describe output: '%%s'"
> +                                %% describe_out)
> +             return pieces
> +@@ -713,26 +892,27 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
> +     else:
> +         # HEX: no tags
> +         pieces["closest-tag"] = None
> +-        count_out, rc = run_command(GITS, ["rev-list", "HEAD", "--count"],
> +-                                    cwd=root)
> +-        pieces["distance"] = int(count_out)  # total number of commits
> ++        out, rc = runner(GITS, ["rev-list", "HEAD", "--left-right"], cwd=root)
> ++        pieces["distance"] = len(out.split())  # total number of commits
> +
> +     # commit date: see ISO-8601 comment in git_versions_from_keywords()
> +-    date = run_command(GITS, ["show", "-s", "--format=%%ci", "HEAD"],
> +-                       cwd=root)[0].strip()
> ++    date = runner(GITS, ["show", "-s", "--format=%%ci", "HEAD"], cwd=root)[0].strip()
> ++    # Use only the last line.  Previous lines may contain GPG signature
> ++    # information.
> ++    date = date.splitlines()[-1]
> +     pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1)
> +
> +     return pieces
> +
> +
> +-def plus_or_dot(pieces):
> ++def plus_or_dot(pieces: Dict[str, Any]) -> str:
> +     """Return a + if we don't already have one, else return a ."""
> +     if "+" in pieces.get("closest-tag", ""):
> +         return "."
> +     return "+"
> +
> +
> +-def render_pep440(pieces):
> ++def render_pep440(pieces: Dict[str, Any]) -> str:
> +     """Build up version string, with post-release "local version identifier".
> +
> +     Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you
> +@@ -757,23 +937,71 @@ def render_pep440(pieces):
> +     return rendered
> +
> +
> +-def render_pep440_pre(pieces):
> +-    """TAG[.post.devDISTANCE] -- No -dirty.
> ++def render_pep440_branch(pieces: Dict[str, Any]) -> str:
> ++    """TAG[[.dev0]+DISTANCE.gHEX[.dirty]] .
> ++
> ++    The ".dev0" means not master branch. Note that .dev0 sorts backwards
> ++    (a feature branch will appear "older" than the master branch).
> +
> +     Exceptions:
> +-    1: no tags. 0.post.devDISTANCE
> ++    1: no tags. 0[.dev0]+untagged.DISTANCE.gHEX[.dirty]
> +     """
> +     if pieces["closest-tag"]:
> +         rendered = pieces["closest-tag"]
> ++        if pieces["distance"] or pieces["dirty"]:
> ++            if pieces["branch"] != "master":
> ++                rendered += ".dev0"
> ++            rendered += plus_or_dot(pieces)
> ++            rendered += "%%d.g%%s" %% (pieces["distance"], pieces["short"])
> ++            if pieces["dirty"]:
> ++                rendered += ".dirty"
> ++    else:
> ++        # exception #1
> ++        rendered = "0"
> ++        if pieces["branch"] != "master":
> ++            rendered += ".dev0"
> ++        rendered += "+untagged.%%d.g%%s" %% (pieces["distance"],
> ++                                          pieces["short"])
> ++        if pieces["dirty"]:
> ++            rendered += ".dirty"
> ++    return rendered
> ++
> ++
> ++def pep440_split_post(ver: str) -> Tuple[str, Optional[int]]:
> ++    """Split pep440 version string at the post-release segment.
> ++
> ++    Returns the release segments before the post-release and the
> ++    post-release version number (or -1 if no post-release segment is present).
> ++    """
> ++    vc = str.split(ver, ".post")
> ++    return vc[0], int(vc[1] or 0) if len(vc) == 2 else None
> ++
> ++
> ++def render_pep440_pre(pieces: Dict[str, Any]) -> str:
> ++    """TAG[.postN.devDISTANCE] -- No -dirty.
> ++
> ++    Exceptions:
> ++    1: no tags. 0.post0.devDISTANCE
> ++    """
> ++    if pieces["closest-tag"]:
> +         if pieces["distance"]:
> +-            rendered += ".post.dev%%d" %% pieces["distance"]
> ++            # update the post release segment
> ++            tag_version, post_version = pep440_split_post(pieces["closest-tag"])
> ++            rendered = tag_version
> ++            if post_version is not None:
> ++                rendered += ".post%%d.dev%%d" %% (post_version + 1, pieces["distance"])
> ++            else:
> ++                rendered += ".post0.dev%%d" %% (pieces["distance"])
> ++        else:
> ++            # no commits, use the tag as the version
> ++            rendered = pieces["closest-tag"]
> +     else:
> +         # exception #1
> +-        rendered = "0.post.dev%%d" %% pieces["distance"]
> ++        rendered = "0.post0.dev%%d" %% pieces["distance"]
> +     return rendered
> +
> +
> +-def render_pep440_post(pieces):
> ++def render_pep440_post(pieces: Dict[str, Any]) -> str:
> +     """TAG[.postDISTANCE[.dev0]+gHEX] .
> +
> +     The ".dev0" means dirty. Note that .dev0 sorts backwards
> +@@ -800,12 +1028,41 @@ def render_pep440_post(pieces):
> +     return rendered
> +
> +
> +-def render_pep440_old(pieces):
> ++def render_pep440_post_branch(pieces: Dict[str, Any]) -> str:
> ++    """TAG[.postDISTANCE[.dev0]+gHEX[.dirty]] .
> ++
> ++    The ".dev0" means not master branch.
> ++
> ++    Exceptions:
> ++    1: no tags. 0.postDISTANCE[.dev0]+gHEX[.dirty]
> ++    """
> ++    if pieces["closest-tag"]:
> ++        rendered = pieces["closest-tag"]
> ++        if pieces["distance"] or pieces["dirty"]:
> ++            rendered += ".post%%d" %% pieces["distance"]
> ++            if pieces["branch"] != "master":
> ++                rendered += ".dev0"
> ++            rendered += plus_or_dot(pieces)
> ++            rendered += "g%%s" %% pieces["short"]
> ++            if pieces["dirty"]:
> ++                rendered += ".dirty"
> ++    else:
> ++        # exception #1
> ++        rendered = "0.post%%d" %% pieces["distance"]
> ++        if pieces["branch"] != "master":
> ++            rendered += ".dev0"
> ++        rendered += "+g%%s" %% pieces["short"]
> ++        if pieces["dirty"]:
> ++            rendered += ".dirty"
> ++    return rendered
> ++
> ++
> ++def render_pep440_old(pieces: Dict[str, Any]) -> str:
> +     """TAG[.postDISTANCE[.dev0]] .
> +
> +     The ".dev0" means dirty.
> +
> +-    Eexceptions:
> ++    Exceptions:
> +     1: no tags. 0.postDISTANCE[.dev0]
> +     """
> +     if pieces["closest-tag"]:
> +@@ -822,7 +1079,7 @@ def render_pep440_old(pieces):
> +     return rendered
> +
> +
> +-def render_git_describe(pieces):
> ++def render_git_describe(pieces: Dict[str, Any]) -> str:
> +     """TAG[-DISTANCE-gHEX][-dirty].
> +
> +     Like 'git describe --tags --dirty --always'.
> +@@ -842,7 +1099,7 @@ def render_git_describe(pieces):
> +     return rendered
> +
> +
> +-def render_git_describe_long(pieces):
> ++def render_git_describe_long(pieces: Dict[str, Any]) -> str:
> +     """TAG-DISTANCE-gHEX[-dirty].
> +
> +     Like 'git describe --tags --dirty --always -long'.
> +@@ -862,7 +1119,7 @@ def render_git_describe_long(pieces):
> +     return rendered
> +
> +
> +-def render(pieces, style):
> ++def render(pieces: Dict[str, Any], style: str) -> Dict[str, Any]:
> +     """Render the given version pieces into the requested style."""
> +     if pieces["error"]:
> +         return {"version": "unknown",
> +@@ -876,10 +1133,14 @@ def render(pieces, style):
> +
> +     if style == "pep440":
> +         rendered = render_pep440(pieces)
> ++    elif style == "pep440-branch":
> ++        rendered = render_pep440_branch(pieces)
> +     elif style == "pep440-pre":
> +         rendered = render_pep440_pre(pieces)
> +     elif style == "pep440-post":
> +         rendered = render_pep440_post(pieces)
> ++    elif style == "pep440-post-branch":
> ++        rendered = render_pep440_post_branch(pieces)
> +     elif style == "pep440-old":
> +         rendered = render_pep440_old(pieces)
> +     elif style == "git-describe":
> +@@ -894,7 +1155,7 @@ def render(pieces, style):
> +             "date": pieces.get("date")}
> +
> +
> +-def get_versions():
> ++def get_versions() -> Dict[str, Any]:
> +     """Get version information or return default if unable to do so."""
> +     # I am in _version.py, which lives at ROOT/VERSIONFILE_SOURCE. If we have
> +     # __file__, we can work backwards from there to the root. Some
> +@@ -915,7 +1176,7 @@ def get_versions():
> +         # versionfile_source is the relative path from the top of the source
> +         # tree (where the .git directory might live) to this file. Invert
> +         # this to find the root from __file__.
> +-        for i in cfg.versionfile_source.split('/'):
> ++        for _ in cfg.versionfile_source.split('/'):
> +             root = os.path.dirname(root)
> +     except NameError:
> +         return {"version": "0+unknown", "full-revisionid": None,
> +@@ -942,41 +1203,48 @@ def get_versions():
> +
> +
> + @register_vcs_handler("git", "get_keywords")
> +-def git_get_keywords(versionfile_abs):
> ++def git_get_keywords(versionfile_abs: str) -> Dict[str, str]:
> +     """Extract version information from the given file."""
> +     # the code embedded in _version.py can just fetch the value of these
> +     # keywords. When used from setup.py, we don't want to import _version.py,
> +     # so we do it with a regexp instead. This function is not used from
> +     # _version.py.
> +-    keywords = {}
> ++    keywords: Dict[str, str] = {}
> +     try:
> +-        f = open(versionfile_abs, "r")
> +-        for line in f.readlines():
> +-            if line.strip().startswith("git_refnames ="):
> +-                mo = re.search(r'=\s*"(.*)"', line)
> +-                if mo:
> +-                    keywords["refnames"] = mo.group(1)
> +-            if line.strip().startswith("git_full ="):
> +-                mo = re.search(r'=\s*"(.*)"', line)
> +-                if mo:
> +-                    keywords["full"] = mo.group(1)
> +-            if line.strip().startswith("git_date ="):
> +-                mo = re.search(r'=\s*"(.*)"', line)
> +-                if mo:
> +-                    keywords["date"] = mo.group(1)
> +-        f.close()
> +-    except EnvironmentError:
> ++        with open(versionfile_abs, "r") as fobj:
> ++            for line in fobj:
> ++                if line.strip().startswith("git_refnames ="):
> ++                    mo = re.search(r'=\s*"(.*)"', line)
> ++                    if mo:
> ++                        keywords["refnames"] = mo.group(1)
> ++                if line.strip().startswith("git_full ="):
> ++                    mo = re.search(r'=\s*"(.*)"', line)
> ++                    if mo:
> ++                        keywords["full"] = mo.group(1)
> ++                if line.strip().startswith("git_date ="):
> ++                    mo = re.search(r'=\s*"(.*)"', line)
> ++                    if mo:
> ++                        keywords["date"] = mo.group(1)
> ++    except OSError:
> +         pass
> +     return keywords
> +
> +
> + @register_vcs_handler("git", "keywords")
> +-def git_versions_from_keywords(keywords, tag_prefix, verbose):
> ++def git_versions_from_keywords(
> ++    keywords: Dict[str, str],
> ++    tag_prefix: str,
> ++    verbose: bool,
> ++) -> Dict[str, Any]:
> +     """Get version information from git keywords."""
> +-    if not keywords:
> +-        raise NotThisMethod("no keywords at all, weird")
> ++    if "refnames" not in keywords:
> ++        raise NotThisMethod("Short version file found")
> +     date = keywords.get("date")
> +     if date is not None:
> ++        # Use only the last line.  Previous lines may contain GPG signature
> ++        # information.
> ++        date = date.splitlines()[-1]
> ++
> +         # git-2.2.0 added "%cI", which expands to an ISO-8601 -compliant
> +         # datestamp. However we prefer "%ci" (which expands to an "ISO-8601
> +         # -like" string, which we must then edit to make compliant), because
> +@@ -989,11 +1257,11 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose):
> +         if verbose:
> +             print("keywords are unexpanded, not using")
> +         raise NotThisMethod("unexpanded keywords, not a git-archive tarball")
> +-    refs = set([r.strip() for r in refnames.strip("()").split(",")])
> ++    refs = {r.strip() for r in refnames.strip("()").split(",")}
> +     # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of
> +     # just "foo-1.0". If we see a "tag: " prefix, prefer those.
> +     TAG = "tag: "
> +-    tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)])
> ++    tags = {r[len(TAG) :] for r in refs if r.startswith(TAG)}
> +     if not tags:
> +         # Either we're using git < 1.8.3, or there really are no tags. We use
> +         # a heuristic: assume all version tags have a digit. The old git %d
> +@@ -1002,7 +1270,7 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose):
> +         # between branches and tags. By ignoring refnames without digits, we
> +         # filter out many common branch names like "release" and
> +         # "stabilization", as well as "HEAD" and "master".
> +-        tags = set([r for r in refs if re.search(r'\d', r)])
> ++        tags = {r for r in refs if re.search(r"\d", r)}
> +         if verbose:
> +             print("discarding '%s', no digits" % ",".join(refs - tags))
> +     if verbose:
> +@@ -1010,23 +1278,37 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose):
> +     for ref in sorted(tags):
> +         # sorting will prefer e.g. "2.0" over "2.0rc1"
> +         if ref.startswith(tag_prefix):
> +-            r = ref[len(tag_prefix):]
> ++            r = ref[len(tag_prefix) :]
> ++            # Filter out refs that exactly match prefix or that don't start
> ++            # with a number once the prefix is stripped (mostly a concern
> ++            # when prefix is '')
> ++            if not re.match(r"\d", r):
> ++                continue
> +             if verbose:
> +                 print("picking %s" % r)
> +-            return {"version": r,
> +-                    "full-revisionid": keywords["full"].strip(),
> +-                    "dirty": False, "error": None,
> +-                    "date": date}
> ++            return {
> ++                "version": r,
> ++                "full-revisionid": keywords["full"].strip(),
> ++                "dirty": False,
> ++                "error": None,
> ++                "date": date,
> ++            }
> +     # no suitable tags, so version is "0+unknown", but full hex is still there
> +     if verbose:
> +         print("no suitable tags, using unknown + full revision id")
> +-    return {"version": "0+unknown",
> +-            "full-revisionid": keywords["full"].strip(),
> +-            "dirty": False, "error": "no suitable tags", "date": None}
> ++    return {
> ++        "version": "0+unknown",
> ++        "full-revisionid": keywords["full"].strip(),
> ++        "dirty": False,
> ++        "error": "no suitable tags",
> ++        "date": None,
> ++    }
> +
> +
> + @register_vcs_handler("git", "pieces_from_vcs")
> +-def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
> ++def git_pieces_from_vcs(
> ++    tag_prefix: str, root: str, verbose: bool, runner: Callable = run_command
> ++) -> Dict[str, Any]:
> +     """Get version from 'git describe' in the root of the source tree.
> +
> +     This only gets called if the git-archive 'subst' keywords were *not*
> +@@ -1037,8 +1319,14 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
> +     if sys.platform == "win32":
> +         GITS = ["git.cmd", "git.exe"]
> +
> +-    out, rc = run_command(GITS, ["rev-parse", "--git-dir"], cwd=root,
> +-                          hide_stderr=True)
> ++    # GIT_DIR can interfere with correct operation of Versioneer.
> ++    # It may be intended to be passed to the Versioneer-versioned project,
> ++    # but that should not change where we get our version from.
> ++    env = os.environ.copy()
> ++    env.pop("GIT_DIR", None)
> ++    runner = functools.partial(runner, env=env)
> ++
> ++    _, rc = runner(GITS, ["rev-parse", "--git-dir"], cwd=root, hide_stderr=not verbose)
> +     if rc != 0:
> +         if verbose:
> +             print("Directory %s not under git control" % root)
> +@@ -1046,24 +1334,65 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
> +
> +     # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty]
> +     # if there isn't one, this yields HEX[-dirty] (no NUM)
> +-    describe_out, rc = run_command(GITS, ["describe", "--tags", "--dirty",
> +-                                          "--always", "--long",
> +-                                          "--match", "%s*" % tag_prefix],
> +-                                   cwd=root)
> ++    describe_out, rc = runner(
> ++        GITS,
> ++        [
> ++            "describe",
> ++            "--tags",
> ++            "--dirty",
> ++            "--always",
> ++            "--long",
> ++            "--match",
> ++            f"{tag_prefix}[[:digit:]]*",
> ++        ],
> ++        cwd=root,
> ++    )
> +     # --long was added in git-1.5.5
> +     if describe_out is None:
> +         raise NotThisMethod("'git describe' failed")
> +     describe_out = describe_out.strip()
> +-    full_out, rc = run_command(GITS, ["rev-parse", "HEAD"], cwd=root)
> ++    full_out, rc = runner(GITS, ["rev-parse", "HEAD"], cwd=root)
> +     if full_out is None:
> +         raise NotThisMethod("'git rev-parse' failed")
> +     full_out = full_out.strip()
> +
> +-    pieces = {}
> ++    pieces: Dict[str, Any] = {}
> +     pieces["long"] = full_out
> +     pieces["short"] = full_out[:7]  # maybe improved later
> +     pieces["error"] = None
> +
> ++    branch_name, rc = runner(GITS, ["rev-parse", "--abbrev-ref", "HEAD"], cwd=root)
> ++    # --abbrev-ref was added in git-1.6.3
> ++    if rc != 0 or branch_name is None:
> ++        raise NotThisMethod("'git rev-parse --abbrev-ref' returned error")
> ++    branch_name = branch_name.strip()
> ++
> ++    if branch_name == "HEAD":
> ++        # If we aren't exactly on a branch, pick a branch which represents
> ++        # the current commit. If all else fails, we are on a branchless
> ++        # commit.
> ++        branches, rc = runner(GITS, ["branch", "--contains"], cwd=root)
> ++        # --contains was added in git-1.5.4
> ++        if rc != 0 or branches is None:
> ++            raise NotThisMethod("'git branch --contains' returned error")
> ++        branches = branches.split("\n")
> ++
> ++        # Remove the first line if we're running detached
> ++        if "(" in branches[0]:
> ++            branches.pop(0)
> ++
> ++        # Strip off the leading "* " from the list of branches.
> ++        branches = [branch[2:] for branch in branches]
> ++        if "master" in branches:
> ++            branch_name = "master"
> ++        elif not branches:
> ++            branch_name = None
> ++        else:
> ++            # Pick the first branch that is returned. Good or bad.
> ++            branch_name = branches[0]
> ++
> ++    pieces["branch"] = branch_name
> ++
> +     # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty]
> +     # TAG might have hyphens.
> +     git_describe = describe_out
> +@@ -1072,17 +1401,16 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
> +     dirty = git_describe.endswith("-dirty")
> +     pieces["dirty"] = dirty
> +     if dirty:
> +-        git_describe = git_describe[:git_describe.rindex("-dirty")]
> ++        git_describe = git_describe[: git_describe.rindex("-dirty")]
> +
> +     # now we have TAG-NUM-gHEX or HEX
> +
> +     if "-" in git_describe:
> +         # TAG-NUM-gHEX
> +-        mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe)
> ++        mo = re.search(r"^(.+)-(\d+)-g([0-9a-f]+)$", git_describe)
> +         if not mo:
> +-            # unparseable. Maybe git-describe is misbehaving?
> +-            pieces["error"] = ("unable to parse git-describe output: '%s'"
> +-                               % describe_out)
> ++            # unparsable. Maybe git-describe is misbehaving?
> ++            pieces["error"] = "unable to parse git-describe output: '%s'" % describe_out
> +             return pieces
> +
> +         # tag
> +@@ -1091,10 +1419,12 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
> +             if verbose:
> +                 fmt = "tag '%s' doesn't start with prefix '%s'"
> +                 print(fmt % (full_tag, tag_prefix))
> +-            pieces["error"] = ("tag '%s' doesn't start with prefix '%s'"
> +-                               % (full_tag, tag_prefix))
> ++            pieces["error"] = "tag '%s' doesn't start with prefix '%s'" % (
> ++                full_tag,
> ++                tag_prefix,
> ++            )
> +             return pieces
> +-        pieces["closest-tag"] = full_tag[len(tag_prefix):]
> ++        pieces["closest-tag"] = full_tag[len(tag_prefix) :]
> +
> +         # distance: number of commits since tag
> +         pieces["distance"] = int(mo.group(2))
> +@@ -1105,19 +1435,20 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
> +     else:
> +         # HEX: no tags
> +         pieces["closest-tag"] = None
> +-        count_out, rc = run_command(GITS, ["rev-list", "HEAD", "--count"],
> +-                                    cwd=root)
> +-        pieces["distance"] = int(count_out)  # total number of commits
> ++        out, rc = runner(GITS, ["rev-list", "HEAD", "--left-right"], cwd=root)
> ++        pieces["distance"] = len(out.split())  # total number of commits
> +
> +     # commit date: see ISO-8601 comment in git_versions_from_keywords()
> +-    date = run_command(GITS, ["show", "-s", "--format=%ci", "HEAD"],
> +-                       cwd=root)[0].strip()
> ++    date = runner(GITS, ["show", "-s", "--format=%ci", "HEAD"], cwd=root)[0].strip()
> ++    # Use only the last line.  Previous lines may contain GPG signature
> ++    # information.
> ++    date = date.splitlines()[-1]
> +     pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1)
> +
> +     return pieces
> +
> +
> +-def do_vcs_install(manifest_in, versionfile_source, ipy):
> ++def do_vcs_install(versionfile_source: str, ipy: Optional[str]) -> None:
> +     """Git-specific installation logic for Versioneer.
> +
> +     For Git, this means creating/changing .gitattributes to mark _version.py
> +@@ -1126,36 +1457,40 @@ def do_vcs_install(manifest_in, versionfile_source, ipy):
> +     GITS = ["git"]
> +     if sys.platform == "win32":
> +         GITS = ["git.cmd", "git.exe"]
> +-    files = [manifest_in, versionfile_source]
> ++    files = [versionfile_source]
> +     if ipy:
> +         files.append(ipy)
> +-    try:
> +-        me = __file__
> +-        if me.endswith(".pyc") or me.endswith(".pyo"):
> +-            me = os.path.splitext(me)[0] + ".py"
> +-        versioneer_file = os.path.relpath(me)
> +-    except NameError:
> +-        versioneer_file = "versioneer.py"
> +-    files.append(versioneer_file)
> ++    if "VERSIONEER_PEP518" not in globals():
> ++        try:
> ++            my_path = __file__
> ++            if my_path.endswith((".pyc", ".pyo")):
> ++                my_path = os.path.splitext(my_path)[0] + ".py"
> ++            versioneer_file = os.path.relpath(my_path)
> ++        except NameError:
> ++            versioneer_file = "versioneer.py"
> ++        files.append(versioneer_file)
> +     present = False
> +     try:
> +-        f = open(".gitattributes", "r")
> +-        for line in f.readlines():
> +-            if line.strip().startswith(versionfile_source):
> +-                if "export-subst" in line.strip().split()[1:]:
> +-                    present = True
> +-        f.close()
> +-    except EnvironmentError:
> ++        with open(".gitattributes", "r") as fobj:
> ++            for line in fobj:
> ++                if line.strip().startswith(versionfile_source):
> ++                    if "export-subst" in line.strip().split()[1:]:
> ++                        present = True
> ++                        break
> ++    except OSError:
> +         pass
> +     if not present:
> +-        f = open(".gitattributes", "a+")
> +-        f.write("%s export-subst\n" % versionfile_source)
> +-        f.close()
> ++        with open(".gitattributes", "a+") as fobj:
> ++            fobj.write(f"{versionfile_source} export-subst\n")
> +         files.append(".gitattributes")
> +     run_command(GITS, ["add", "--"] + files)
> +
> +
> +-def versions_from_parentdir(parentdir_prefix, root, verbose):
> ++def versions_from_parentdir(
> ++    parentdir_prefix: str,
> ++    root: str,
> ++    verbose: bool,
> ++) -> Dict[str, Any]:
> +     """Try to determine the version from the parent directory name.
> +
> +     Source tarballs conventionally unpack into a directory that includes both
> +@@ -1164,24 +1499,29 @@ def versions_from_parentdir(parentdir_prefix, root, verbose):
> +     """
> +     rootdirs = []
> +
> +-    for i in range(3):
> ++    for _ in range(3):
> +         dirname = os.path.basename(root)
> +         if dirname.startswith(parentdir_prefix):
> +-            return {"version": dirname[len(parentdir_prefix):],
> +-                    "full-revisionid": None,
> +-                    "dirty": False, "error": None, "date": None}
> +-        else:
> +-            rootdirs.append(root)
> +-            root = os.path.dirname(root)  # up a level
> ++            return {
> ++                "version": dirname[len(parentdir_prefix) :],
> ++                "full-revisionid": None,
> ++                "dirty": False,
> ++                "error": None,
> ++                "date": None,
> ++            }
> ++        rootdirs.append(root)
> ++        root = os.path.dirname(root)  # up a level
> +
> +     if verbose:
> +-        print("Tried directories %s but none started with prefix %s" %
> +-              (str(rootdirs), parentdir_prefix))
> ++        print(
> ++            "Tried directories %s but none started with prefix %s"
> ++            % (str(rootdirs), parentdir_prefix)
> ++        )
> +     raise NotThisMethod("rootdir doesn't start with parentdir_prefix")
> +
> +
> + SHORT_VERSION_PY = """
> +-# This file was generated by 'versioneer.py' (0.18) from
> ++# This file was generated by 'versioneer.py' (0.29) from
> + # revision-control system data, or from the parent directory name of an
> + # unpacked source archive. Distribution tarballs contain a pre-generated copy
> + # of this file.
> +@@ -1198,42 +1538,42 @@ def get_versions():
> + """
> +
> +
> +-def versions_from_file(filename):
> ++def versions_from_file(filename: str) -> Dict[str, Any]:
> +     """Try to determine the version from _version.py if present."""
> +     try:
> +         with open(filename) as f:
> +             contents = f.read()
> +-    except EnvironmentError:
> ++    except OSError:
> +         raise NotThisMethod("unable to read _version.py")
> +-    mo = re.search(r"version_json = '''\n(.*)'''  # END VERSION_JSON",
> +-                   contents, re.M | re.S)
> ++    mo = re.search(
> ++        r"version_json = '''\n(.*)'''  # END VERSION_JSON", contents, re.M | re.S
> ++    )
> +     if not mo:
> +-        mo = re.search(r"version_json = '''\r\n(.*)'''  # END VERSION_JSON",
> +-                       contents, re.M | re.S)
> ++        mo = re.search(
> ++            r"version_json = '''\r\n(.*)'''  # END VERSION_JSON", contents, re.M | re.S
> ++        )
> +     if not mo:
> +         raise NotThisMethod("no version_json in _version.py")
> +     return json.loads(mo.group(1))
> +
> +
> +-def write_to_version_file(filename, versions):
> ++def write_to_version_file(filename: str, versions: Dict[str, Any]) -> None:
> +     """Write the given version number to the given _version.py file."""
> +-    os.unlink(filename)
> +-    contents = json.dumps(versions, sort_keys=True,
> +-                          indent=1, separators=(",", ": "))
> ++    contents = json.dumps(versions, sort_keys=True, indent=1, separators=(",", ": "))
> +     with open(filename, "w") as f:
> +         f.write(SHORT_VERSION_PY % contents)
> +
> +     print("set %s to '%s'" % (filename, versions["version"]))
> +
> +
> +-def plus_or_dot(pieces):
> ++def plus_or_dot(pieces: Dict[str, Any]) -> str:
> +     """Return a + if we don't already have one, else return a ."""
> +     if "+" in pieces.get("closest-tag", ""):
> +         return "."
> +     return "+"
> +
> +
> +-def render_pep440(pieces):
> ++def render_pep440(pieces: Dict[str, Any]) -> str:
> +     """Build up version string, with post-release "local version identifier".
> +
> +     Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you
> +@@ -1251,30 +1591,76 @@ def render_pep440(pieces):
> +                 rendered += ".dirty"
> +     else:
> +         # exception #1
> +-        rendered = "0+untagged.%d.g%s" % (pieces["distance"],
> +-                                          pieces["short"])
> ++        rendered = "0+untagged.%d.g%s" % (pieces["distance"], pieces["short"])
> +         if pieces["dirty"]:
> +             rendered += ".dirty"
> +     return rendered
> +
> +
> +-def render_pep440_pre(pieces):
> +-    """TAG[.post.devDISTANCE] -- No -dirty.
> ++def render_pep440_branch(pieces: Dict[str, Any]) -> str:
> ++    """TAG[[.dev0]+DISTANCE.gHEX[.dirty]] .
> ++
> ++    The ".dev0" means not master branch. Note that .dev0 sorts backwards
> ++    (a feature branch will appear "older" than the master branch).
> +
> +     Exceptions:
> +-    1: no tags. 0.post.devDISTANCE
> ++    1: no tags. 0[.dev0]+untagged.DISTANCE.gHEX[.dirty]
> +     """
> +     if pieces["closest-tag"]:
> +         rendered = pieces["closest-tag"]
> ++        if pieces["distance"] or pieces["dirty"]:
> ++            if pieces["branch"] != "master":
> ++                rendered += ".dev0"
> ++            rendered += plus_or_dot(pieces)
> ++            rendered += "%d.g%s" % (pieces["distance"], pieces["short"])
> ++            if pieces["dirty"]:
> ++                rendered += ".dirty"
> ++    else:
> ++        # exception #1
> ++        rendered = "0"
> ++        if pieces["branch"] != "master":
> ++            rendered += ".dev0"
> ++        rendered += "+untagged.%d.g%s" % (pieces["distance"], pieces["short"])
> ++        if pieces["dirty"]:
> ++            rendered += ".dirty"
> ++    return rendered
> ++
> ++
> ++def pep440_split_post(ver: str) -> Tuple[str, Optional[int]]:
> ++    """Split pep440 version string at the post-release segment.
> ++
> ++    Returns the release segments before the post-release and the
> ++    post-release version number (or -1 if no post-release segment is present).
> ++    """
> ++    vc = str.split(ver, ".post")
> ++    return vc[0], int(vc[1] or 0) if len(vc) == 2 else None
> ++
> ++
> ++def render_pep440_pre(pieces: Dict[str, Any]) -> str:
> ++    """TAG[.postN.devDISTANCE] -- No -dirty.
> ++
> ++    Exceptions:
> ++    1: no tags. 0.post0.devDISTANCE
> ++    """
> ++    if pieces["closest-tag"]:
> +         if pieces["distance"]:
> +-            rendered += ".post.dev%d" % pieces["distance"]
> ++            # update the post release segment
> ++            tag_version, post_version = pep440_split_post(pieces["closest-tag"])
> ++            rendered = tag_version
> ++            if post_version is not None:
> ++                rendered += ".post%d.dev%d" % (post_version + 1, pieces["distance"])
> ++            else:
> ++                rendered += ".post0.dev%d" % (pieces["distance"])
> ++        else:
> ++            # no commits, use the tag as the version
> ++            rendered = pieces["closest-tag"]
> +     else:
> +         # exception #1
> +-        rendered = "0.post.dev%d" % pieces["distance"]
> ++        rendered = "0.post0.dev%d" % pieces["distance"]
> +     return rendered
> +
> +
> +-def render_pep440_post(pieces):
> ++def render_pep440_post(pieces: Dict[str, Any]) -> str:
> +     """TAG[.postDISTANCE[.dev0]+gHEX] .
> +
> +     The ".dev0" means dirty. Note that .dev0 sorts backwards
> +@@ -1301,12 +1687,41 @@ def render_pep440_post(pieces):
> +     return rendered
> +
> +
> +-def render_pep440_old(pieces):
> ++def render_pep440_post_branch(pieces: Dict[str, Any]) -> str:
> ++    """TAG[.postDISTANCE[.dev0]+gHEX[.dirty]] .
> ++
> ++    The ".dev0" means not master branch.
> ++
> ++    Exceptions:
> ++    1: no tags. 0.postDISTANCE[.dev0]+gHEX[.dirty]
> ++    """
> ++    if pieces["closest-tag"]:
> ++        rendered = pieces["closest-tag"]
> ++        if pieces["distance"] or pieces["dirty"]:
> ++            rendered += ".post%d" % pieces["distance"]
> ++            if pieces["branch"] != "master":
> ++                rendered += ".dev0"
> ++            rendered += plus_or_dot(pieces)
> ++            rendered += "g%s" % pieces["short"]
> ++            if pieces["dirty"]:
> ++                rendered += ".dirty"
> ++    else:
> ++        # exception #1
> ++        rendered = "0.post%d" % pieces["distance"]
> ++        if pieces["branch"] != "master":
> ++            rendered += ".dev0"
> ++        rendered += "+g%s" % pieces["short"]
> ++        if pieces["dirty"]:
> ++            rendered += ".dirty"
> ++    return rendered
> ++
> ++
> ++def render_pep440_old(pieces: Dict[str, Any]) -> str:
> +     """TAG[.postDISTANCE[.dev0]] .
> +
> +     The ".dev0" means dirty.
> +
> +-    Eexceptions:
> ++    Exceptions:
> +     1: no tags. 0.postDISTANCE[.dev0]
> +     """
> +     if pieces["closest-tag"]:
> +@@ -1323,7 +1738,7 @@ def render_pep440_old(pieces):
> +     return rendered
> +
> +
> +-def render_git_describe(pieces):
> ++def render_git_describe(pieces: Dict[str, Any]) -> str:
> +     """TAG[-DISTANCE-gHEX][-dirty].
> +
> +     Like 'git describe --tags --dirty --always'.
> +@@ -1343,7 +1758,7 @@ def render_git_describe(pieces):
> +     return rendered
> +
> +
> +-def render_git_describe_long(pieces):
> ++def render_git_describe_long(pieces: Dict[str, Any]) -> str:
> +     """TAG-DISTANCE-gHEX[-dirty].
> +
> +     Like 'git describe --tags --dirty --always -long'.
> +@@ -1363,24 +1778,30 @@ def render_git_describe_long(pieces):
> +     return rendered
> +
> +
> +-def render(pieces, style):
> ++def render(pieces: Dict[str, Any], style: str) -> Dict[str, Any]:
> +     """Render the given version pieces into the requested style."""
> +     if pieces["error"]:
> +-        return {"version": "unknown",
> +-                "full-revisionid": pieces.get("long"),
> +-                "dirty": None,
> +-                "error": pieces["error"],
> +-                "date": None}
> ++        return {
> ++            "version": "unknown",
> ++            "full-revisionid": pieces.get("long"),
> ++            "dirty": None,
> ++            "error": pieces["error"],
> ++            "date": None,
> ++        }
> +
> +     if not style or style == "default":
> +         style = "pep440"  # the default
> +
> +     if style == "pep440":
> +         rendered = render_pep440(pieces)
> ++    elif style == "pep440-branch":
> ++        rendered = render_pep440_branch(pieces)
> +     elif style == "pep440-pre":
> +         rendered = render_pep440_pre(pieces)
> +     elif style == "pep440-post":
> +         rendered = render_pep440_post(pieces)
> ++    elif style == "pep440-post-branch":
> ++        rendered = render_pep440_post_branch(pieces)
> +     elif style == "pep440-old":
> +         rendered = render_pep440_old(pieces)
> +     elif style == "git-describe":
> +@@ -1390,16 +1811,20 @@ def render(pieces, style):
> +     else:
> +         raise ValueError("unknown style '%s'" % style)
> +
> +-    return {"version": rendered, "full-revisionid": pieces["long"],
> +-            "dirty": pieces["dirty"], "error": None,
> +-            "date": pieces.get("date")}
> ++    return {
> ++        "version": rendered,
> ++        "full-revisionid": pieces["long"],
> ++        "dirty": pieces["dirty"],
> ++        "error": None,
> ++        "date": pieces.get("date"),
> ++    }
> +
> +
> + class VersioneerBadRootError(Exception):
> +     """The project root directory is unknown or missing key files."""
> +
> +
> +-def get_versions(verbose=False):
> ++def get_versions(verbose: bool = False) -> Dict[str, Any]:
> +     """Get the project version from whatever source is available.
> +
> +     Returns dict with two keys: 'version' and 'full'.
> +@@ -1414,9 +1839,10 @@ def get_versions(verbose=False):
> +     assert cfg.VCS is not None, "please set [versioneer]VCS= in setup.cfg"
> +     handlers = HANDLERS.get(cfg.VCS)
> +     assert handlers, "unrecognized VCS '%s'" % cfg.VCS
> +-    verbose = verbose or cfg.verbose
> +-    assert cfg.versionfile_source is not None, \
> +-        "please set versioneer.versionfile_source"
> ++    verbose = verbose or bool(cfg.verbose)  # `bool()` used to avoid `None`
> ++    assert (
> ++        cfg.versionfile_source is not None
> ++    ), "please set versioneer.versionfile_source"
> +     assert cfg.tag_prefix is not None, "please set versioneer.tag_prefix"
> +
> +     versionfile_abs = os.path.join(root, cfg.versionfile_source)
> +@@ -1470,18 +1896,26 @@ def get_versions(verbose=False):
> +     if verbose:
> +         print("unable to compute version")
> +
> +-    return {"version": "0+unknown", "full-revisionid": None,
> +-            "dirty": None, "error": "unable to compute version",
> +-            "date": None}
> ++    return {
> ++        "version": "0+unknown",
> ++        "full-revisionid": None,
> ++        "dirty": None,
> ++        "error": "unable to compute version",
> ++        "date": None,
> ++    }
> +
> +
> +-def get_version():
> ++def get_version() -> str:
> +     """Get the short version string for this project."""
> +     return get_versions()["version"]
> +
> +
> +-def get_cmdclass():
> +-    """Get the custom setuptools/distutils subclasses used by Versioneer."""
> ++def get_cmdclass(cmdclass: Optional[Dict[str, Any]] = None):
> ++    """Get the custom setuptools subclasses used by Versioneer.
> ++
> ++    If the package uses a different cmdclass (e.g. one from numpy), it
> ++    should be provide as an argument.
> ++    """
> +     if "versioneer" in sys.modules:
> +         del sys.modules["versioneer"]
> +         # this fixes the "python setup.py develop" case (also 'install' and
> +@@ -1495,25 +1929,25 @@ def get_cmdclass():
> +         # parent is protected against the child's "import versioneer". By
> +         # removing ourselves from sys.modules here, before the child build
> +         # happens, we protect the child from the parent's versioneer too.
> +-        # Also see https://github.com/warner/python-versioneer/issues/52
> ++        # Also see https://github.com/python-versioneer/python-versioneer/issues/52
> +
> +-    cmds = {}
> ++    cmds = {} if cmdclass is None else cmdclass.copy()
> +
> +-    # we add "version" to both distutils and setuptools
> +-    from distutils.core import Command
> ++    # we add "version" to setuptools
> ++    from setuptools import Command
> +
> +     class cmd_version(Command):
> +         description = "report generated version string"
> +-        user_options = []
> +-        boolean_options = []
> ++        user_options: List[Tuple[str, str, str]] = []
> ++        boolean_options: List[str] = []
> +
> +-        def initialize_options(self):
> ++        def initialize_options(self) -> None:
> +             pass
> +
> +-        def finalize_options(self):
> ++        def finalize_options(self) -> None:
> +             pass
> +
> +-        def run(self):
> ++        def run(self) -> None:
> +             vers = get_versions(verbose=True)
> +             print("Version: %s" % vers["version"])
> +             print(" full-revisionid: %s" % vers.get("full-revisionid"))
> +@@ -1521,9 +1955,10 @@ def get_cmdclass():
> +             print(" date: %s" % vers.get("date"))
> +             if vers["error"]:
> +                 print(" error: %s" % vers["error"])
> ++
> +     cmds["version"] = cmd_version
> +
> +-    # we override "build_py" in both distutils and setuptools
> ++    # we override "build_py" in setuptools
> +     #
> +     # most invocation pathways end up running build_py:
> +     #  distutils/build -> build_py
> +@@ -1538,29 +1973,71 @@ def get_cmdclass():
> +     #   then does setup.py bdist_wheel, or sometimes setup.py install
> +     #  setup.py egg_info -> ?
> +
> ++    # pip install -e . and setuptool/editable_wheel will invoke build_py
> ++    # but the build_py command is not expected to copy any files.
> ++
> +     # we override different "build_py" commands for both environments
> +-    if "setuptools" in sys.modules:
> +-        from setuptools.command.build_py import build_py as _build_py
> ++    if "build_py" in cmds:
> ++        _build_py: Any = cmds["build_py"]
> +     else:
> +-        from distutils.command.build_py import build_py as _build_py
> ++        from setuptools.command.build_py import build_py as _build_py
> +
> +     class cmd_build_py(_build_py):
> +-        def run(self):
> ++        def run(self) -> None:
> +             root = get_root()
> +             cfg = get_config_from_root(root)
> +             versions = get_versions()
> +             _build_py.run(self)
> ++            if getattr(self, "editable_mode", False):
> ++                # During editable installs `.py` and data files are
> ++                # not copied to build_lib
> ++                return
> +             # now locate _version.py in the new build/ directory and replace
> +             # it with an updated value
> +             if cfg.versionfile_build:
> +-                target_versionfile = os.path.join(self.build_lib,
> +-                                                  cfg.versionfile_build)
> ++                target_versionfile = os.path.join(self.build_lib, cfg.versionfile_build)
> +                 print("UPDATING %s" % target_versionfile)
> +                 write_to_version_file(target_versionfile, versions)
> ++
> +     cmds["build_py"] = cmd_build_py
> +
> ++    if "build_ext" in cmds:
> ++        _build_ext: Any = cmds["build_ext"]
> ++    else:
> ++        from setuptools.command.build_ext import build_ext as _build_ext
> ++
> ++    class cmd_build_ext(_build_ext):
> ++        def run(self) -> None:
> ++            root = get_root()
> ++            cfg = get_config_from_root(root)
> ++            versions = get_versions()
> ++            _build_ext.run(self)
> ++            if self.inplace:
> ++                # build_ext --inplace will only build extensions in
> ++                # build/lib<..> dir with no _version.py to write to.
> ++                # As in place builds will already have a _version.py
> ++                # in the module dir, we do not need to write one.
> ++                return
> ++            # now locate _version.py in the new build/ directory and replace
> ++            # it with an updated value
> ++            if not cfg.versionfile_build:
> ++                return
> ++            target_versionfile = os.path.join(self.build_lib, cfg.versionfile_build)
> ++            if not os.path.exists(target_versionfile):
> ++                print(
> ++                    f"Warning: {target_versionfile} does not exist, skipping "
> ++                    "version update. This can happen if you are running build_ext "
> ++                    "without first running build_py."
> ++                )
> ++                return
> ++            print("UPDATING %s" % target_versionfile)
> ++            write_to_version_file(target_versionfile, versions)
> ++
> ++    cmds["build_ext"] = cmd_build_ext
> ++
> +     if "cx_Freeze" in sys.modules:  # cx_freeze enabled?
> +-        from cx_Freeze.dist import build_exe as _build_exe
> ++        from cx_Freeze.dist import build_exe as _build_exe  # type: ignore
> ++
> +         # nczeczulin reports that py2exe won't like the pep440-style string
> +         # as FILEVERSION, but it can be used for PRODUCTVERSION, e.g.
> +         # setup(console=[{
> +@@ -1569,7 +2046,7 @@ def get_cmdclass():
> +         #   ...
> +
> +         class cmd_build_exe(_build_exe):
> +-            def run(self):
> ++            def run(self) -> None:
> +                 root = get_root()
> +                 cfg = get_config_from_root(root)
> +                 versions = get_versions()
> +@@ -1581,24 +2058,28 @@ def get_cmdclass():
> +                 os.unlink(target_versionfile)
> +                 with open(cfg.versionfile_source, "w") as f:
> +                     LONG = LONG_VERSION_PY[cfg.VCS]
> +-                    f.write(LONG %
> +-                            {"DOLLAR": "$",
> +-                             "STYLE": cfg.style,
> +-                             "TAG_PREFIX": cfg.tag_prefix,
> +-                             "PARENTDIR_PREFIX": cfg.parentdir_prefix,
> +-                             "VERSIONFILE_SOURCE": cfg.versionfile_source,
> +-                             })
> ++                    f.write(
> ++                        LONG
> ++                        % {
> ++                            "DOLLAR": "$",
> ++                            "STYLE": cfg.style,
> ++                            "TAG_PREFIX": cfg.tag_prefix,
> ++                            "PARENTDIR_PREFIX": cfg.parentdir_prefix,
> ++                            "VERSIONFILE_SOURCE": cfg.versionfile_source,
> ++                        }
> ++                    )
> ++
> +         cmds["build_exe"] = cmd_build_exe
> +         del cmds["build_py"]
> +
> +-    if 'py2exe' in sys.modules:  # py2exe enabled?
> ++    if "py2exe" in sys.modules:  # py2exe enabled?
> +         try:
> +-            from py2exe.distutils_buildexe import py2exe as _py2exe  # py3
> ++            from py2exe.setuptools_buildexe import py2exe as _py2exe  # type: ignore
> +         except ImportError:
> +-            from py2exe.build_exe import py2exe as _py2exe  # py2
> ++            from py2exe.distutils_buildexe import py2exe as _py2exe  # type: ignore
> +
> +         class cmd_py2exe(_py2exe):
> +-            def run(self):
> ++            def run(self) -> None:
> +                 root = get_root()
> +                 cfg = get_config_from_root(root)
> +                 versions = get_versions()
> +@@ -1610,23 +2091,67 @@ def get_cmdclass():
> +                 os.unlink(target_versionfile)
> +                 with open(cfg.versionfile_source, "w") as f:
> +                     LONG = LONG_VERSION_PY[cfg.VCS]
> +-                    f.write(LONG %
> +-                            {"DOLLAR": "$",
> +-                             "STYLE": cfg.style,
> +-                             "TAG_PREFIX": cfg.tag_prefix,
> +-                             "PARENTDIR_PREFIX": cfg.parentdir_prefix,
> +-                             "VERSIONFILE_SOURCE": cfg.versionfile_source,
> +-                             })
> ++                    f.write(
> ++                        LONG
> ++                        % {
> ++                            "DOLLAR": "$",
> ++                            "STYLE": cfg.style,
> ++                            "TAG_PREFIX": cfg.tag_prefix,
> ++                            "PARENTDIR_PREFIX": cfg.parentdir_prefix,
> ++                            "VERSIONFILE_SOURCE": cfg.versionfile_source,
> ++                        }
> ++                    )
> ++
> +         cmds["py2exe"] = cmd_py2exe
> +
> ++    # sdist farms its file list building out to egg_info
> ++    if "egg_info" in cmds:
> ++        _egg_info: Any = cmds["egg_info"]
> ++    else:
> ++        from setuptools.command.egg_info import egg_info as _egg_info
> ++
> ++    class cmd_egg_info(_egg_info):
> ++        def find_sources(self) -> None:
> ++            # egg_info.find_sources builds the manifest list and writes it
> ++            # in one shot
> ++            super().find_sources()
> ++
> ++            # Modify the filelist and normalize it
> ++            root = get_root()
> ++            cfg = get_config_from_root(root)
> ++            self.filelist.append("versioneer.py")
> ++            if cfg.versionfile_source:
> ++                # There are rare cases where versionfile_source might not be
> ++                # included by default, so we must be explicit
> ++                self.filelist.append(cfg.versionfile_source)
> ++            self.filelist.sort()
> ++            self.filelist.remove_duplicates()
> ++
> ++            # The write method is hidden in the manifest_maker instance that
> ++            # generated the filelist and was thrown away
> ++            # We will instead replicate their final normalization (to unicode,
> ++            # and POSIX-style paths)
> ++            from setuptools import unicode_utils
> ++
> ++            normalized = [
> ++                unicode_utils.filesys_decode(f).replace(os.sep, "/")
> ++                for f in self.filelist.files
> ++            ]
> ++
> ++            manifest_filename = os.path.join(self.egg_info, "SOURCES.txt")
> ++            with open(manifest_filename, "w") as fobj:
> ++                fobj.write("\n".join(normalized))
> ++
> ++    cmds["egg_info"] = cmd_egg_info
> ++
> +     # we override different "sdist" commands for both environments
> +-    if "setuptools" in sys.modules:
> +-        from setuptools.command.sdist import sdist as _sdist
> ++    if "sdist" in cmds:
> ++        _sdist: Any = cmds["sdist"]
> +     else:
> +-        from distutils.command.sdist import sdist as _sdist
> ++        from setuptools.command.sdist import sdist as _sdist
> +
> +     class cmd_sdist(_sdist):
> +-        def run(self):
> ++        def run(self) -> None:
> +             versions = get_versions()
> +             self._versioneer_generated_versions = versions
> +             # unless we update this, the command will keep using the old
> +@@ -1634,7 +2159,7 @@ def get_cmdclass():
> +             self.distribution.metadata.version = versions["version"]
> +             return _sdist.run(self)
> +
> +-        def make_release_tree(self, base_dir, files):
> ++        def make_release_tree(self, base_dir: str, files: List[str]) -> None:
> +             root = get_root()
> +             cfg = get_config_from_root(root)
> +             _sdist.make_release_tree(self, base_dir, files)
> +@@ -1643,8 +2168,10 @@ def get_cmdclass():
> +             # updated value
> +             target_versionfile = os.path.join(base_dir, cfg.versionfile_source)
> +             print("UPDATING %s" % target_versionfile)
> +-            write_to_version_file(target_versionfile,
> +-                                  self._versioneer_generated_versions)
> ++            write_to_version_file(
> ++                target_versionfile, self._versioneer_generated_versions
> ++            )
> ++
> +     cmds["sdist"] = cmd_sdist
> +
> +     return cmds
> +@@ -1687,23 +2214,26 @@ SAMPLE_CONFIG = """
> +
> + """
> +
> +-INIT_PY_SNIPPET = """
> ++OLD_SNIPPET = """
> + from ._version import get_versions
> + __version__ = get_versions()['version']
> + del get_versions
> + """
> +
> ++INIT_PY_SNIPPET = """
> ++from . import {0}
> ++__version__ = {0}.get_versions()['version']
> ++"""
> ++
> +
> +-def do_setup():
> +-    """Main VCS-independent setup function for installing Versioneer."""
> ++def do_setup() -> int:
> ++    """Do main VCS-independent setup function for installing Versioneer."""
> +     root = get_root()
> +     try:
> +         cfg = get_config_from_root(root)
> +-    except (EnvironmentError, configparser.NoSectionError,
> +-            configparser.NoOptionError) as e:
> +-        if isinstance(e, (EnvironmentError, configparser.NoSectionError)):
> +-            print("Adding sample versioneer config to setup.cfg",
> +-                  file=sys.stderr)
> ++    except (OSError, configparser.NoSectionError, configparser.NoOptionError) as e:
> ++        if isinstance(e, (OSError, configparser.NoSectionError)):
> ++            print("Adding sample versioneer config to setup.cfg", file=sys.stderr)
> +             with open(os.path.join(root, "setup.cfg"), "a") as f:
> +                 f.write(SAMPLE_CONFIG)
> +         print(CONFIG_ERROR, file=sys.stderr)
> +@@ -1712,71 +2242,49 @@ def do_setup():
> +     print(" creating %s" % cfg.versionfile_source)
> +     with open(cfg.versionfile_source, "w") as f:
> +         LONG = LONG_VERSION_PY[cfg.VCS]
> +-        f.write(LONG % {"DOLLAR": "$",
> +-                        "STYLE": cfg.style,
> +-                        "TAG_PREFIX": cfg.tag_prefix,
> +-                        "PARENTDIR_PREFIX": cfg.parentdir_prefix,
> +-                        "VERSIONFILE_SOURCE": cfg.versionfile_source,
> +-                        })
> +-
> +-    ipy = os.path.join(os.path.dirname(cfg.versionfile_source),
> +-                       "__init__.py")
> ++        f.write(
> ++            LONG
> ++            % {
> ++                "DOLLAR": "$",
> ++                "STYLE": cfg.style,
> ++                "TAG_PREFIX": cfg.tag_prefix,
> ++                "PARENTDIR_PREFIX": cfg.parentdir_prefix,
> ++                "VERSIONFILE_SOURCE": cfg.versionfile_source,
> ++            }
> ++        )
> ++
> ++    ipy = os.path.join(os.path.dirname(cfg.versionfile_source), "__init__.py")
> ++    maybe_ipy: Optional[str] = ipy
> +     if os.path.exists(ipy):
> +         try:
> +             with open(ipy, "r") as f:
> +                 old = f.read()
> +-        except EnvironmentError:
> ++        except OSError:
> +             old = ""
> +-        if INIT_PY_SNIPPET not in old:
> ++        module = os.path.splitext(os.path.basename(cfg.versionfile_source))[0]
> ++        snippet = INIT_PY_SNIPPET.format(module)
> ++        if OLD_SNIPPET in old:
> ++            print(" replacing boilerplate in %s" % ipy)
> ++            with open(ipy, "w") as f:
> ++                f.write(old.replace(OLD_SNIPPET, snippet))
> ++        elif snippet not in old:
> +             print(" appending to %s" % ipy)
> +             with open(ipy, "a") as f:
> +-                f.write(INIT_PY_SNIPPET)
> ++                f.write(snippet)
> +         else:
> +             print(" %s unmodified" % ipy)
> +     else:
> +         print(" %s doesn't exist, ok" % ipy)
> +-        ipy = None
> +-
> +-    # Make sure both the top-level "versioneer.py" and versionfile_source
> +-    # (PKG/_version.py, used by runtime code) are in MANIFEST.in, so
> +-    # they'll be copied into source distributions. Pip won't be able to
> +-    # install the package without this.
> +-    manifest_in = os.path.join(root, "MANIFEST.in")
> +-    simple_includes = set()
> +-    try:
> +-        with open(manifest_in, "r") as f:
> +-            for line in f:
> +-                if line.startswith("include "):
> +-                    for include in line.split()[1:]:
> +-                        simple_includes.add(include)
> +-    except EnvironmentError:
> +-        pass
> +-    # That doesn't cover everything MANIFEST.in can do
> +-    # (http://docs.python.org/2/distutils/sourcedist.html#commands), so
> +-    # it might give some false negatives. Appending redundant 'include'
> +-    # lines is safe, though.
> +-    if "versioneer.py" not in simple_includes:
> +-        print(" appending 'versioneer.py' to MANIFEST.in")
> +-        with open(manifest_in, "a") as f:
> +-            f.write("include versioneer.py\n")
> +-    else:
> +-        print(" 'versioneer.py' already in MANIFEST.in")
> +-    if cfg.versionfile_source not in simple_includes:
> +-        print(" appending versionfile_source ('%s') to MANIFEST.in" %
> +-              cfg.versionfile_source)
> +-        with open(manifest_in, "a") as f:
> +-            f.write("include %s\n" % cfg.versionfile_source)
> +-    else:
> +-        print(" versionfile_source already in MANIFEST.in")
> ++        maybe_ipy = None
> +
> +     # Make VCS-specific changes. For git, this means creating/changing
> +     # .gitattributes to mark _version.py for export-subst keyword
> +     # substitution.
> +-    do_vcs_install(manifest_in, cfg.versionfile_source, ipy)
> ++    do_vcs_install(cfg.versionfile_source, maybe_ipy)
> +     return 0
> +
> +
> +-def scan_setup_py():
> ++def scan_setup_py() -> int:
> +     """Validate the contents of setup.py against Versioneer's expectations."""
> +     found = set()
> +     setters = False
> +@@ -1813,10 +2321,14 @@ def scan_setup_py():
> +     return errors
> +
> +
> ++def setup_command() -> NoReturn:
> ++    """Set up Versioneer and exit with appropriate error code."""
> ++    errors = do_setup()
> ++    errors += scan_setup_py()
> ++    sys.exit(1 if errors else 0)
> ++
> ++
> + if __name__ == "__main__":
> +     cmd = sys.argv[1]
> +     if cmd == "setup":
> +-        errors = do_setup()
> +-        errors += scan_setup_py()
> +-        if errors:
> +-            sys.exit(1)
> ++        setup_command()
> +--
> +2.41.0
> +

_______________________________________________
buildroot mailing list
buildroot@buildroot.org
https://lists.buildroot.org/mailman/listinfo/buildroot


^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: [Buildroot] [PATCH 23/30] package/python-magic-wormhole-transit-relay: update versioneer to 0.29
  2023-10-26  9:26 ` [Buildroot] [PATCH 23/30] package/python-magic-wormhole-transit-relay: " Adam Duskett
@ 2023-11-04 21:31   ` Arnout Vandecappelle via buildroot
  0 siblings, 0 replies; 54+ messages in thread
From: Arnout Vandecappelle via buildroot @ 2023-11-04 21:31 UTC (permalink / raw)
  To: Adam Duskett, buildroot
  Cc: Andrey Smirnov, James Hilliard, Asaf Kahlon, Julien Olivain,
	Thomas Petazzoni, Mauro Condarelli



On 26/10/2023 11:26, Adam Duskett wrote:
> Versioneer < 0.21 is incompatible with Python 3.12.0. Use the latest version
> which is 0.29 as of this commit.
> 
> Signed-off-by: Adam Duskett <adam.duskett@amarulasolutions.com>

  Applied to master, thanks.

  Regards,
  Arnout

> ---
>   .../0001-Update-versioneer-to-0.29.patch      | 2194 +++++++++++++++++
>   1 file changed, 2194 insertions(+)
>   create mode 100644 package/python-magic-wormhole-transit-relay/0001-Update-versioneer-to-0.29.patch
> 
> diff --git a/package/python-magic-wormhole-transit-relay/0001-Update-versioneer-to-0.29.patch b/package/python-magic-wormhole-transit-relay/0001-Update-versioneer-to-0.29.patch
> new file mode 100644
> index 0000000000..51cecd138a
> --- /dev/null
> +++ b/package/python-magic-wormhole-transit-relay/0001-Update-versioneer-to-0.29.patch
> @@ -0,0 +1,2194 @@
> +From f00f54ecbd9bea970795da4f1f6091828a011bcd Mon Sep 17 00:00:00 2001
> +From: Adam Duskett <adam.duskett@amarulasolutions.com>
> +Date: Tue, 24 Oct 2023 09:52:45 +0200
> +Subject: [PATCH] Update versioneer to 0.29
> +
> +Fixes builds against Python 3.12.0
> +
> +Upstream: https://github.com/magic-wormhole/magic-wormhole-transit-relay/pull/34
> +
> +Signed-off-by: Adam Duskett <adam.duskett@amarulasolutions.com>
> +---
> + versioneer.py | 1350 ++++++++++++++++++++++++++++++++++---------------
> + 1 file changed, 931 insertions(+), 419 deletions(-)
> +
> +diff --git a/versioneer.py b/versioneer.py
> +index 64fea1c..de97d90 100644
> +--- a/versioneer.py
> ++++ b/versioneer.py
> +@@ -1,5 +1,4 @@
> +-
> +-# Version: 0.18
> ++# Version: 0.29
> +
> + """The Versioneer - like a rocketeer, but for versions.
> +
> +@@ -7,18 +6,14 @@ The Versioneer
> + ==============
> +
> + * like a rocketeer, but for versions!
> +-* https://github.com/warner/python-versioneer
> ++* https://github.com/python-versioneer/python-versioneer
> + * Brian Warner
> +-* License: Public Domain
> +-* Compatible With: python2.6, 2.7, 3.2, 3.3, 3.4, 3.5, 3.6, and pypy
> +-* [![Latest Version]
> +-(https://pypip.in/version/versioneer/badge.svg?style=flat)
> +-](https://pypi.python.org/pypi/versioneer/)
> +-* [![Build Status]
> +-(https://travis-ci.org/warner/python-versioneer.png?branch=master)
> +-](https://travis-ci.org/warner/python-versioneer)
> +-
> +-This is a tool for managing a recorded version number in distutils-based
> ++* License: Public Domain (Unlicense)
> ++* Compatible with: Python 3.7, 3.8, 3.9, 3.10, 3.11 and pypy3
> ++* [![Latest Version][pypi-image]][pypi-url]
> ++* [![Build Status][travis-image]][travis-url]
> ++
> ++This is a tool for managing a recorded version number in setuptools-based
> + python projects. The goal is to remove the tedious and error-prone "update
> + the embedded version string" step from your release process. Making a new
> + release should be as easy as recording a new tag in your version-control
> +@@ -27,9 +22,38 @@ system, and maybe making new tarballs.
> +
> + ## Quick Install
> +
> +-* `pip install versioneer` to somewhere to your $PATH
> +-* add a `[versioneer]` section to your setup.cfg (see below)
> +-* run `versioneer install` in your source tree, commit the results
> ++Versioneer provides two installation modes. The "classic" vendored mode installs
> ++a copy of versioneer into your repository. The experimental build-time dependency mode
> ++is intended to allow you to skip this step and simplify the process of upgrading.
> ++
> ++### Vendored mode
> ++
> ++* `pip install versioneer` to somewhere in your $PATH
> ++   * A [conda-forge recipe](https://github.com/conda-forge/versioneer-feedstock) is
> ++     available, so you can also use `conda install -c conda-forge versioneer`
> ++* add a `[tool.versioneer]` section to your `pyproject.toml` or a
> ++  `[versioneer]` section to your `setup.cfg` (see [Install](INSTALL.md))
> ++   * Note that you will need to add `tomli; python_version < "3.11"` to your
> ++     build-time dependencies if you use `pyproject.toml`
> ++* run `versioneer install --vendor` in your source tree, commit the results
> ++* verify version information with `python setup.py version`
> ++
> ++### Build-time dependency mode
> ++
> ++* `pip install versioneer` to somewhere in your $PATH
> ++   * A [conda-forge recipe](https://github.com/conda-forge/versioneer-feedstock) is
> ++     available, so you can also use `conda install -c conda-forge versioneer`
> ++* add a `[tool.versioneer]` section to your `pyproject.toml` or a
> ++  `[versioneer]` section to your `setup.cfg` (see [Install](INSTALL.md))
> ++* add `versioneer` (with `[toml]` extra, if configuring in `pyproject.toml`)
> ++  to the `requires` key of the `build-system` table in `pyproject.toml`:
> ++  ```toml
> ++  [build-system]
> ++  requires = ["setuptools", "versioneer[toml]"]
> ++  build-backend = "setuptools.build_meta"
> ++  ```
> ++* run `versioneer install --no-vendor` in your source tree, commit the results
> ++* verify version information with `python setup.py version`
> +
> + ## Version Identifiers
> +
> +@@ -61,7 +85,7 @@ version 1.3). Many VCS systems can report a description that captures this,
> + for example `git describe --tags --dirty --always` reports things like
> + "0.7-1-g574ab98-dirty" to indicate that the checkout is one revision past the
> + 0.7 tag, has a unique revision id of "574ab98", and is "dirty" (it has
> +-uncommitted changes.
> ++uncommitted changes).
> +
> + The version identifier is used for multiple purposes:
> +
> +@@ -166,7 +190,7 @@ which may help identify what went wrong).
> +
> + Some situations are known to cause problems for Versioneer. This details the
> + most significant ones. More can be found on Github
> +-[issues page](https://github.com/warner/python-versioneer/issues).
> ++[issues page](https://github.com/python-versioneer/python-versioneer/issues).
> +
> + ### Subprojects
> +
> +@@ -180,7 +204,7 @@ two common reasons why `setup.py` might not be in the root:
> +   `setup.cfg`, and `tox.ini`. Projects like these produce multiple PyPI
> +   distributions (and upload multiple independently-installable tarballs).
> + * Source trees whose main purpose is to contain a C library, but which also
> +-  provide bindings to Python (and perhaps other langauges) in subdirectories.
> ++  provide bindings to Python (and perhaps other languages) in subdirectories.
> +
> + Versioneer will look for `.git` in parent directories, and most operations
> + should get the right version string. However `pip` and `setuptools` have bugs
> +@@ -194,9 +218,9 @@ work too.
> + Pip-8.1.1 is known to have this problem, but hopefully it will get fixed in
> + some later version.
> +
> +-[Bug #38](https://github.com/warner/python-versioneer/issues/38) is tracking
> ++[Bug #38](https://github.com/python-versioneer/python-versioneer/issues/38) is tracking
> + this issue. The discussion in
> +-[PR #61](https://github.com/warner/python-versioneer/pull/61) describes the
> ++[PR #61](https://github.com/python-versioneer/python-versioneer/pull/61) describes the
> + issue from the Versioneer side in more detail.
> + [pip PR#3176](https://github.com/pypa/pip/pull/3176) and
> + [pip PR#3615](https://github.com/pypa/pip/pull/3615) contain work to improve
> +@@ -224,31 +248,20 @@ regenerated while a different version is checked out. Many setup.py commands
> + cause egg_info to be rebuilt (including `sdist`, `wheel`, and installing into
> + a different virtualenv), so this can be surprising.
> +
> +-[Bug #83](https://github.com/warner/python-versioneer/issues/83) describes
> ++[Bug #83](https://github.com/python-versioneer/python-versioneer/issues/83) describes
> + this one, but upgrading to a newer version of setuptools should probably
> + resolve it.
> +
> +-### Unicode version strings
> +-
> +-While Versioneer works (and is continually tested) with both Python 2 and
> +-Python 3, it is not entirely consistent with bytes-vs-unicode distinctions.
> +-Newer releases probably generate unicode version strings on py2. It's not
> +-clear that this is wrong, but it may be surprising for applications when then
> +-write these strings to a network connection or include them in bytes-oriented
> +-APIs like cryptographic checksums.
> +-
> +-[Bug #71](https://github.com/warner/python-versioneer/issues/71) investigates
> +-this question.
> +-
> +
> + ## Updating Versioneer
> +
> + To upgrade your project to a new release of Versioneer, do the following:
> +
> + * install the new Versioneer (`pip install -U versioneer` or equivalent)
> +-* edit `setup.cfg`, if necessary, to include any new configuration settings
> +-  indicated by the release notes. See [UPGRADING](./UPGRADING.md) for details.
> +-* re-run `versioneer install` in your source tree, to replace
> ++* edit `setup.cfg` and `pyproject.toml`, if necessary,
> ++  to include any new configuration settings indicated by the release notes.
> ++  See [UPGRADING](./UPGRADING.md) for details.
> ++* re-run `versioneer install --[no-]vendor` in your source tree, to replace
> +   `SRC/_version.py`
> + * commit any changed files
> +
> +@@ -265,35 +278,70 @@ installation by editing setup.py . Alternatively, it might go the other
> + direction and include code from all supported VCS systems, reducing the
> + number of intermediate scripts.
> +
> ++## Similar projects
> ++
> ++* [setuptools_scm](https://github.com/pypa/setuptools_scm/) - a non-vendored build-time
> ++  dependency
> ++* [minver](https://github.com/jbweston/miniver) - a lightweight reimplementation of
> ++  versioneer
> ++* [versioningit](https://github.com/jwodder/versioningit) - a PEP 518-based setuptools
> ++  plugin
> +
> + ## License
> +
> + To make Versioneer easier to embed, all its code is dedicated to the public
> + domain. The `_version.py` that it creates is also in the public domain.
> +-Specifically, both are released under the Creative Commons "Public Domain
> +-Dedication" license (CC0-1.0), as described in
> +-https://creativecommons.org/publicdomain/zero/1.0/ .
> ++Specifically, both are released under the "Unlicense", as described in
> ++https://unlicense.org/.
> ++
> ++[pypi-image]: https://img.shields.io/pypi/v/versioneer.svg
> ++[pypi-url]: https://pypi.python.org/pypi/versioneer/
> ++[travis-image]:
> ++https://img.shields.io/travis/com/python-versioneer/python-versioneer.svg
> ++[travis-url]: https://travis-ci.com/github/python-versioneer/python-versioneer
> +
> + """
> ++# pylint:disable=invalid-name,import-outside-toplevel,missing-function-docstring
> ++# pylint:disable=missing-class-docstring,too-many-branches,too-many-statements
> ++# pylint:disable=raise-missing-from,too-many-lines,too-many-locals,import-error
> ++# pylint:disable=too-few-public-methods,redefined-outer-name,consider-using-with
> ++# pylint:disable=attribute-defined-outside-init,too-many-arguments
> +
> +-from __future__ import print_function
> +-try:
> +-    import configparser
> +-except ImportError:
> +-    import ConfigParser as configparser
> ++import configparser
> + import errno
> + import json
> + import os
> + import re
> + import subprocess
> + import sys
> ++from pathlib import Path
> ++from typing import Any, Callable, cast, Dict, List, Optional, Tuple, Union
> ++from typing import NoReturn
> ++import functools
> ++
> ++have_tomllib = True
> ++if sys.version_info >= (3, 11):
> ++    import tomllib
> ++else:
> ++    try:
> ++        import tomli as tomllib
> ++    except ImportError:
> ++        have_tomllib = False
> +
> +
> + class VersioneerConfig:
> +     """Container for Versioneer configuration parameters."""
> +
> ++    VCS: str
> ++    style: str
> ++    tag_prefix: str
> ++    versionfile_source: str
> ++    versionfile_build: Optional[str]
> ++    parentdir_prefix: Optional[str]
> ++    verbose: Optional[bool]
> ++
> +
> +-def get_root():
> ++def get_root() -> str:
> +     """Get the project root directory.
> +
> +     We require that all commands are run from the project root, i.e. the
> +@@ -301,18 +349,30 @@ def get_root():
> +     """
> +     root = os.path.realpath(os.path.abspath(os.getcwd()))
> +     setup_py = os.path.join(root, "setup.py")
> ++    pyproject_toml = os.path.join(root, "pyproject.toml")
> +     versioneer_py = os.path.join(root, "versioneer.py")
> +-    if not (os.path.exists(setup_py) or os.path.exists(versioneer_py)):
> ++    if not (
> ++        os.path.exists(setup_py)
> ++        or os.path.exists(pyproject_toml)
> ++        or os.path.exists(versioneer_py)
> ++    ):
> +         # allow 'python path/to/setup.py COMMAND'
> +         root = os.path.dirname(os.path.realpath(os.path.abspath(sys.argv[0])))
> +         setup_py = os.path.join(root, "setup.py")
> ++        pyproject_toml = os.path.join(root, "pyproject.toml")
> +         versioneer_py = os.path.join(root, "versioneer.py")
> +-    if not (os.path.exists(setup_py) or os.path.exists(versioneer_py)):
> +-        err = ("Versioneer was unable to run the project root directory. "
> +-               "Versioneer requires setup.py to be executed from "
> +-               "its immediate directory (like 'python setup.py COMMAND'), "
> +-               "or in a way that lets it use sys.argv[0] to find the root "
> +-               "(like 'python path/to/setup.py COMMAND').")
> ++    if not (
> ++        os.path.exists(setup_py)
> ++        or os.path.exists(pyproject_toml)
> ++        or os.path.exists(versioneer_py)
> ++    ):
> ++        err = (
> ++            "Versioneer was unable to run the project root directory. "
> ++            "Versioneer requires setup.py to be executed from "
> ++            "its immediate directory (like 'python setup.py COMMAND'), "
> ++            "or in a way that lets it use sys.argv[0] to find the root "
> ++            "(like 'python path/to/setup.py COMMAND')."
> ++        )
> +         raise VersioneerBadRootError(err)
> +     try:
> +         # Certain runtime workflows (setup.py install/develop in a setuptools
> +@@ -321,43 +381,64 @@ def get_root():
> +         # module-import table will cache the first one. So we can't use
> +         # os.path.dirname(__file__), as that will find whichever
> +         # versioneer.py was first imported, even in later projects.
> +-        me = os.path.realpath(os.path.abspath(__file__))
> +-        me_dir = os.path.normcase(os.path.splitext(me)[0])
> ++        my_path = os.path.realpath(os.path.abspath(__file__))
> ++        me_dir = os.path.normcase(os.path.splitext(my_path)[0])
> +         vsr_dir = os.path.normcase(os.path.splitext(versioneer_py)[0])
> +-        if me_dir != vsr_dir:
> +-            print("Warning: build in %s is using versioneer.py from %s"
> +-                  % (os.path.dirname(me), versioneer_py))
> ++        if me_dir != vsr_dir and "VERSIONEER_PEP518" not in globals():
> ++            print(
> ++                "Warning: build in %s is using versioneer.py from %s"
> ++                % (os.path.dirname(my_path), versioneer_py)
> ++            )
> +     except NameError:
> +         pass
> +     return root
> +
> +
> +-def get_config_from_root(root):
> ++def get_config_from_root(root: str) -> VersioneerConfig:
> +     """Read the project setup.cfg file to determine Versioneer config."""
> +-    # This might raise EnvironmentError (if setup.cfg is missing), or
> ++    # This might raise OSError (if setup.cfg is missing), or
> +     # configparser.NoSectionError (if it lacks a [versioneer] section), or
> +     # configparser.NoOptionError (if it lacks "VCS="). See the docstring at
> +     # the top of versioneer.py for instructions on writing your setup.cfg .
> +-    setup_cfg = os.path.join(root, "setup.cfg")
> +-    parser = configparser.SafeConfigParser()
> +-    with open(setup_cfg, "r") as f:
> +-        parser.readfp(f)
> +-    VCS = parser.get("versioneer", "VCS")  # mandatory
> +-
> +-    def get(parser, name):
> +-        if parser.has_option("versioneer", name):
> +-            return parser.get("versioneer", name)
> +-        return None
> ++    root_pth = Path(root)
> ++    pyproject_toml = root_pth / "pyproject.toml"
> ++    setup_cfg = root_pth / "setup.cfg"
> ++    section: Union[Dict[str, Any], configparser.SectionProxy, None] = None
> ++    if pyproject_toml.exists() and have_tomllib:
> ++        try:
> ++            with open(pyproject_toml, "rb") as fobj:
> ++                pp = tomllib.load(fobj)
> ++            section = pp["tool"]["versioneer"]
> ++        except (tomllib.TOMLDecodeError, KeyError) as e:
> ++            print(f"Failed to load config from {pyproject_toml}: {e}")
> ++            print("Try to load it from setup.cfg")
> ++    if not section:
> ++        parser = configparser.ConfigParser()
> ++        with open(setup_cfg) as cfg_file:
> ++            parser.read_file(cfg_file)
> ++        parser.get("versioneer", "VCS")  # raise error if missing
> ++
> ++        section = parser["versioneer"]
> ++
> ++    # `cast`` really shouldn't be used, but its simplest for the
> ++    # common VersioneerConfig users at the moment. We verify against
> ++    # `None` values elsewhere where it matters
> ++
> +     cfg = VersioneerConfig()
> +-    cfg.VCS = VCS
> +-    cfg.style = get(parser, "style") or ""
> +-    cfg.versionfile_source = get(parser, "versionfile_source")
> +-    cfg.versionfile_build = get(parser, "versionfile_build")
> +-    cfg.tag_prefix = get(parser, "tag_prefix")
> +-    if cfg.tag_prefix in ("''", '""'):
> ++    cfg.VCS = section["VCS"]
> ++    cfg.style = section.get("style", "")
> ++    cfg.versionfile_source = cast(str, section.get("versionfile_source"))
> ++    cfg.versionfile_build = section.get("versionfile_build")
> ++    cfg.tag_prefix = cast(str, section.get("tag_prefix"))
> ++    if cfg.tag_prefix in ("''", '""', None):
> +         cfg.tag_prefix = ""
> +-    cfg.parentdir_prefix = get(parser, "parentdir_prefix")
> +-    cfg.verbose = get(parser, "verbose")
> ++    cfg.parentdir_prefix = section.get("parentdir_prefix")
> ++    if isinstance(section, configparser.SectionProxy):
> ++        # Make sure configparser translates to bool
> ++        cfg.verbose = section.getboolean("verbose")
> ++    else:
> ++        cfg.verbose = section.get("verbose")
> ++
> +     return cfg
> +
> +
> +@@ -366,37 +447,54 @@ class NotThisMethod(Exception):
> +
> +
> + # these dictionaries contain VCS-specific tools
> +-LONG_VERSION_PY = {}
> +-HANDLERS = {}
> ++LONG_VERSION_PY: Dict[str, str] = {}
> ++HANDLERS: Dict[str, Dict[str, Callable]] = {}
> +
> +
> +-def register_vcs_handler(vcs, method):  # decorator
> +-    """Decorator to mark a method as the handler for a particular VCS."""
> +-    def decorate(f):
> ++def register_vcs_handler(vcs: str, method: str) -> Callable:  # decorator
> ++    """Create decorator to mark a method as the handler of a VCS."""
> ++
> ++    def decorate(f: Callable) -> Callable:
> +         """Store f in HANDLERS[vcs][method]."""
> +-        if vcs not in HANDLERS:
> +-            HANDLERS[vcs] = {}
> +-        HANDLERS[vcs][method] = f
> ++        HANDLERS.setdefault(vcs, {})[method] = f
> +         return f
> ++
> +     return decorate
> +
> +
> +-def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False,
> +-                env=None):
> ++def run_command(
> ++    commands: List[str],
> ++    args: List[str],
> ++    cwd: Optional[str] = None,
> ++    verbose: bool = False,
> ++    hide_stderr: bool = False,
> ++    env: Optional[Dict[str, str]] = None,
> ++) -> Tuple[Optional[str], Optional[int]]:
> +     """Call the given command(s)."""
> +     assert isinstance(commands, list)
> +-    p = None
> +-    for c in commands:
> ++    process = None
> ++
> ++    popen_kwargs: Dict[str, Any] = {}
> ++    if sys.platform == "win32":
> ++        # This hides the console window if pythonw.exe is used
> ++        startupinfo = subprocess.STARTUPINFO()
> ++        startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
> ++        popen_kwargs["startupinfo"] = startupinfo
> ++
> ++    for command in commands:
> +         try:
> +-            dispcmd = str([c] + args)
> ++            dispcmd = str([command] + args)
> +             # remember shell=False, so use git.cmd on windows, not just git
> +-            p = subprocess.Popen([c] + args, cwd=cwd, env=env,
> +-                                 stdout=subprocess.PIPE,
> +-                                 stderr=(subprocess.PIPE if hide_stderr
> +-                                         else None))
> ++            process = subprocess.Popen(
> ++                [command] + args,
> ++                cwd=cwd,
> ++                env=env,
> ++                stdout=subprocess.PIPE,
> ++                stderr=(subprocess.PIPE if hide_stderr else None),
> ++                **popen_kwargs,
> ++            )
> +             break
> +-        except EnvironmentError:
> +-            e = sys.exc_info()[1]
> ++        except OSError as e:
> +             if e.errno == errno.ENOENT:
> +                 continue
> +             if verbose:
> +@@ -407,26 +505,27 @@ def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False,
> +         if verbose:
> +             print("unable to find command, tried %s" % (commands,))
> +         return None, None
> +-    stdout = p.communicate()[0].strip()
> +-    if sys.version_info[0] >= 3:
> +-        stdout = stdout.decode()
> +-    if p.returncode != 0:
> ++    stdout = process.communicate()[0].strip().decode()
> ++    if process.returncode != 0:
> +         if verbose:
> +             print("unable to run %s (error)" % dispcmd)
> +             print("stdout was %s" % stdout)
> +-        return None, p.returncode
> +-    return stdout, p.returncode
> ++        return None, process.returncode
> ++    return stdout, process.returncode
> +
> +
> +-LONG_VERSION_PY['git'] = '''
> ++LONG_VERSION_PY[
> ++    "git"
> ++] = r'''
> + # This file helps to compute a version number in source trees obtained from
> + # git-archive tarball (such as those provided by githubs download-from-tag
> + # feature). Distribution tarballs (built by setup.py sdist) and build
> + # directories (produced by setup.py build) will contain a much shorter file
> + # that just contains the computed version number.
> +
> +-# This file is released into the public domain. Generated by
> +-# versioneer-0.18 (https://github.com/warner/python-versioneer)
> ++# This file is released into the public domain.
> ++# Generated by versioneer-0.29
> ++# https://github.com/python-versioneer/python-versioneer
> +
> + """Git implementation of _version.py."""
> +
> +@@ -435,9 +534,11 @@ import os
> + import re
> + import subprocess
> + import sys
> ++from typing import Any, Callable, Dict, List, Optional, Tuple
> ++import functools
> +
> +
> +-def get_keywords():
> ++def get_keywords() -> Dict[str, str]:
> +     """Get the keywords needed to look up the version information."""
> +     # these strings will be replaced by git during git-archive.
> +     # setup.py/versioneer.py will grep for the variable names, so they must
> +@@ -453,8 +554,15 @@ def get_keywords():
> + class VersioneerConfig:
> +     """Container for Versioneer configuration parameters."""
> +
> ++    VCS: str
> ++    style: str
> ++    tag_prefix: str
> ++    parentdir_prefix: str
> ++    versionfile_source: str
> ++    verbose: bool
> ++
> +
> +-def get_config():
> ++def get_config() -> VersioneerConfig:
> +     """Create, populate and return the VersioneerConfig() object."""
> +     # these strings are filled in when 'setup.py versioneer' creates
> +     # _version.py
> +@@ -472,13 +580,13 @@ class NotThisMethod(Exception):
> +     """Exception raised if a method is not valid for the current scenario."""
> +
> +
> +-LONG_VERSION_PY = {}
> +-HANDLERS = {}
> ++LONG_VERSION_PY: Dict[str, str] = {}
> ++HANDLERS: Dict[str, Dict[str, Callable]] = {}
> +
> +
> +-def register_vcs_handler(vcs, method):  # decorator
> +-    """Decorator to mark a method as the handler for a particular VCS."""
> +-    def decorate(f):
> ++def register_vcs_handler(vcs: str, method: str) -> Callable:  # decorator
> ++    """Create decorator to mark a method as the handler of a VCS."""
> ++    def decorate(f: Callable) -> Callable:
> +         """Store f in HANDLERS[vcs][method]."""
> +         if vcs not in HANDLERS:
> +             HANDLERS[vcs] = {}
> +@@ -487,22 +595,35 @@ def register_vcs_handler(vcs, method):  # decorator
> +     return decorate
> +
> +
> +-def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False,
> +-                env=None):
> ++def run_command(
> ++    commands: List[str],
> ++    args: List[str],
> ++    cwd: Optional[str] = None,
> ++    verbose: bool = False,
> ++    hide_stderr: bool = False,
> ++    env: Optional[Dict[str, str]] = None,
> ++) -> Tuple[Optional[str], Optional[int]]:
> +     """Call the given command(s)."""
> +     assert isinstance(commands, list)
> +-    p = None
> +-    for c in commands:
> ++    process = None
> ++
> ++    popen_kwargs: Dict[str, Any] = {}
> ++    if sys.platform == "win32":
> ++        # This hides the console window if pythonw.exe is used
> ++        startupinfo = subprocess.STARTUPINFO()
> ++        startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
> ++        popen_kwargs["startupinfo"] = startupinfo
> ++
> ++    for command in commands:
> +         try:
> +-            dispcmd = str([c] + args)
> ++            dispcmd = str([command] + args)
> +             # remember shell=False, so use git.cmd on windows, not just git
> +-            p = subprocess.Popen([c] + args, cwd=cwd, env=env,
> +-                                 stdout=subprocess.PIPE,
> +-                                 stderr=(subprocess.PIPE if hide_stderr
> +-                                         else None))
> ++            process = subprocess.Popen([command] + args, cwd=cwd, env=env,
> ++                                       stdout=subprocess.PIPE,
> ++                                       stderr=(subprocess.PIPE if hide_stderr
> ++                                               else None), **popen_kwargs)
> +             break
> +-        except EnvironmentError:
> +-            e = sys.exc_info()[1]
> ++        except OSError as e:
> +             if e.errno == errno.ENOENT:
> +                 continue
> +             if verbose:
> +@@ -513,18 +634,20 @@ def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False,
> +         if verbose:
> +             print("unable to find command, tried %%s" %% (commands,))
> +         return None, None
> +-    stdout = p.communicate()[0].strip()
> +-    if sys.version_info[0] >= 3:
> +-        stdout = stdout.decode()
> +-    if p.returncode != 0:
> ++    stdout = process.communicate()[0].strip().decode()
> ++    if process.returncode != 0:
> +         if verbose:
> +             print("unable to run %%s (error)" %% dispcmd)
> +             print("stdout was %%s" %% stdout)
> +-        return None, p.returncode
> +-    return stdout, p.returncode
> ++        return None, process.returncode
> ++    return stdout, process.returncode
> +
> +
> +-def versions_from_parentdir(parentdir_prefix, root, verbose):
> ++def versions_from_parentdir(
> ++    parentdir_prefix: str,
> ++    root: str,
> ++    verbose: bool,
> ++) -> Dict[str, Any]:
> +     """Try to determine the version from the parent directory name.
> +
> +     Source tarballs conventionally unpack into a directory that includes both
> +@@ -533,15 +656,14 @@ def versions_from_parentdir(parentdir_prefix, root, verbose):
> +     """
> +     rootdirs = []
> +
> +-    for i in range(3):
> ++    for _ in range(3):
> +         dirname = os.path.basename(root)
> +         if dirname.startswith(parentdir_prefix):
> +             return {"version": dirname[len(parentdir_prefix):],
> +                     "full-revisionid": None,
> +                     "dirty": False, "error": None, "date": None}
> +-        else:
> +-            rootdirs.append(root)
> +-            root = os.path.dirname(root)  # up a level
> ++        rootdirs.append(root)
> ++        root = os.path.dirname(root)  # up a level
> +
> +     if verbose:
> +         print("Tried directories %%s but none started with prefix %%s" %%
> +@@ -550,41 +672,48 @@ def versions_from_parentdir(parentdir_prefix, root, verbose):
> +
> +
> + @register_vcs_handler("git", "get_keywords")
> +-def git_get_keywords(versionfile_abs):
> ++def git_get_keywords(versionfile_abs: str) -> Dict[str, str]:
> +     """Extract version information from the given file."""
> +     # the code embedded in _version.py can just fetch the value of these
> +     # keywords. When used from setup.py, we don't want to import _version.py,
> +     # so we do it with a regexp instead. This function is not used from
> +     # _version.py.
> +-    keywords = {}
> ++    keywords: Dict[str, str] = {}
> +     try:
> +-        f = open(versionfile_abs, "r")
> +-        for line in f.readlines():
> +-            if line.strip().startswith("git_refnames ="):
> +-                mo = re.search(r'=\s*"(.*)"', line)
> +-                if mo:
> +-                    keywords["refnames"] = mo.group(1)
> +-            if line.strip().startswith("git_full ="):
> +-                mo = re.search(r'=\s*"(.*)"', line)
> +-                if mo:
> +-                    keywords["full"] = mo.group(1)
> +-            if line.strip().startswith("git_date ="):
> +-                mo = re.search(r'=\s*"(.*)"', line)
> +-                if mo:
> +-                    keywords["date"] = mo.group(1)
> +-        f.close()
> +-    except EnvironmentError:
> ++        with open(versionfile_abs, "r") as fobj:
> ++            for line in fobj:
> ++                if line.strip().startswith("git_refnames ="):
> ++                    mo = re.search(r'=\s*"(.*)"', line)
> ++                    if mo:
> ++                        keywords["refnames"] = mo.group(1)
> ++                if line.strip().startswith("git_full ="):
> ++                    mo = re.search(r'=\s*"(.*)"', line)
> ++                    if mo:
> ++                        keywords["full"] = mo.group(1)
> ++                if line.strip().startswith("git_date ="):
> ++                    mo = re.search(r'=\s*"(.*)"', line)
> ++                    if mo:
> ++                        keywords["date"] = mo.group(1)
> ++    except OSError:
> +         pass
> +     return keywords
> +
> +
> + @register_vcs_handler("git", "keywords")
> +-def git_versions_from_keywords(keywords, tag_prefix, verbose):
> ++def git_versions_from_keywords(
> ++    keywords: Dict[str, str],
> ++    tag_prefix: str,
> ++    verbose: bool,
> ++) -> Dict[str, Any]:
> +     """Get version information from git keywords."""
> +-    if not keywords:
> +-        raise NotThisMethod("no keywords at all, weird")
> ++    if "refnames" not in keywords:
> ++        raise NotThisMethod("Short version file found")
> +     date = keywords.get("date")
> +     if date is not None:
> ++        # Use only the last line.  Previous lines may contain GPG signature
> ++        # information.
> ++        date = date.splitlines()[-1]
> ++
> +         # git-2.2.0 added "%%cI", which expands to an ISO-8601 -compliant
> +         # datestamp. However we prefer "%%ci" (which expands to an "ISO-8601
> +         # -like" string, which we must then edit to make compliant), because
> +@@ -597,11 +726,11 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose):
> +         if verbose:
> +             print("keywords are unexpanded, not using")
> +         raise NotThisMethod("unexpanded keywords, not a git-archive tarball")
> +-    refs = set([r.strip() for r in refnames.strip("()").split(",")])
> ++    refs = {r.strip() for r in refnames.strip("()").split(",")}
> +     # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of
> +     # just "foo-1.0". If we see a "tag: " prefix, prefer those.
> +     TAG = "tag: "
> +-    tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)])
> ++    tags = {r[len(TAG):] for r in refs if r.startswith(TAG)}
> +     if not tags:
> +         # Either we're using git < 1.8.3, or there really are no tags. We use
> +         # a heuristic: assume all version tags have a digit. The old git %%d
> +@@ -610,7 +739,7 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose):
> +         # between branches and tags. By ignoring refnames without digits, we
> +         # filter out many common branch names like "release" and
> +         # "stabilization", as well as "HEAD" and "master".
> +-        tags = set([r for r in refs if re.search(r'\d', r)])
> ++        tags = {r for r in refs if re.search(r'\d', r)}
> +         if verbose:
> +             print("discarding '%%s', no digits" %% ",".join(refs - tags))
> +     if verbose:
> +@@ -619,6 +748,11 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose):
> +         # sorting will prefer e.g. "2.0" over "2.0rc1"
> +         if ref.startswith(tag_prefix):
> +             r = ref[len(tag_prefix):]
> ++            # Filter out refs that exactly match prefix or that don't start
> ++            # with a number once the prefix is stripped (mostly a concern
> ++            # when prefix is '')
> ++            if not re.match(r'\d', r):
> ++                continue
> +             if verbose:
> +                 print("picking %%s" %% r)
> +             return {"version": r,
> +@@ -634,7 +768,12 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose):
> +
> +
> + @register_vcs_handler("git", "pieces_from_vcs")
> +-def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
> ++def git_pieces_from_vcs(
> ++    tag_prefix: str,
> ++    root: str,
> ++    verbose: bool,
> ++    runner: Callable = run_command
> ++) -> Dict[str, Any]:
> +     """Get version from 'git describe' in the root of the source tree.
> +
> +     This only gets called if the git-archive 'subst' keywords were *not*
> +@@ -645,8 +784,15 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
> +     if sys.platform == "win32":
> +         GITS = ["git.cmd", "git.exe"]
> +
> +-    out, rc = run_command(GITS, ["rev-parse", "--git-dir"], cwd=root,
> +-                          hide_stderr=True)
> ++    # GIT_DIR can interfere with correct operation of Versioneer.
> ++    # It may be intended to be passed to the Versioneer-versioned project,
> ++    # but that should not change where we get our version from.
> ++    env = os.environ.copy()
> ++    env.pop("GIT_DIR", None)
> ++    runner = functools.partial(runner, env=env)
> ++
> ++    _, rc = runner(GITS, ["rev-parse", "--git-dir"], cwd=root,
> ++                   hide_stderr=not verbose)
> +     if rc != 0:
> +         if verbose:
> +             print("Directory %%s not under git control" %% root)
> +@@ -654,24 +800,57 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
> +
> +     # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty]
> +     # if there isn't one, this yields HEX[-dirty] (no NUM)
> +-    describe_out, rc = run_command(GITS, ["describe", "--tags", "--dirty",
> +-                                          "--always", "--long",
> +-                                          "--match", "%%s*" %% tag_prefix],
> +-                                   cwd=root)
> ++    describe_out, rc = runner(GITS, [
> ++        "describe", "--tags", "--dirty", "--always", "--long",
> ++        "--match", f"{tag_prefix}[[:digit:]]*"
> ++    ], cwd=root)
> +     # --long was added in git-1.5.5
> +     if describe_out is None:
> +         raise NotThisMethod("'git describe' failed")
> +     describe_out = describe_out.strip()
> +-    full_out, rc = run_command(GITS, ["rev-parse", "HEAD"], cwd=root)
> ++    full_out, rc = runner(GITS, ["rev-parse", "HEAD"], cwd=root)
> +     if full_out is None:
> +         raise NotThisMethod("'git rev-parse' failed")
> +     full_out = full_out.strip()
> +
> +-    pieces = {}
> ++    pieces: Dict[str, Any] = {}
> +     pieces["long"] = full_out
> +     pieces["short"] = full_out[:7]  # maybe improved later
> +     pieces["error"] = None
> +
> ++    branch_name, rc = runner(GITS, ["rev-parse", "--abbrev-ref", "HEAD"],
> ++                             cwd=root)
> ++    # --abbrev-ref was added in git-1.6.3
> ++    if rc != 0 or branch_name is None:
> ++        raise NotThisMethod("'git rev-parse --abbrev-ref' returned error")
> ++    branch_name = branch_name.strip()
> ++
> ++    if branch_name == "HEAD":
> ++        # If we aren't exactly on a branch, pick a branch which represents
> ++        # the current commit. If all else fails, we are on a branchless
> ++        # commit.
> ++        branches, rc = runner(GITS, ["branch", "--contains"], cwd=root)
> ++        # --contains was added in git-1.5.4
> ++        if rc != 0 or branches is None:
> ++            raise NotThisMethod("'git branch --contains' returned error")
> ++        branches = branches.split("\n")
> ++
> ++        # Remove the first line if we're running detached
> ++        if "(" in branches[0]:
> ++            branches.pop(0)
> ++
> ++        # Strip off the leading "* " from the list of branches.
> ++        branches = [branch[2:] for branch in branches]
> ++        if "master" in branches:
> ++            branch_name = "master"
> ++        elif not branches:
> ++            branch_name = None
> ++        else:
> ++            # Pick the first branch that is returned. Good or bad.
> ++            branch_name = branches[0]
> ++
> ++    pieces["branch"] = branch_name
> ++
> +     # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty]
> +     # TAG might have hyphens.
> +     git_describe = describe_out
> +@@ -688,7 +867,7 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
> +         # TAG-NUM-gHEX
> +         mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe)
> +         if not mo:
> +-            # unparseable. Maybe git-describe is misbehaving?
> ++            # unparsable. Maybe git-describe is misbehaving?
> +             pieces["error"] = ("unable to parse git-describe output: '%%s'"
> +                                %% describe_out)
> +             return pieces
> +@@ -713,26 +892,27 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
> +     else:
> +         # HEX: no tags
> +         pieces["closest-tag"] = None
> +-        count_out, rc = run_command(GITS, ["rev-list", "HEAD", "--count"],
> +-                                    cwd=root)
> +-        pieces["distance"] = int(count_out)  # total number of commits
> ++        out, rc = runner(GITS, ["rev-list", "HEAD", "--left-right"], cwd=root)
> ++        pieces["distance"] = len(out.split())  # total number of commits
> +
> +     # commit date: see ISO-8601 comment in git_versions_from_keywords()
> +-    date = run_command(GITS, ["show", "-s", "--format=%%ci", "HEAD"],
> +-                       cwd=root)[0].strip()
> ++    date = runner(GITS, ["show", "-s", "--format=%%ci", "HEAD"], cwd=root)[0].strip()
> ++    # Use only the last line.  Previous lines may contain GPG signature
> ++    # information.
> ++    date = date.splitlines()[-1]
> +     pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1)
> +
> +     return pieces
> +
> +
> +-def plus_or_dot(pieces):
> ++def plus_or_dot(pieces: Dict[str, Any]) -> str:
> +     """Return a + if we don't already have one, else return a ."""
> +     if "+" in pieces.get("closest-tag", ""):
> +         return "."
> +     return "+"
> +
> +
> +-def render_pep440(pieces):
> ++def render_pep440(pieces: Dict[str, Any]) -> str:
> +     """Build up version string, with post-release "local version identifier".
> +
> +     Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you
> +@@ -757,23 +937,71 @@ def render_pep440(pieces):
> +     return rendered
> +
> +
> +-def render_pep440_pre(pieces):
> +-    """TAG[.post.devDISTANCE] -- No -dirty.
> ++def render_pep440_branch(pieces: Dict[str, Any]) -> str:
> ++    """TAG[[.dev0]+DISTANCE.gHEX[.dirty]] .
> ++
> ++    The ".dev0" means not master branch. Note that .dev0 sorts backwards
> ++    (a feature branch will appear "older" than the master branch).
> +
> +     Exceptions:
> +-    1: no tags. 0.post.devDISTANCE
> ++    1: no tags. 0[.dev0]+untagged.DISTANCE.gHEX[.dirty]
> +     """
> +     if pieces["closest-tag"]:
> +         rendered = pieces["closest-tag"]
> ++        if pieces["distance"] or pieces["dirty"]:
> ++            if pieces["branch"] != "master":
> ++                rendered += ".dev0"
> ++            rendered += plus_or_dot(pieces)
> ++            rendered += "%%d.g%%s" %% (pieces["distance"], pieces["short"])
> ++            if pieces["dirty"]:
> ++                rendered += ".dirty"
> ++    else:
> ++        # exception #1
> ++        rendered = "0"
> ++        if pieces["branch"] != "master":
> ++            rendered += ".dev0"
> ++        rendered += "+untagged.%%d.g%%s" %% (pieces["distance"],
> ++                                          pieces["short"])
> ++        if pieces["dirty"]:
> ++            rendered += ".dirty"
> ++    return rendered
> ++
> ++
> ++def pep440_split_post(ver: str) -> Tuple[str, Optional[int]]:
> ++    """Split pep440 version string at the post-release segment.
> ++
> ++    Returns the release segments before the post-release and the
> ++    post-release version number (or -1 if no post-release segment is present).
> ++    """
> ++    vc = str.split(ver, ".post")
> ++    return vc[0], int(vc[1] or 0) if len(vc) == 2 else None
> ++
> ++
> ++def render_pep440_pre(pieces: Dict[str, Any]) -> str:
> ++    """TAG[.postN.devDISTANCE] -- No -dirty.
> ++
> ++    Exceptions:
> ++    1: no tags. 0.post0.devDISTANCE
> ++    """
> ++    if pieces["closest-tag"]:
> +         if pieces["distance"]:
> +-            rendered += ".post.dev%%d" %% pieces["distance"]
> ++            # update the post release segment
> ++            tag_version, post_version = pep440_split_post(pieces["closest-tag"])
> ++            rendered = tag_version
> ++            if post_version is not None:
> ++                rendered += ".post%%d.dev%%d" %% (post_version + 1, pieces["distance"])
> ++            else:
> ++                rendered += ".post0.dev%%d" %% (pieces["distance"])
> ++        else:
> ++            # no commits, use the tag as the version
> ++            rendered = pieces["closest-tag"]
> +     else:
> +         # exception #1
> +-        rendered = "0.post.dev%%d" %% pieces["distance"]
> ++        rendered = "0.post0.dev%%d" %% pieces["distance"]
> +     return rendered
> +
> +
> +-def render_pep440_post(pieces):
> ++def render_pep440_post(pieces: Dict[str, Any]) -> str:
> +     """TAG[.postDISTANCE[.dev0]+gHEX] .
> +
> +     The ".dev0" means dirty. Note that .dev0 sorts backwards
> +@@ -800,12 +1028,41 @@ def render_pep440_post(pieces):
> +     return rendered
> +
> +
> +-def render_pep440_old(pieces):
> ++def render_pep440_post_branch(pieces: Dict[str, Any]) -> str:
> ++    """TAG[.postDISTANCE[.dev0]+gHEX[.dirty]] .
> ++
> ++    The ".dev0" means not master branch.
> ++
> ++    Exceptions:
> ++    1: no tags. 0.postDISTANCE[.dev0]+gHEX[.dirty]
> ++    """
> ++    if pieces["closest-tag"]:
> ++        rendered = pieces["closest-tag"]
> ++        if pieces["distance"] or pieces["dirty"]:
> ++            rendered += ".post%%d" %% pieces["distance"]
> ++            if pieces["branch"] != "master":
> ++                rendered += ".dev0"
> ++            rendered += plus_or_dot(pieces)
> ++            rendered += "g%%s" %% pieces["short"]
> ++            if pieces["dirty"]:
> ++                rendered += ".dirty"
> ++    else:
> ++        # exception #1
> ++        rendered = "0.post%%d" %% pieces["distance"]
> ++        if pieces["branch"] != "master":
> ++            rendered += ".dev0"
> ++        rendered += "+g%%s" %% pieces["short"]
> ++        if pieces["dirty"]:
> ++            rendered += ".dirty"
> ++    return rendered
> ++
> ++
> ++def render_pep440_old(pieces: Dict[str, Any]) -> str:
> +     """TAG[.postDISTANCE[.dev0]] .
> +
> +     The ".dev0" means dirty.
> +
> +-    Eexceptions:
> ++    Exceptions:
> +     1: no tags. 0.postDISTANCE[.dev0]
> +     """
> +     if pieces["closest-tag"]:
> +@@ -822,7 +1079,7 @@ def render_pep440_old(pieces):
> +     return rendered
> +
> +
> +-def render_git_describe(pieces):
> ++def render_git_describe(pieces: Dict[str, Any]) -> str:
> +     """TAG[-DISTANCE-gHEX][-dirty].
> +
> +     Like 'git describe --tags --dirty --always'.
> +@@ -842,7 +1099,7 @@ def render_git_describe(pieces):
> +     return rendered
> +
> +
> +-def render_git_describe_long(pieces):
> ++def render_git_describe_long(pieces: Dict[str, Any]) -> str:
> +     """TAG-DISTANCE-gHEX[-dirty].
> +
> +     Like 'git describe --tags --dirty --always -long'.
> +@@ -862,7 +1119,7 @@ def render_git_describe_long(pieces):
> +     return rendered
> +
> +
> +-def render(pieces, style):
> ++def render(pieces: Dict[str, Any], style: str) -> Dict[str, Any]:
> +     """Render the given version pieces into the requested style."""
> +     if pieces["error"]:
> +         return {"version": "unknown",
> +@@ -876,10 +1133,14 @@ def render(pieces, style):
> +
> +     if style == "pep440":
> +         rendered = render_pep440(pieces)
> ++    elif style == "pep440-branch":
> ++        rendered = render_pep440_branch(pieces)
> +     elif style == "pep440-pre":
> +         rendered = render_pep440_pre(pieces)
> +     elif style == "pep440-post":
> +         rendered = render_pep440_post(pieces)
> ++    elif style == "pep440-post-branch":
> ++        rendered = render_pep440_post_branch(pieces)
> +     elif style == "pep440-old":
> +         rendered = render_pep440_old(pieces)
> +     elif style == "git-describe":
> +@@ -894,7 +1155,7 @@ def render(pieces, style):
> +             "date": pieces.get("date")}
> +
> +
> +-def get_versions():
> ++def get_versions() -> Dict[str, Any]:
> +     """Get version information or return default if unable to do so."""
> +     # I am in _version.py, which lives at ROOT/VERSIONFILE_SOURCE. If we have
> +     # __file__, we can work backwards from there to the root. Some
> +@@ -915,7 +1176,7 @@ def get_versions():
> +         # versionfile_source is the relative path from the top of the source
> +         # tree (where the .git directory might live) to this file. Invert
> +         # this to find the root from __file__.
> +-        for i in cfg.versionfile_source.split('/'):
> ++        for _ in cfg.versionfile_source.split('/'):
> +             root = os.path.dirname(root)
> +     except NameError:
> +         return {"version": "0+unknown", "full-revisionid": None,
> +@@ -942,41 +1203,48 @@ def get_versions():
> +
> +
> + @register_vcs_handler("git", "get_keywords")
> +-def git_get_keywords(versionfile_abs):
> ++def git_get_keywords(versionfile_abs: str) -> Dict[str, str]:
> +     """Extract version information from the given file."""
> +     # the code embedded in _version.py can just fetch the value of these
> +     # keywords. When used from setup.py, we don't want to import _version.py,
> +     # so we do it with a regexp instead. This function is not used from
> +     # _version.py.
> +-    keywords = {}
> ++    keywords: Dict[str, str] = {}
> +     try:
> +-        f = open(versionfile_abs, "r")
> +-        for line in f.readlines():
> +-            if line.strip().startswith("git_refnames ="):
> +-                mo = re.search(r'=\s*"(.*)"', line)
> +-                if mo:
> +-                    keywords["refnames"] = mo.group(1)
> +-            if line.strip().startswith("git_full ="):
> +-                mo = re.search(r'=\s*"(.*)"', line)
> +-                if mo:
> +-                    keywords["full"] = mo.group(1)
> +-            if line.strip().startswith("git_date ="):
> +-                mo = re.search(r'=\s*"(.*)"', line)
> +-                if mo:
> +-                    keywords["date"] = mo.group(1)
> +-        f.close()
> +-    except EnvironmentError:
> ++        with open(versionfile_abs, "r") as fobj:
> ++            for line in fobj:
> ++                if line.strip().startswith("git_refnames ="):
> ++                    mo = re.search(r'=\s*"(.*)"', line)
> ++                    if mo:
> ++                        keywords["refnames"] = mo.group(1)
> ++                if line.strip().startswith("git_full ="):
> ++                    mo = re.search(r'=\s*"(.*)"', line)
> ++                    if mo:
> ++                        keywords["full"] = mo.group(1)
> ++                if line.strip().startswith("git_date ="):
> ++                    mo = re.search(r'=\s*"(.*)"', line)
> ++                    if mo:
> ++                        keywords["date"] = mo.group(1)
> ++    except OSError:
> +         pass
> +     return keywords
> +
> +
> + @register_vcs_handler("git", "keywords")
> +-def git_versions_from_keywords(keywords, tag_prefix, verbose):
> ++def git_versions_from_keywords(
> ++    keywords: Dict[str, str],
> ++    tag_prefix: str,
> ++    verbose: bool,
> ++) -> Dict[str, Any]:
> +     """Get version information from git keywords."""
> +-    if not keywords:
> +-        raise NotThisMethod("no keywords at all, weird")
> ++    if "refnames" not in keywords:
> ++        raise NotThisMethod("Short version file found")
> +     date = keywords.get("date")
> +     if date is not None:
> ++        # Use only the last line.  Previous lines may contain GPG signature
> ++        # information.
> ++        date = date.splitlines()[-1]
> ++
> +         # git-2.2.0 added "%cI", which expands to an ISO-8601 -compliant
> +         # datestamp. However we prefer "%ci" (which expands to an "ISO-8601
> +         # -like" string, which we must then edit to make compliant), because
> +@@ -989,11 +1257,11 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose):
> +         if verbose:
> +             print("keywords are unexpanded, not using")
> +         raise NotThisMethod("unexpanded keywords, not a git-archive tarball")
> +-    refs = set([r.strip() for r in refnames.strip("()").split(",")])
> ++    refs = {r.strip() for r in refnames.strip("()").split(",")}
> +     # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of
> +     # just "foo-1.0". If we see a "tag: " prefix, prefer those.
> +     TAG = "tag: "
> +-    tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)])
> ++    tags = {r[len(TAG) :] for r in refs if r.startswith(TAG)}
> +     if not tags:
> +         # Either we're using git < 1.8.3, or there really are no tags. We use
> +         # a heuristic: assume all version tags have a digit. The old git %d
> +@@ -1002,7 +1270,7 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose):
> +         # between branches and tags. By ignoring refnames without digits, we
> +         # filter out many common branch names like "release" and
> +         # "stabilization", as well as "HEAD" and "master".
> +-        tags = set([r for r in refs if re.search(r'\d', r)])
> ++        tags = {r for r in refs if re.search(r"\d", r)}
> +         if verbose:
> +             print("discarding '%s', no digits" % ",".join(refs - tags))
> +     if verbose:
> +@@ -1010,23 +1278,37 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose):
> +     for ref in sorted(tags):
> +         # sorting will prefer e.g. "2.0" over "2.0rc1"
> +         if ref.startswith(tag_prefix):
> +-            r = ref[len(tag_prefix):]
> ++            r = ref[len(tag_prefix) :]
> ++            # Filter out refs that exactly match prefix or that don't start
> ++            # with a number once the prefix is stripped (mostly a concern
> ++            # when prefix is '')
> ++            if not re.match(r"\d", r):
> ++                continue
> +             if verbose:
> +                 print("picking %s" % r)
> +-            return {"version": r,
> +-                    "full-revisionid": keywords["full"].strip(),
> +-                    "dirty": False, "error": None,
> +-                    "date": date}
> ++            return {
> ++                "version": r,
> ++                "full-revisionid": keywords["full"].strip(),
> ++                "dirty": False,
> ++                "error": None,
> ++                "date": date,
> ++            }
> +     # no suitable tags, so version is "0+unknown", but full hex is still there
> +     if verbose:
> +         print("no suitable tags, using unknown + full revision id")
> +-    return {"version": "0+unknown",
> +-            "full-revisionid": keywords["full"].strip(),
> +-            "dirty": False, "error": "no suitable tags", "date": None}
> ++    return {
> ++        "version": "0+unknown",
> ++        "full-revisionid": keywords["full"].strip(),
> ++        "dirty": False,
> ++        "error": "no suitable tags",
> ++        "date": None,
> ++    }
> +
> +
> + @register_vcs_handler("git", "pieces_from_vcs")
> +-def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
> ++def git_pieces_from_vcs(
> ++    tag_prefix: str, root: str, verbose: bool, runner: Callable = run_command
> ++) -> Dict[str, Any]:
> +     """Get version from 'git describe' in the root of the source tree.
> +
> +     This only gets called if the git-archive 'subst' keywords were *not*
> +@@ -1037,8 +1319,14 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
> +     if sys.platform == "win32":
> +         GITS = ["git.cmd", "git.exe"]
> +
> +-    out, rc = run_command(GITS, ["rev-parse", "--git-dir"], cwd=root,
> +-                          hide_stderr=True)
> ++    # GIT_DIR can interfere with correct operation of Versioneer.
> ++    # It may be intended to be passed to the Versioneer-versioned project,
> ++    # but that should not change where we get our version from.
> ++    env = os.environ.copy()
> ++    env.pop("GIT_DIR", None)
> ++    runner = functools.partial(runner, env=env)
> ++
> ++    _, rc = runner(GITS, ["rev-parse", "--git-dir"], cwd=root, hide_stderr=not verbose)
> +     if rc != 0:
> +         if verbose:
> +             print("Directory %s not under git control" % root)
> +@@ -1046,24 +1334,65 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
> +
> +     # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty]
> +     # if there isn't one, this yields HEX[-dirty] (no NUM)
> +-    describe_out, rc = run_command(GITS, ["describe", "--tags", "--dirty",
> +-                                          "--always", "--long",
> +-                                          "--match", "%s*" % tag_prefix],
> +-                                   cwd=root)
> ++    describe_out, rc = runner(
> ++        GITS,
> ++        [
> ++            "describe",
> ++            "--tags",
> ++            "--dirty",
> ++            "--always",
> ++            "--long",
> ++            "--match",
> ++            f"{tag_prefix}[[:digit:]]*",
> ++        ],
> ++        cwd=root,
> ++    )
> +     # --long was added in git-1.5.5
> +     if describe_out is None:
> +         raise NotThisMethod("'git describe' failed")
> +     describe_out = describe_out.strip()
> +-    full_out, rc = run_command(GITS, ["rev-parse", "HEAD"], cwd=root)
> ++    full_out, rc = runner(GITS, ["rev-parse", "HEAD"], cwd=root)
> +     if full_out is None:
> +         raise NotThisMethod("'git rev-parse' failed")
> +     full_out = full_out.strip()
> +
> +-    pieces = {}
> ++    pieces: Dict[str, Any] = {}
> +     pieces["long"] = full_out
> +     pieces["short"] = full_out[:7]  # maybe improved later
> +     pieces["error"] = None
> +
> ++    branch_name, rc = runner(GITS, ["rev-parse", "--abbrev-ref", "HEAD"], cwd=root)
> ++    # --abbrev-ref was added in git-1.6.3
> ++    if rc != 0 or branch_name is None:
> ++        raise NotThisMethod("'git rev-parse --abbrev-ref' returned error")
> ++    branch_name = branch_name.strip()
> ++
> ++    if branch_name == "HEAD":
> ++        # If we aren't exactly on a branch, pick a branch which represents
> ++        # the current commit. If all else fails, we are on a branchless
> ++        # commit.
> ++        branches, rc = runner(GITS, ["branch", "--contains"], cwd=root)
> ++        # --contains was added in git-1.5.4
> ++        if rc != 0 or branches is None:
> ++            raise NotThisMethod("'git branch --contains' returned error")
> ++        branches = branches.split("\n")
> ++
> ++        # Remove the first line if we're running detached
> ++        if "(" in branches[0]:
> ++            branches.pop(0)
> ++
> ++        # Strip off the leading "* " from the list of branches.
> ++        branches = [branch[2:] for branch in branches]
> ++        if "master" in branches:
> ++            branch_name = "master"
> ++        elif not branches:
> ++            branch_name = None
> ++        else:
> ++            # Pick the first branch that is returned. Good or bad.
> ++            branch_name = branches[0]
> ++
> ++    pieces["branch"] = branch_name
> ++
> +     # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty]
> +     # TAG might have hyphens.
> +     git_describe = describe_out
> +@@ -1072,17 +1401,16 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
> +     dirty = git_describe.endswith("-dirty")
> +     pieces["dirty"] = dirty
> +     if dirty:
> +-        git_describe = git_describe[:git_describe.rindex("-dirty")]
> ++        git_describe = git_describe[: git_describe.rindex("-dirty")]
> +
> +     # now we have TAG-NUM-gHEX or HEX
> +
> +     if "-" in git_describe:
> +         # TAG-NUM-gHEX
> +-        mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe)
> ++        mo = re.search(r"^(.+)-(\d+)-g([0-9a-f]+)$", git_describe)
> +         if not mo:
> +-            # unparseable. Maybe git-describe is misbehaving?
> +-            pieces["error"] = ("unable to parse git-describe output: '%s'"
> +-                               % describe_out)
> ++            # unparsable. Maybe git-describe is misbehaving?
> ++            pieces["error"] = "unable to parse git-describe output: '%s'" % describe_out
> +             return pieces
> +
> +         # tag
> +@@ -1091,10 +1419,12 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
> +             if verbose:
> +                 fmt = "tag '%s' doesn't start with prefix '%s'"
> +                 print(fmt % (full_tag, tag_prefix))
> +-            pieces["error"] = ("tag '%s' doesn't start with prefix '%s'"
> +-                               % (full_tag, tag_prefix))
> ++            pieces["error"] = "tag '%s' doesn't start with prefix '%s'" % (
> ++                full_tag,
> ++                tag_prefix,
> ++            )
> +             return pieces
> +-        pieces["closest-tag"] = full_tag[len(tag_prefix):]
> ++        pieces["closest-tag"] = full_tag[len(tag_prefix) :]
> +
> +         # distance: number of commits since tag
> +         pieces["distance"] = int(mo.group(2))
> +@@ -1105,19 +1435,20 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
> +     else:
> +         # HEX: no tags
> +         pieces["closest-tag"] = None
> +-        count_out, rc = run_command(GITS, ["rev-list", "HEAD", "--count"],
> +-                                    cwd=root)
> +-        pieces["distance"] = int(count_out)  # total number of commits
> ++        out, rc = runner(GITS, ["rev-list", "HEAD", "--left-right"], cwd=root)
> ++        pieces["distance"] = len(out.split())  # total number of commits
> +
> +     # commit date: see ISO-8601 comment in git_versions_from_keywords()
> +-    date = run_command(GITS, ["show", "-s", "--format=%ci", "HEAD"],
> +-                       cwd=root)[0].strip()
> ++    date = runner(GITS, ["show", "-s", "--format=%ci", "HEAD"], cwd=root)[0].strip()
> ++    # Use only the last line.  Previous lines may contain GPG signature
> ++    # information.
> ++    date = date.splitlines()[-1]
> +     pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1)
> +
> +     return pieces
> +
> +
> +-def do_vcs_install(manifest_in, versionfile_source, ipy):
> ++def do_vcs_install(versionfile_source: str, ipy: Optional[str]) -> None:
> +     """Git-specific installation logic for Versioneer.
> +
> +     For Git, this means creating/changing .gitattributes to mark _version.py
> +@@ -1126,36 +1457,40 @@ def do_vcs_install(manifest_in, versionfile_source, ipy):
> +     GITS = ["git"]
> +     if sys.platform == "win32":
> +         GITS = ["git.cmd", "git.exe"]
> +-    files = [manifest_in, versionfile_source]
> ++    files = [versionfile_source]
> +     if ipy:
> +         files.append(ipy)
> +-    try:
> +-        me = __file__
> +-        if me.endswith(".pyc") or me.endswith(".pyo"):
> +-            me = os.path.splitext(me)[0] + ".py"
> +-        versioneer_file = os.path.relpath(me)
> +-    except NameError:
> +-        versioneer_file = "versioneer.py"
> +-    files.append(versioneer_file)
> ++    if "VERSIONEER_PEP518" not in globals():
> ++        try:
> ++            my_path = __file__
> ++            if my_path.endswith((".pyc", ".pyo")):
> ++                my_path = os.path.splitext(my_path)[0] + ".py"
> ++            versioneer_file = os.path.relpath(my_path)
> ++        except NameError:
> ++            versioneer_file = "versioneer.py"
> ++        files.append(versioneer_file)
> +     present = False
> +     try:
> +-        f = open(".gitattributes", "r")
> +-        for line in f.readlines():
> +-            if line.strip().startswith(versionfile_source):
> +-                if "export-subst" in line.strip().split()[1:]:
> +-                    present = True
> +-        f.close()
> +-    except EnvironmentError:
> ++        with open(".gitattributes", "r") as fobj:
> ++            for line in fobj:
> ++                if line.strip().startswith(versionfile_source):
> ++                    if "export-subst" in line.strip().split()[1:]:
> ++                        present = True
> ++                        break
> ++    except OSError:
> +         pass
> +     if not present:
> +-        f = open(".gitattributes", "a+")
> +-        f.write("%s export-subst\n" % versionfile_source)
> +-        f.close()
> ++        with open(".gitattributes", "a+") as fobj:
> ++            fobj.write(f"{versionfile_source} export-subst\n")
> +         files.append(".gitattributes")
> +     run_command(GITS, ["add", "--"] + files)
> +
> +
> +-def versions_from_parentdir(parentdir_prefix, root, verbose):
> ++def versions_from_parentdir(
> ++    parentdir_prefix: str,
> ++    root: str,
> ++    verbose: bool,
> ++) -> Dict[str, Any]:
> +     """Try to determine the version from the parent directory name.
> +
> +     Source tarballs conventionally unpack into a directory that includes both
> +@@ -1164,24 +1499,29 @@ def versions_from_parentdir(parentdir_prefix, root, verbose):
> +     """
> +     rootdirs = []
> +
> +-    for i in range(3):
> ++    for _ in range(3):
> +         dirname = os.path.basename(root)
> +         if dirname.startswith(parentdir_prefix):
> +-            return {"version": dirname[len(parentdir_prefix):],
> +-                    "full-revisionid": None,
> +-                    "dirty": False, "error": None, "date": None}
> +-        else:
> +-            rootdirs.append(root)
> +-            root = os.path.dirname(root)  # up a level
> ++            return {
> ++                "version": dirname[len(parentdir_prefix) :],
> ++                "full-revisionid": None,
> ++                "dirty": False,
> ++                "error": None,
> ++                "date": None,
> ++            }
> ++        rootdirs.append(root)
> ++        root = os.path.dirname(root)  # up a level
> +
> +     if verbose:
> +-        print("Tried directories %s but none started with prefix %s" %
> +-              (str(rootdirs), parentdir_prefix))
> ++        print(
> ++            "Tried directories %s but none started with prefix %s"
> ++            % (str(rootdirs), parentdir_prefix)
> ++        )
> +     raise NotThisMethod("rootdir doesn't start with parentdir_prefix")
> +
> +
> + SHORT_VERSION_PY = """
> +-# This file was generated by 'versioneer.py' (0.18) from
> ++# This file was generated by 'versioneer.py' (0.29) from
> + # revision-control system data, or from the parent directory name of an
> + # unpacked source archive. Distribution tarballs contain a pre-generated copy
> + # of this file.
> +@@ -1198,42 +1538,42 @@ def get_versions():
> + """
> +
> +
> +-def versions_from_file(filename):
> ++def versions_from_file(filename: str) -> Dict[str, Any]:
> +     """Try to determine the version from _version.py if present."""
> +     try:
> +         with open(filename) as f:
> +             contents = f.read()
> +-    except EnvironmentError:
> ++    except OSError:
> +         raise NotThisMethod("unable to read _version.py")
> +-    mo = re.search(r"version_json = '''\n(.*)'''  # END VERSION_JSON",
> +-                   contents, re.M | re.S)
> ++    mo = re.search(
> ++        r"version_json = '''\n(.*)'''  # END VERSION_JSON", contents, re.M | re.S
> ++    )
> +     if not mo:
> +-        mo = re.search(r"version_json = '''\r\n(.*)'''  # END VERSION_JSON",
> +-                       contents, re.M | re.S)
> ++        mo = re.search(
> ++            r"version_json = '''\r\n(.*)'''  # END VERSION_JSON", contents, re.M | re.S
> ++        )
> +     if not mo:
> +         raise NotThisMethod("no version_json in _version.py")
> +     return json.loads(mo.group(1))
> +
> +
> +-def write_to_version_file(filename, versions):
> ++def write_to_version_file(filename: str, versions: Dict[str, Any]) -> None:
> +     """Write the given version number to the given _version.py file."""
> +-    os.unlink(filename)
> +-    contents = json.dumps(versions, sort_keys=True,
> +-                          indent=1, separators=(",", ": "))
> ++    contents = json.dumps(versions, sort_keys=True, indent=1, separators=(",", ": "))
> +     with open(filename, "w") as f:
> +         f.write(SHORT_VERSION_PY % contents)
> +
> +     print("set %s to '%s'" % (filename, versions["version"]))
> +
> +
> +-def plus_or_dot(pieces):
> ++def plus_or_dot(pieces: Dict[str, Any]) -> str:
> +     """Return a + if we don't already have one, else return a ."""
> +     if "+" in pieces.get("closest-tag", ""):
> +         return "."
> +     return "+"
> +
> +
> +-def render_pep440(pieces):
> ++def render_pep440(pieces: Dict[str, Any]) -> str:
> +     """Build up version string, with post-release "local version identifier".
> +
> +     Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you
> +@@ -1251,30 +1591,76 @@ def render_pep440(pieces):
> +                 rendered += ".dirty"
> +     else:
> +         # exception #1
> +-        rendered = "0+untagged.%d.g%s" % (pieces["distance"],
> +-                                          pieces["short"])
> ++        rendered = "0+untagged.%d.g%s" % (pieces["distance"], pieces["short"])
> +         if pieces["dirty"]:
> +             rendered += ".dirty"
> +     return rendered
> +
> +
> +-def render_pep440_pre(pieces):
> +-    """TAG[.post.devDISTANCE] -- No -dirty.
> ++def render_pep440_branch(pieces: Dict[str, Any]) -> str:
> ++    """TAG[[.dev0]+DISTANCE.gHEX[.dirty]] .
> ++
> ++    The ".dev0" means not master branch. Note that .dev0 sorts backwards
> ++    (a feature branch will appear "older" than the master branch).
> +
> +     Exceptions:
> +-    1: no tags. 0.post.devDISTANCE
> ++    1: no tags. 0[.dev0]+untagged.DISTANCE.gHEX[.dirty]
> +     """
> +     if pieces["closest-tag"]:
> +         rendered = pieces["closest-tag"]
> ++        if pieces["distance"] or pieces["dirty"]:
> ++            if pieces["branch"] != "master":
> ++                rendered += ".dev0"
> ++            rendered += plus_or_dot(pieces)
> ++            rendered += "%d.g%s" % (pieces["distance"], pieces["short"])
> ++            if pieces["dirty"]:
> ++                rendered += ".dirty"
> ++    else:
> ++        # exception #1
> ++        rendered = "0"
> ++        if pieces["branch"] != "master":
> ++            rendered += ".dev0"
> ++        rendered += "+untagged.%d.g%s" % (pieces["distance"], pieces["short"])
> ++        if pieces["dirty"]:
> ++            rendered += ".dirty"
> ++    return rendered
> ++
> ++
> ++def pep440_split_post(ver: str) -> Tuple[str, Optional[int]]:
> ++    """Split pep440 version string at the post-release segment.
> ++
> ++    Returns the release segments before the post-release and the
> ++    post-release version number (or -1 if no post-release segment is present).
> ++    """
> ++    vc = str.split(ver, ".post")
> ++    return vc[0], int(vc[1] or 0) if len(vc) == 2 else None
> ++
> ++
> ++def render_pep440_pre(pieces: Dict[str, Any]) -> str:
> ++    """TAG[.postN.devDISTANCE] -- No -dirty.
> ++
> ++    Exceptions:
> ++    1: no tags. 0.post0.devDISTANCE
> ++    """
> ++    if pieces["closest-tag"]:
> +         if pieces["distance"]:
> +-            rendered += ".post.dev%d" % pieces["distance"]
> ++            # update the post release segment
> ++            tag_version, post_version = pep440_split_post(pieces["closest-tag"])
> ++            rendered = tag_version
> ++            if post_version is not None:
> ++                rendered += ".post%d.dev%d" % (post_version + 1, pieces["distance"])
> ++            else:
> ++                rendered += ".post0.dev%d" % (pieces["distance"])
> ++        else:
> ++            # no commits, use the tag as the version
> ++            rendered = pieces["closest-tag"]
> +     else:
> +         # exception #1
> +-        rendered = "0.post.dev%d" % pieces["distance"]
> ++        rendered = "0.post0.dev%d" % pieces["distance"]
> +     return rendered
> +
> +
> +-def render_pep440_post(pieces):
> ++def render_pep440_post(pieces: Dict[str, Any]) -> str:
> +     """TAG[.postDISTANCE[.dev0]+gHEX] .
> +
> +     The ".dev0" means dirty. Note that .dev0 sorts backwards
> +@@ -1301,12 +1687,41 @@ def render_pep440_post(pieces):
> +     return rendered
> +
> +
> +-def render_pep440_old(pieces):
> ++def render_pep440_post_branch(pieces: Dict[str, Any]) -> str:
> ++    """TAG[.postDISTANCE[.dev0]+gHEX[.dirty]] .
> ++
> ++    The ".dev0" means not master branch.
> ++
> ++    Exceptions:
> ++    1: no tags. 0.postDISTANCE[.dev0]+gHEX[.dirty]
> ++    """
> ++    if pieces["closest-tag"]:
> ++        rendered = pieces["closest-tag"]
> ++        if pieces["distance"] or pieces["dirty"]:
> ++            rendered += ".post%d" % pieces["distance"]
> ++            if pieces["branch"] != "master":
> ++                rendered += ".dev0"
> ++            rendered += plus_or_dot(pieces)
> ++            rendered += "g%s" % pieces["short"]
> ++            if pieces["dirty"]:
> ++                rendered += ".dirty"
> ++    else:
> ++        # exception #1
> ++        rendered = "0.post%d" % pieces["distance"]
> ++        if pieces["branch"] != "master":
> ++            rendered += ".dev0"
> ++        rendered += "+g%s" % pieces["short"]
> ++        if pieces["dirty"]:
> ++            rendered += ".dirty"
> ++    return rendered
> ++
> ++
> ++def render_pep440_old(pieces: Dict[str, Any]) -> str:
> +     """TAG[.postDISTANCE[.dev0]] .
> +
> +     The ".dev0" means dirty.
> +
> +-    Eexceptions:
> ++    Exceptions:
> +     1: no tags. 0.postDISTANCE[.dev0]
> +     """
> +     if pieces["closest-tag"]:
> +@@ -1323,7 +1738,7 @@ def render_pep440_old(pieces):
> +     return rendered
> +
> +
> +-def render_git_describe(pieces):
> ++def render_git_describe(pieces: Dict[str, Any]) -> str:
> +     """TAG[-DISTANCE-gHEX][-dirty].
> +
> +     Like 'git describe --tags --dirty --always'.
> +@@ -1343,7 +1758,7 @@ def render_git_describe(pieces):
> +     return rendered
> +
> +
> +-def render_git_describe_long(pieces):
> ++def render_git_describe_long(pieces: Dict[str, Any]) -> str:
> +     """TAG-DISTANCE-gHEX[-dirty].
> +
> +     Like 'git describe --tags --dirty --always -long'.
> +@@ -1363,24 +1778,30 @@ def render_git_describe_long(pieces):
> +     return rendered
> +
> +
> +-def render(pieces, style):
> ++def render(pieces: Dict[str, Any], style: str) -> Dict[str, Any]:
> +     """Render the given version pieces into the requested style."""
> +     if pieces["error"]:
> +-        return {"version": "unknown",
> +-                "full-revisionid": pieces.get("long"),
> +-                "dirty": None,
> +-                "error": pieces["error"],
> +-                "date": None}
> ++        return {
> ++            "version": "unknown",
> ++            "full-revisionid": pieces.get("long"),
> ++            "dirty": None,
> ++            "error": pieces["error"],
> ++            "date": None,
> ++        }
> +
> +     if not style or style == "default":
> +         style = "pep440"  # the default
> +
> +     if style == "pep440":
> +         rendered = render_pep440(pieces)
> ++    elif style == "pep440-branch":
> ++        rendered = render_pep440_branch(pieces)
> +     elif style == "pep440-pre":
> +         rendered = render_pep440_pre(pieces)
> +     elif style == "pep440-post":
> +         rendered = render_pep440_post(pieces)
> ++    elif style == "pep440-post-branch":
> ++        rendered = render_pep440_post_branch(pieces)
> +     elif style == "pep440-old":
> +         rendered = render_pep440_old(pieces)
> +     elif style == "git-describe":
> +@@ -1390,16 +1811,20 @@ def render(pieces, style):
> +     else:
> +         raise ValueError("unknown style '%s'" % style)
> +
> +-    return {"version": rendered, "full-revisionid": pieces["long"],
> +-            "dirty": pieces["dirty"], "error": None,
> +-            "date": pieces.get("date")}
> ++    return {
> ++        "version": rendered,
> ++        "full-revisionid": pieces["long"],
> ++        "dirty": pieces["dirty"],
> ++        "error": None,
> ++        "date": pieces.get("date"),
> ++    }
> +
> +
> + class VersioneerBadRootError(Exception):
> +     """The project root directory is unknown or missing key files."""
> +
> +
> +-def get_versions(verbose=False):
> ++def get_versions(verbose: bool = False) -> Dict[str, Any]:
> +     """Get the project version from whatever source is available.
> +
> +     Returns dict with two keys: 'version' and 'full'.
> +@@ -1414,9 +1839,10 @@ def get_versions(verbose=False):
> +     assert cfg.VCS is not None, "please set [versioneer]VCS= in setup.cfg"
> +     handlers = HANDLERS.get(cfg.VCS)
> +     assert handlers, "unrecognized VCS '%s'" % cfg.VCS
> +-    verbose = verbose or cfg.verbose
> +-    assert cfg.versionfile_source is not None, \
> +-        "please set versioneer.versionfile_source"
> ++    verbose = verbose or bool(cfg.verbose)  # `bool()` used to avoid `None`
> ++    assert (
> ++        cfg.versionfile_source is not None
> ++    ), "please set versioneer.versionfile_source"
> +     assert cfg.tag_prefix is not None, "please set versioneer.tag_prefix"
> +
> +     versionfile_abs = os.path.join(root, cfg.versionfile_source)
> +@@ -1470,18 +1896,26 @@ def get_versions(verbose=False):
> +     if verbose:
> +         print("unable to compute version")
> +
> +-    return {"version": "0+unknown", "full-revisionid": None,
> +-            "dirty": None, "error": "unable to compute version",
> +-            "date": None}
> ++    return {
> ++        "version": "0+unknown",
> ++        "full-revisionid": None,
> ++        "dirty": None,
> ++        "error": "unable to compute version",
> ++        "date": None,
> ++    }
> +
> +
> +-def get_version():
> ++def get_version() -> str:
> +     """Get the short version string for this project."""
> +     return get_versions()["version"]
> +
> +
> +-def get_cmdclass():
> +-    """Get the custom setuptools/distutils subclasses used by Versioneer."""
> ++def get_cmdclass(cmdclass: Optional[Dict[str, Any]] = None):
> ++    """Get the custom setuptools subclasses used by Versioneer.
> ++
> ++    If the package uses a different cmdclass (e.g. one from numpy), it
> ++    should be provide as an argument.
> ++    """
> +     if "versioneer" in sys.modules:
> +         del sys.modules["versioneer"]
> +         # this fixes the "python setup.py develop" case (also 'install' and
> +@@ -1495,25 +1929,25 @@ def get_cmdclass():
> +         # parent is protected against the child's "import versioneer". By
> +         # removing ourselves from sys.modules here, before the child build
> +         # happens, we protect the child from the parent's versioneer too.
> +-        # Also see https://github.com/warner/python-versioneer/issues/52
> ++        # Also see https://github.com/python-versioneer/python-versioneer/issues/52
> +
> +-    cmds = {}
> ++    cmds = {} if cmdclass is None else cmdclass.copy()
> +
> +-    # we add "version" to both distutils and setuptools
> +-    from distutils.core import Command
> ++    # we add "version" to setuptools
> ++    from setuptools import Command
> +
> +     class cmd_version(Command):
> +         description = "report generated version string"
> +-        user_options = []
> +-        boolean_options = []
> ++        user_options: List[Tuple[str, str, str]] = []
> ++        boolean_options: List[str] = []
> +
> +-        def initialize_options(self):
> ++        def initialize_options(self) -> None:
> +             pass
> +
> +-        def finalize_options(self):
> ++        def finalize_options(self) -> None:
> +             pass
> +
> +-        def run(self):
> ++        def run(self) -> None:
> +             vers = get_versions(verbose=True)
> +             print("Version: %s" % vers["version"])
> +             print(" full-revisionid: %s" % vers.get("full-revisionid"))
> +@@ -1521,9 +1955,10 @@ def get_cmdclass():
> +             print(" date: %s" % vers.get("date"))
> +             if vers["error"]:
> +                 print(" error: %s" % vers["error"])
> ++
> +     cmds["version"] = cmd_version
> +
> +-    # we override "build_py" in both distutils and setuptools
> ++    # we override "build_py" in setuptools
> +     #
> +     # most invocation pathways end up running build_py:
> +     #  distutils/build -> build_py
> +@@ -1538,29 +1973,71 @@ def get_cmdclass():
> +     #   then does setup.py bdist_wheel, or sometimes setup.py install
> +     #  setup.py egg_info -> ?
> +
> ++    # pip install -e . and setuptool/editable_wheel will invoke build_py
> ++    # but the build_py command is not expected to copy any files.
> ++
> +     # we override different "build_py" commands for both environments
> +-    if "setuptools" in sys.modules:
> +-        from setuptools.command.build_py import build_py as _build_py
> ++    if "build_py" in cmds:
> ++        _build_py: Any = cmds["build_py"]
> +     else:
> +-        from distutils.command.build_py import build_py as _build_py
> ++        from setuptools.command.build_py import build_py as _build_py
> +
> +     class cmd_build_py(_build_py):
> +-        def run(self):
> ++        def run(self) -> None:
> +             root = get_root()
> +             cfg = get_config_from_root(root)
> +             versions = get_versions()
> +             _build_py.run(self)
> ++            if getattr(self, "editable_mode", False):
> ++                # During editable installs `.py` and data files are
> ++                # not copied to build_lib
> ++                return
> +             # now locate _version.py in the new build/ directory and replace
> +             # it with an updated value
> +             if cfg.versionfile_build:
> +-                target_versionfile = os.path.join(self.build_lib,
> +-                                                  cfg.versionfile_build)
> ++                target_versionfile = os.path.join(self.build_lib, cfg.versionfile_build)
> +                 print("UPDATING %s" % target_versionfile)
> +                 write_to_version_file(target_versionfile, versions)
> ++
> +     cmds["build_py"] = cmd_build_py
> +
> ++    if "build_ext" in cmds:
> ++        _build_ext: Any = cmds["build_ext"]
> ++    else:
> ++        from setuptools.command.build_ext import build_ext as _build_ext
> ++
> ++    class cmd_build_ext(_build_ext):
> ++        def run(self) -> None:
> ++            root = get_root()
> ++            cfg = get_config_from_root(root)
> ++            versions = get_versions()
> ++            _build_ext.run(self)
> ++            if self.inplace:
> ++                # build_ext --inplace will only build extensions in
> ++                # build/lib<..> dir with no _version.py to write to.
> ++                # As in place builds will already have a _version.py
> ++                # in the module dir, we do not need to write one.
> ++                return
> ++            # now locate _version.py in the new build/ directory and replace
> ++            # it with an updated value
> ++            if not cfg.versionfile_build:
> ++                return
> ++            target_versionfile = os.path.join(self.build_lib, cfg.versionfile_build)
> ++            if not os.path.exists(target_versionfile):
> ++                print(
> ++                    f"Warning: {target_versionfile} does not exist, skipping "
> ++                    "version update. This can happen if you are running build_ext "
> ++                    "without first running build_py."
> ++                )
> ++                return
> ++            print("UPDATING %s" % target_versionfile)
> ++            write_to_version_file(target_versionfile, versions)
> ++
> ++    cmds["build_ext"] = cmd_build_ext
> ++
> +     if "cx_Freeze" in sys.modules:  # cx_freeze enabled?
> +-        from cx_Freeze.dist import build_exe as _build_exe
> ++        from cx_Freeze.dist import build_exe as _build_exe  # type: ignore
> ++
> +         # nczeczulin reports that py2exe won't like the pep440-style string
> +         # as FILEVERSION, but it can be used for PRODUCTVERSION, e.g.
> +         # setup(console=[{
> +@@ -1569,7 +2046,7 @@ def get_cmdclass():
> +         #   ...
> +
> +         class cmd_build_exe(_build_exe):
> +-            def run(self):
> ++            def run(self) -> None:
> +                 root = get_root()
> +                 cfg = get_config_from_root(root)
> +                 versions = get_versions()
> +@@ -1581,24 +2058,28 @@ def get_cmdclass():
> +                 os.unlink(target_versionfile)
> +                 with open(cfg.versionfile_source, "w") as f:
> +                     LONG = LONG_VERSION_PY[cfg.VCS]
> +-                    f.write(LONG %
> +-                            {"DOLLAR": "$",
> +-                             "STYLE": cfg.style,
> +-                             "TAG_PREFIX": cfg.tag_prefix,
> +-                             "PARENTDIR_PREFIX": cfg.parentdir_prefix,
> +-                             "VERSIONFILE_SOURCE": cfg.versionfile_source,
> +-                             })
> ++                    f.write(
> ++                        LONG
> ++                        % {
> ++                            "DOLLAR": "$",
> ++                            "STYLE": cfg.style,
> ++                            "TAG_PREFIX": cfg.tag_prefix,
> ++                            "PARENTDIR_PREFIX": cfg.parentdir_prefix,
> ++                            "VERSIONFILE_SOURCE": cfg.versionfile_source,
> ++                        }
> ++                    )
> ++
> +         cmds["build_exe"] = cmd_build_exe
> +         del cmds["build_py"]
> +
> +-    if 'py2exe' in sys.modules:  # py2exe enabled?
> ++    if "py2exe" in sys.modules:  # py2exe enabled?
> +         try:
> +-            from py2exe.distutils_buildexe import py2exe as _py2exe  # py3
> ++            from py2exe.setuptools_buildexe import py2exe as _py2exe  # type: ignore
> +         except ImportError:
> +-            from py2exe.build_exe import py2exe as _py2exe  # py2
> ++            from py2exe.distutils_buildexe import py2exe as _py2exe  # type: ignore
> +
> +         class cmd_py2exe(_py2exe):
> +-            def run(self):
> ++            def run(self) -> None:
> +                 root = get_root()
> +                 cfg = get_config_from_root(root)
> +                 versions = get_versions()
> +@@ -1610,23 +2091,67 @@ def get_cmdclass():
> +                 os.unlink(target_versionfile)
> +                 with open(cfg.versionfile_source, "w") as f:
> +                     LONG = LONG_VERSION_PY[cfg.VCS]
> +-                    f.write(LONG %
> +-                            {"DOLLAR": "$",
> +-                             "STYLE": cfg.style,
> +-                             "TAG_PREFIX": cfg.tag_prefix,
> +-                             "PARENTDIR_PREFIX": cfg.parentdir_prefix,
> +-                             "VERSIONFILE_SOURCE": cfg.versionfile_source,
> +-                             })
> ++                    f.write(
> ++                        LONG
> ++                        % {
> ++                            "DOLLAR": "$",
> ++                            "STYLE": cfg.style,
> ++                            "TAG_PREFIX": cfg.tag_prefix,
> ++                            "PARENTDIR_PREFIX": cfg.parentdir_prefix,
> ++                            "VERSIONFILE_SOURCE": cfg.versionfile_source,
> ++                        }
> ++                    )
> ++
> +         cmds["py2exe"] = cmd_py2exe
> +
> ++    # sdist farms its file list building out to egg_info
> ++    if "egg_info" in cmds:
> ++        _egg_info: Any = cmds["egg_info"]
> ++    else:
> ++        from setuptools.command.egg_info import egg_info as _egg_info
> ++
> ++    class cmd_egg_info(_egg_info):
> ++        def find_sources(self) -> None:
> ++            # egg_info.find_sources builds the manifest list and writes it
> ++            # in one shot
> ++            super().find_sources()
> ++
> ++            # Modify the filelist and normalize it
> ++            root = get_root()
> ++            cfg = get_config_from_root(root)
> ++            self.filelist.append("versioneer.py")
> ++            if cfg.versionfile_source:
> ++                # There are rare cases where versionfile_source might not be
> ++                # included by default, so we must be explicit
> ++                self.filelist.append(cfg.versionfile_source)
> ++            self.filelist.sort()
> ++            self.filelist.remove_duplicates()
> ++
> ++            # The write method is hidden in the manifest_maker instance that
> ++            # generated the filelist and was thrown away
> ++            # We will instead replicate their final normalization (to unicode,
> ++            # and POSIX-style paths)
> ++            from setuptools import unicode_utils
> ++
> ++            normalized = [
> ++                unicode_utils.filesys_decode(f).replace(os.sep, "/")
> ++                for f in self.filelist.files
> ++            ]
> ++
> ++            manifest_filename = os.path.join(self.egg_info, "SOURCES.txt")
> ++            with open(manifest_filename, "w") as fobj:
> ++                fobj.write("\n".join(normalized))
> ++
> ++    cmds["egg_info"] = cmd_egg_info
> ++
> +     # we override different "sdist" commands for both environments
> +-    if "setuptools" in sys.modules:
> +-        from setuptools.command.sdist import sdist as _sdist
> ++    if "sdist" in cmds:
> ++        _sdist: Any = cmds["sdist"]
> +     else:
> +-        from distutils.command.sdist import sdist as _sdist
> ++        from setuptools.command.sdist import sdist as _sdist
> +
> +     class cmd_sdist(_sdist):
> +-        def run(self):
> ++        def run(self) -> None:
> +             versions = get_versions()
> +             self._versioneer_generated_versions = versions
> +             # unless we update this, the command will keep using the old
> +@@ -1634,7 +2159,7 @@ def get_cmdclass():
> +             self.distribution.metadata.version = versions["version"]
> +             return _sdist.run(self)
> +
> +-        def make_release_tree(self, base_dir, files):
> ++        def make_release_tree(self, base_dir: str, files: List[str]) -> None:
> +             root = get_root()
> +             cfg = get_config_from_root(root)
> +             _sdist.make_release_tree(self, base_dir, files)
> +@@ -1643,8 +2168,10 @@ def get_cmdclass():
> +             # updated value
> +             target_versionfile = os.path.join(base_dir, cfg.versionfile_source)
> +             print("UPDATING %s" % target_versionfile)
> +-            write_to_version_file(target_versionfile,
> +-                                  self._versioneer_generated_versions)
> ++            write_to_version_file(
> ++                target_versionfile, self._versioneer_generated_versions
> ++            )
> ++
> +     cmds["sdist"] = cmd_sdist
> +
> +     return cmds
> +@@ -1687,23 +2214,26 @@ SAMPLE_CONFIG = """
> +
> + """
> +
> +-INIT_PY_SNIPPET = """
> ++OLD_SNIPPET = """
> + from ._version import get_versions
> + __version__ = get_versions()['version']
> + del get_versions
> + """
> +
> ++INIT_PY_SNIPPET = """
> ++from . import {0}
> ++__version__ = {0}.get_versions()['version']
> ++"""
> ++
> +
> +-def do_setup():
> +-    """Main VCS-independent setup function for installing Versioneer."""
> ++def do_setup() -> int:
> ++    """Do main VCS-independent setup function for installing Versioneer."""
> +     root = get_root()
> +     try:
> +         cfg = get_config_from_root(root)
> +-    except (EnvironmentError, configparser.NoSectionError,
> +-            configparser.NoOptionError) as e:
> +-        if isinstance(e, (EnvironmentError, configparser.NoSectionError)):
> +-            print("Adding sample versioneer config to setup.cfg",
> +-                  file=sys.stderr)
> ++    except (OSError, configparser.NoSectionError, configparser.NoOptionError) as e:
> ++        if isinstance(e, (OSError, configparser.NoSectionError)):
> ++            print("Adding sample versioneer config to setup.cfg", file=sys.stderr)
> +             with open(os.path.join(root, "setup.cfg"), "a") as f:
> +                 f.write(SAMPLE_CONFIG)
> +         print(CONFIG_ERROR, file=sys.stderr)
> +@@ -1712,71 +2242,49 @@ def do_setup():
> +     print(" creating %s" % cfg.versionfile_source)
> +     with open(cfg.versionfile_source, "w") as f:
> +         LONG = LONG_VERSION_PY[cfg.VCS]
> +-        f.write(LONG % {"DOLLAR": "$",
> +-                        "STYLE": cfg.style,
> +-                        "TAG_PREFIX": cfg.tag_prefix,
> +-                        "PARENTDIR_PREFIX": cfg.parentdir_prefix,
> +-                        "VERSIONFILE_SOURCE": cfg.versionfile_source,
> +-                        })
> +-
> +-    ipy = os.path.join(os.path.dirname(cfg.versionfile_source),
> +-                       "__init__.py")
> ++        f.write(
> ++            LONG
> ++            % {
> ++                "DOLLAR": "$",
> ++                "STYLE": cfg.style,
> ++                "TAG_PREFIX": cfg.tag_prefix,
> ++                "PARENTDIR_PREFIX": cfg.parentdir_prefix,
> ++                "VERSIONFILE_SOURCE": cfg.versionfile_source,
> ++            }
> ++        )
> ++
> ++    ipy = os.path.join(os.path.dirname(cfg.versionfile_source), "__init__.py")
> ++    maybe_ipy: Optional[str] = ipy
> +     if os.path.exists(ipy):
> +         try:
> +             with open(ipy, "r") as f:
> +                 old = f.read()
> +-        except EnvironmentError:
> ++        except OSError:
> +             old = ""
> +-        if INIT_PY_SNIPPET not in old:
> ++        module = os.path.splitext(os.path.basename(cfg.versionfile_source))[0]
> ++        snippet = INIT_PY_SNIPPET.format(module)
> ++        if OLD_SNIPPET in old:
> ++            print(" replacing boilerplate in %s" % ipy)
> ++            with open(ipy, "w") as f:
> ++                f.write(old.replace(OLD_SNIPPET, snippet))
> ++        elif snippet not in old:
> +             print(" appending to %s" % ipy)
> +             with open(ipy, "a") as f:
> +-                f.write(INIT_PY_SNIPPET)
> ++                f.write(snippet)
> +         else:
> +             print(" %s unmodified" % ipy)
> +     else:
> +         print(" %s doesn't exist, ok" % ipy)
> +-        ipy = None
> +-
> +-    # Make sure both the top-level "versioneer.py" and versionfile_source
> +-    # (PKG/_version.py, used by runtime code) are in MANIFEST.in, so
> +-    # they'll be copied into source distributions. Pip won't be able to
> +-    # install the package without this.
> +-    manifest_in = os.path.join(root, "MANIFEST.in")
> +-    simple_includes = set()
> +-    try:
> +-        with open(manifest_in, "r") as f:
> +-            for line in f:
> +-                if line.startswith("include "):
> +-                    for include in line.split()[1:]:
> +-                        simple_includes.add(include)
> +-    except EnvironmentError:
> +-        pass
> +-    # That doesn't cover everything MANIFEST.in can do
> +-    # (http://docs.python.org/2/distutils/sourcedist.html#commands), so
> +-    # it might give some false negatives. Appending redundant 'include'
> +-    # lines is safe, though.
> +-    if "versioneer.py" not in simple_includes:
> +-        print(" appending 'versioneer.py' to MANIFEST.in")
> +-        with open(manifest_in, "a") as f:
> +-            f.write("include versioneer.py\n")
> +-    else:
> +-        print(" 'versioneer.py' already in MANIFEST.in")
> +-    if cfg.versionfile_source not in simple_includes:
> +-        print(" appending versionfile_source ('%s') to MANIFEST.in" %
> +-              cfg.versionfile_source)
> +-        with open(manifest_in, "a") as f:
> +-            f.write("include %s\n" % cfg.versionfile_source)
> +-    else:
> +-        print(" versionfile_source already in MANIFEST.in")
> ++        maybe_ipy = None
> +
> +     # Make VCS-specific changes. For git, this means creating/changing
> +     # .gitattributes to mark _version.py for export-subst keyword
> +     # substitution.
> +-    do_vcs_install(manifest_in, cfg.versionfile_source, ipy)
> ++    do_vcs_install(cfg.versionfile_source, maybe_ipy)
> +     return 0
> +
> +
> +-def scan_setup_py():
> ++def scan_setup_py() -> int:
> +     """Validate the contents of setup.py against Versioneer's expectations."""
> +     found = set()
> +     setters = False
> +@@ -1813,10 +2321,14 @@ def scan_setup_py():
> +     return errors
> +
> +
> ++def setup_command() -> NoReturn:
> ++    """Set up Versioneer and exit with appropriate error code."""
> ++    errors = do_setup()
> ++    errors += scan_setup_py()
> ++    sys.exit(1 if errors else 0)
> ++
> ++
> + if __name__ == "__main__":
> +     cmd = sys.argv[1]
> +     if cmd == "setup":
> +-        errors = do_setup()
> +-        errors += scan_setup_py()
> +-        if errors:
> +-            sys.exit(1)
> ++        setup_command()
> +--
> +2.41.0
> +

_______________________________________________
buildroot mailing list
buildroot@buildroot.org
https://lists.buildroot.org/mailman/listinfo/buildroot


^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: [Buildroot] [PATCH 24/30] package/python-spake2: update versioneer to 0.29
  2023-10-26  9:26 ` [Buildroot] [PATCH 24/30] package/python-spake2: " Adam Duskett
@ 2023-11-04 21:32   ` Arnout Vandecappelle via buildroot
  0 siblings, 0 replies; 54+ messages in thread
From: Arnout Vandecappelle via buildroot @ 2023-11-04 21:32 UTC (permalink / raw)
  To: Adam Duskett, buildroot
  Cc: Andrey Smirnov, James Hilliard, Asaf Kahlon, Julien Olivain,
	Thomas Petazzoni, Mauro Condarelli



On 26/10/2023 11:26, Adam Duskett wrote:
> Versioneer < 0.21 is incompatible with Python 3.12.0. Use the latest version
> which is 0.29 as of this commit.
> 
> Signed-off-by: Adam Duskett <adam.duskett@amarulasolutions.com>
> ---
>   .../0001-Update-versioneer-to-0.29.patch      | 2194 +++++++++++++++++
>   1 file changed, 2194 insertions(+)
>   create mode 100644 package/python-spake2/0001-Update-versioneer-to-0.29.patch
> 
> diff --git a/package/python-spake2/0001-Update-versioneer-to-0.29.patch b/package/python-spake2/0001-Update-versioneer-to-0.29.patch
> new file mode 100644
> index 0000000000..3b5db5342c
> --- /dev/null
> +++ b/package/python-spake2/0001-Update-versioneer-to-0.29.patch
> @@ -0,0 +1,2194 @@
> +From 5b5436f11d01e66505bb4c148304c2eb49346529 Mon Sep 17 00:00:00 2001
> +From: Adam Duskett <adam.duskett@amarulasolutions.com>
> +Date: Tue, 24 Oct 2023 09:56:57 +0200
> +Subject: [PATCH] Update versioneer to 0.29
> +
> +Fixes builds against Python 3.12.0
> +
> +Upstream: https://github.com/warner/python-spake2/pull/15

  Upstream hasn't had a commit in 5 years so I don't know if this will ever get 
merged...

  Applied to master, thanks.

  Regards,
  Arnout

> +
> +Signed-off-by: Adam Duskett <adam.duskett@amarulasolutions.com>
> +---
> + versioneer.py | 1350 ++++++++++++++++++++++++++++++++++---------------
> + 1 file changed, 931 insertions(+), 419 deletions(-)
> +
> +diff --git a/versioneer.py b/versioneer.py
> +index 64fea1c..de97d90 100644
> +--- a/versioneer.py
> ++++ b/versioneer.py
> +@@ -1,5 +1,4 @@
> +-
> +-# Version: 0.18
> ++# Version: 0.29
> +
> + """The Versioneer - like a rocketeer, but for versions.
> +
> +@@ -7,18 +6,14 @@ The Versioneer
> + ==============
> +
> + * like a rocketeer, but for versions!
> +-* https://github.com/warner/python-versioneer
> ++* https://github.com/python-versioneer/python-versioneer
> + * Brian Warner
> +-* License: Public Domain
> +-* Compatible With: python2.6, 2.7, 3.2, 3.3, 3.4, 3.5, 3.6, and pypy
> +-* [![Latest Version]
> +-(https://pypip.in/version/versioneer/badge.svg?style=flat)
> +-](https://pypi.python.org/pypi/versioneer/)
> +-* [![Build Status]
> +-(https://travis-ci.org/warner/python-versioneer.png?branch=master)
> +-](https://travis-ci.org/warner/python-versioneer)
> +-
> +-This is a tool for managing a recorded version number in distutils-based
> ++* License: Public Domain (Unlicense)
> ++* Compatible with: Python 3.7, 3.8, 3.9, 3.10, 3.11 and pypy3
> ++* [![Latest Version][pypi-image]][pypi-url]
> ++* [![Build Status][travis-image]][travis-url]
> ++
> ++This is a tool for managing a recorded version number in setuptools-based
> + python projects. The goal is to remove the tedious and error-prone "update
> + the embedded version string" step from your release process. Making a new
> + release should be as easy as recording a new tag in your version-control
> +@@ -27,9 +22,38 @@ system, and maybe making new tarballs.
> +
> + ## Quick Install
> +
> +-* `pip install versioneer` to somewhere to your $PATH
> +-* add a `[versioneer]` section to your setup.cfg (see below)
> +-* run `versioneer install` in your source tree, commit the results
> ++Versioneer provides two installation modes. The "classic" vendored mode installs
> ++a copy of versioneer into your repository. The experimental build-time dependency mode
> ++is intended to allow you to skip this step and simplify the process of upgrading.
> ++
> ++### Vendored mode
> ++
> ++* `pip install versioneer` to somewhere in your $PATH
> ++   * A [conda-forge recipe](https://github.com/conda-forge/versioneer-feedstock) is
> ++     available, so you can also use `conda install -c conda-forge versioneer`
> ++* add a `[tool.versioneer]` section to your `pyproject.toml` or a
> ++  `[versioneer]` section to your `setup.cfg` (see [Install](INSTALL.md))
> ++   * Note that you will need to add `tomli; python_version < "3.11"` to your
> ++     build-time dependencies if you use `pyproject.toml`
> ++* run `versioneer install --vendor` in your source tree, commit the results
> ++* verify version information with `python setup.py version`
> ++
> ++### Build-time dependency mode
> ++
> ++* `pip install versioneer` to somewhere in your $PATH
> ++   * A [conda-forge recipe](https://github.com/conda-forge/versioneer-feedstock) is
> ++     available, so you can also use `conda install -c conda-forge versioneer`
> ++* add a `[tool.versioneer]` section to your `pyproject.toml` or a
> ++  `[versioneer]` section to your `setup.cfg` (see [Install](INSTALL.md))
> ++* add `versioneer` (with `[toml]` extra, if configuring in `pyproject.toml`)
> ++  to the `requires` key of the `build-system` table in `pyproject.toml`:
> ++  ```toml
> ++  [build-system]
> ++  requires = ["setuptools", "versioneer[toml]"]
> ++  build-backend = "setuptools.build_meta"
> ++  ```
> ++* run `versioneer install --no-vendor` in your source tree, commit the results
> ++* verify version information with `python setup.py version`
> +
> + ## Version Identifiers
> +
> +@@ -61,7 +85,7 @@ version 1.3). Many VCS systems can report a description that captures this,
> + for example `git describe --tags --dirty --always` reports things like
> + "0.7-1-g574ab98-dirty" to indicate that the checkout is one revision past the
> + 0.7 tag, has a unique revision id of "574ab98", and is "dirty" (it has
> +-uncommitted changes.
> ++uncommitted changes).
> +
> + The version identifier is used for multiple purposes:
> +
> +@@ -166,7 +190,7 @@ which may help identify what went wrong).
> +
> + Some situations are known to cause problems for Versioneer. This details the
> + most significant ones. More can be found on Github
> +-[issues page](https://github.com/warner/python-versioneer/issues).
> ++[issues page](https://github.com/python-versioneer/python-versioneer/issues).
> +
> + ### Subprojects
> +
> +@@ -180,7 +204,7 @@ two common reasons why `setup.py` might not be in the root:
> +   `setup.cfg`, and `tox.ini`. Projects like these produce multiple PyPI
> +   distributions (and upload multiple independently-installable tarballs).
> + * Source trees whose main purpose is to contain a C library, but which also
> +-  provide bindings to Python (and perhaps other langauges) in subdirectories.
> ++  provide bindings to Python (and perhaps other languages) in subdirectories.
> +
> + Versioneer will look for `.git` in parent directories, and most operations
> + should get the right version string. However `pip` and `setuptools` have bugs
> +@@ -194,9 +218,9 @@ work too.
> + Pip-8.1.1 is known to have this problem, but hopefully it will get fixed in
> + some later version.
> +
> +-[Bug #38](https://github.com/warner/python-versioneer/issues/38) is tracking
> ++[Bug #38](https://github.com/python-versioneer/python-versioneer/issues/38) is tracking
> + this issue. The discussion in
> +-[PR #61](https://github.com/warner/python-versioneer/pull/61) describes the
> ++[PR #61](https://github.com/python-versioneer/python-versioneer/pull/61) describes the
> + issue from the Versioneer side in more detail.
> + [pip PR#3176](https://github.com/pypa/pip/pull/3176) and
> + [pip PR#3615](https://github.com/pypa/pip/pull/3615) contain work to improve
> +@@ -224,31 +248,20 @@ regenerated while a different version is checked out. Many setup.py commands
> + cause egg_info to be rebuilt (including `sdist`, `wheel`, and installing into
> + a different virtualenv), so this can be surprising.
> +
> +-[Bug #83](https://github.com/warner/python-versioneer/issues/83) describes
> ++[Bug #83](https://github.com/python-versioneer/python-versioneer/issues/83) describes
> + this one, but upgrading to a newer version of setuptools should probably
> + resolve it.
> +
> +-### Unicode version strings
> +-
> +-While Versioneer works (and is continually tested) with both Python 2 and
> +-Python 3, it is not entirely consistent with bytes-vs-unicode distinctions.
> +-Newer releases probably generate unicode version strings on py2. It's not
> +-clear that this is wrong, but it may be surprising for applications when then
> +-write these strings to a network connection or include them in bytes-oriented
> +-APIs like cryptographic checksums.
> +-
> +-[Bug #71](https://github.com/warner/python-versioneer/issues/71) investigates
> +-this question.
> +-
> +
> + ## Updating Versioneer
> +
> + To upgrade your project to a new release of Versioneer, do the following:
> +
> + * install the new Versioneer (`pip install -U versioneer` or equivalent)
> +-* edit `setup.cfg`, if necessary, to include any new configuration settings
> +-  indicated by the release notes. See [UPGRADING](./UPGRADING.md) for details.
> +-* re-run `versioneer install` in your source tree, to replace
> ++* edit `setup.cfg` and `pyproject.toml`, if necessary,
> ++  to include any new configuration settings indicated by the release notes.
> ++  See [UPGRADING](./UPGRADING.md) for details.
> ++* re-run `versioneer install --[no-]vendor` in your source tree, to replace
> +   `SRC/_version.py`
> + * commit any changed files
> +
> +@@ -265,35 +278,70 @@ installation by editing setup.py . Alternatively, it might go the other
> + direction and include code from all supported VCS systems, reducing the
> + number of intermediate scripts.
> +
> ++## Similar projects
> ++
> ++* [setuptools_scm](https://github.com/pypa/setuptools_scm/) - a non-vendored build-time
> ++  dependency
> ++* [minver](https://github.com/jbweston/miniver) - a lightweight reimplementation of
> ++  versioneer
> ++* [versioningit](https://github.com/jwodder/versioningit) - a PEP 518-based setuptools
> ++  plugin
> +
> + ## License
> +
> + To make Versioneer easier to embed, all its code is dedicated to the public
> + domain. The `_version.py` that it creates is also in the public domain.
> +-Specifically, both are released under the Creative Commons "Public Domain
> +-Dedication" license (CC0-1.0), as described in
> +-https://creativecommons.org/publicdomain/zero/1.0/ .
> ++Specifically, both are released under the "Unlicense", as described in
> ++https://unlicense.org/.
> ++
> ++[pypi-image]: https://img.shields.io/pypi/v/versioneer.svg
> ++[pypi-url]: https://pypi.python.org/pypi/versioneer/
> ++[travis-image]:
> ++https://img.shields.io/travis/com/python-versioneer/python-versioneer.svg
> ++[travis-url]: https://travis-ci.com/github/python-versioneer/python-versioneer
> +
> + """
> ++# pylint:disable=invalid-name,import-outside-toplevel,missing-function-docstring
> ++# pylint:disable=missing-class-docstring,too-many-branches,too-many-statements
> ++# pylint:disable=raise-missing-from,too-many-lines,too-many-locals,import-error
> ++# pylint:disable=too-few-public-methods,redefined-outer-name,consider-using-with
> ++# pylint:disable=attribute-defined-outside-init,too-many-arguments
> +
> +-from __future__ import print_function
> +-try:
> +-    import configparser
> +-except ImportError:
> +-    import ConfigParser as configparser
> ++import configparser
> + import errno
> + import json
> + import os
> + import re
> + import subprocess
> + import sys
> ++from pathlib import Path
> ++from typing import Any, Callable, cast, Dict, List, Optional, Tuple, Union
> ++from typing import NoReturn
> ++import functools
> ++
> ++have_tomllib = True
> ++if sys.version_info >= (3, 11):
> ++    import tomllib
> ++else:
> ++    try:
> ++        import tomli as tomllib
> ++    except ImportError:
> ++        have_tomllib = False
> +
> +
> + class VersioneerConfig:
> +     """Container for Versioneer configuration parameters."""
> +
> ++    VCS: str
> ++    style: str
> ++    tag_prefix: str
> ++    versionfile_source: str
> ++    versionfile_build: Optional[str]
> ++    parentdir_prefix: Optional[str]
> ++    verbose: Optional[bool]
> ++
> +
> +-def get_root():
> ++def get_root() -> str:
> +     """Get the project root directory.
> +
> +     We require that all commands are run from the project root, i.e. the
> +@@ -301,18 +349,30 @@ def get_root():
> +     """
> +     root = os.path.realpath(os.path.abspath(os.getcwd()))
> +     setup_py = os.path.join(root, "setup.py")
> ++    pyproject_toml = os.path.join(root, "pyproject.toml")
> +     versioneer_py = os.path.join(root, "versioneer.py")
> +-    if not (os.path.exists(setup_py) or os.path.exists(versioneer_py)):
> ++    if not (
> ++        os.path.exists(setup_py)
> ++        or os.path.exists(pyproject_toml)
> ++        or os.path.exists(versioneer_py)
> ++    ):
> +         # allow 'python path/to/setup.py COMMAND'
> +         root = os.path.dirname(os.path.realpath(os.path.abspath(sys.argv[0])))
> +         setup_py = os.path.join(root, "setup.py")
> ++        pyproject_toml = os.path.join(root, "pyproject.toml")
> +         versioneer_py = os.path.join(root, "versioneer.py")
> +-    if not (os.path.exists(setup_py) or os.path.exists(versioneer_py)):
> +-        err = ("Versioneer was unable to run the project root directory. "
> +-               "Versioneer requires setup.py to be executed from "
> +-               "its immediate directory (like 'python setup.py COMMAND'), "
> +-               "or in a way that lets it use sys.argv[0] to find the root "
> +-               "(like 'python path/to/setup.py COMMAND').")
> ++    if not (
> ++        os.path.exists(setup_py)
> ++        or os.path.exists(pyproject_toml)
> ++        or os.path.exists(versioneer_py)
> ++    ):
> ++        err = (
> ++            "Versioneer was unable to run the project root directory. "
> ++            "Versioneer requires setup.py to be executed from "
> ++            "its immediate directory (like 'python setup.py COMMAND'), "
> ++            "or in a way that lets it use sys.argv[0] to find the root "
> ++            "(like 'python path/to/setup.py COMMAND')."
> ++        )
> +         raise VersioneerBadRootError(err)
> +     try:
> +         # Certain runtime workflows (setup.py install/develop in a setuptools
> +@@ -321,43 +381,64 @@ def get_root():
> +         # module-import table will cache the first one. So we can't use
> +         # os.path.dirname(__file__), as that will find whichever
> +         # versioneer.py was first imported, even in later projects.
> +-        me = os.path.realpath(os.path.abspath(__file__))
> +-        me_dir = os.path.normcase(os.path.splitext(me)[0])
> ++        my_path = os.path.realpath(os.path.abspath(__file__))
> ++        me_dir = os.path.normcase(os.path.splitext(my_path)[0])
> +         vsr_dir = os.path.normcase(os.path.splitext(versioneer_py)[0])
> +-        if me_dir != vsr_dir:
> +-            print("Warning: build in %s is using versioneer.py from %s"
> +-                  % (os.path.dirname(me), versioneer_py))
> ++        if me_dir != vsr_dir and "VERSIONEER_PEP518" not in globals():
> ++            print(
> ++                "Warning: build in %s is using versioneer.py from %s"
> ++                % (os.path.dirname(my_path), versioneer_py)
> ++            )
> +     except NameError:
> +         pass
> +     return root
> +
> +
> +-def get_config_from_root(root):
> ++def get_config_from_root(root: str) -> VersioneerConfig:
> +     """Read the project setup.cfg file to determine Versioneer config."""
> +-    # This might raise EnvironmentError (if setup.cfg is missing), or
> ++    # This might raise OSError (if setup.cfg is missing), or
> +     # configparser.NoSectionError (if it lacks a [versioneer] section), or
> +     # configparser.NoOptionError (if it lacks "VCS="). See the docstring at
> +     # the top of versioneer.py for instructions on writing your setup.cfg .
> +-    setup_cfg = os.path.join(root, "setup.cfg")
> +-    parser = configparser.SafeConfigParser()
> +-    with open(setup_cfg, "r") as f:
> +-        parser.readfp(f)
> +-    VCS = parser.get("versioneer", "VCS")  # mandatory
> +-
> +-    def get(parser, name):
> +-        if parser.has_option("versioneer", name):
> +-            return parser.get("versioneer", name)
> +-        return None
> ++    root_pth = Path(root)
> ++    pyproject_toml = root_pth / "pyproject.toml"
> ++    setup_cfg = root_pth / "setup.cfg"
> ++    section: Union[Dict[str, Any], configparser.SectionProxy, None] = None
> ++    if pyproject_toml.exists() and have_tomllib:
> ++        try:
> ++            with open(pyproject_toml, "rb") as fobj:
> ++                pp = tomllib.load(fobj)
> ++            section = pp["tool"]["versioneer"]
> ++        except (tomllib.TOMLDecodeError, KeyError) as e:
> ++            print(f"Failed to load config from {pyproject_toml}: {e}")
> ++            print("Try to load it from setup.cfg")
> ++    if not section:
> ++        parser = configparser.ConfigParser()
> ++        with open(setup_cfg) as cfg_file:
> ++            parser.read_file(cfg_file)
> ++        parser.get("versioneer", "VCS")  # raise error if missing
> ++
> ++        section = parser["versioneer"]
> ++
> ++    # `cast`` really shouldn't be used, but its simplest for the
> ++    # common VersioneerConfig users at the moment. We verify against
> ++    # `None` values elsewhere where it matters
> ++
> +     cfg = VersioneerConfig()
> +-    cfg.VCS = VCS
> +-    cfg.style = get(parser, "style") or ""
> +-    cfg.versionfile_source = get(parser, "versionfile_source")
> +-    cfg.versionfile_build = get(parser, "versionfile_build")
> +-    cfg.tag_prefix = get(parser, "tag_prefix")
> +-    if cfg.tag_prefix in ("''", '""'):
> ++    cfg.VCS = section["VCS"]
> ++    cfg.style = section.get("style", "")
> ++    cfg.versionfile_source = cast(str, section.get("versionfile_source"))
> ++    cfg.versionfile_build = section.get("versionfile_build")
> ++    cfg.tag_prefix = cast(str, section.get("tag_prefix"))
> ++    if cfg.tag_prefix in ("''", '""', None):
> +         cfg.tag_prefix = ""
> +-    cfg.parentdir_prefix = get(parser, "parentdir_prefix")
> +-    cfg.verbose = get(parser, "verbose")
> ++    cfg.parentdir_prefix = section.get("parentdir_prefix")
> ++    if isinstance(section, configparser.SectionProxy):
> ++        # Make sure configparser translates to bool
> ++        cfg.verbose = section.getboolean("verbose")
> ++    else:
> ++        cfg.verbose = section.get("verbose")
> ++
> +     return cfg
> +
> +
> +@@ -366,37 +447,54 @@ class NotThisMethod(Exception):
> +
> +
> + # these dictionaries contain VCS-specific tools
> +-LONG_VERSION_PY = {}
> +-HANDLERS = {}
> ++LONG_VERSION_PY: Dict[str, str] = {}
> ++HANDLERS: Dict[str, Dict[str, Callable]] = {}
> +
> +
> +-def register_vcs_handler(vcs, method):  # decorator
> +-    """Decorator to mark a method as the handler for a particular VCS."""
> +-    def decorate(f):
> ++def register_vcs_handler(vcs: str, method: str) -> Callable:  # decorator
> ++    """Create decorator to mark a method as the handler of a VCS."""
> ++
> ++    def decorate(f: Callable) -> Callable:
> +         """Store f in HANDLERS[vcs][method]."""
> +-        if vcs not in HANDLERS:
> +-            HANDLERS[vcs] = {}
> +-        HANDLERS[vcs][method] = f
> ++        HANDLERS.setdefault(vcs, {})[method] = f
> +         return f
> ++
> +     return decorate
> +
> +
> +-def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False,
> +-                env=None):
> ++def run_command(
> ++    commands: List[str],
> ++    args: List[str],
> ++    cwd: Optional[str] = None,
> ++    verbose: bool = False,
> ++    hide_stderr: bool = False,
> ++    env: Optional[Dict[str, str]] = None,
> ++) -> Tuple[Optional[str], Optional[int]]:
> +     """Call the given command(s)."""
> +     assert isinstance(commands, list)
> +-    p = None
> +-    for c in commands:
> ++    process = None
> ++
> ++    popen_kwargs: Dict[str, Any] = {}
> ++    if sys.platform == "win32":
> ++        # This hides the console window if pythonw.exe is used
> ++        startupinfo = subprocess.STARTUPINFO()
> ++        startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
> ++        popen_kwargs["startupinfo"] = startupinfo
> ++
> ++    for command in commands:
> +         try:
> +-            dispcmd = str([c] + args)
> ++            dispcmd = str([command] + args)
> +             # remember shell=False, so use git.cmd on windows, not just git
> +-            p = subprocess.Popen([c] + args, cwd=cwd, env=env,
> +-                                 stdout=subprocess.PIPE,
> +-                                 stderr=(subprocess.PIPE if hide_stderr
> +-                                         else None))
> ++            process = subprocess.Popen(
> ++                [command] + args,
> ++                cwd=cwd,
> ++                env=env,
> ++                stdout=subprocess.PIPE,
> ++                stderr=(subprocess.PIPE if hide_stderr else None),
> ++                **popen_kwargs,
> ++            )
> +             break
> +-        except EnvironmentError:
> +-            e = sys.exc_info()[1]
> ++        except OSError as e:
> +             if e.errno == errno.ENOENT:
> +                 continue
> +             if verbose:
> +@@ -407,26 +505,27 @@ def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False,
> +         if verbose:
> +             print("unable to find command, tried %s" % (commands,))
> +         return None, None
> +-    stdout = p.communicate()[0].strip()
> +-    if sys.version_info[0] >= 3:
> +-        stdout = stdout.decode()
> +-    if p.returncode != 0:
> ++    stdout = process.communicate()[0].strip().decode()
> ++    if process.returncode != 0:
> +         if verbose:
> +             print("unable to run %s (error)" % dispcmd)
> +             print("stdout was %s" % stdout)
> +-        return None, p.returncode
> +-    return stdout, p.returncode
> ++        return None, process.returncode
> ++    return stdout, process.returncode
> +
> +
> +-LONG_VERSION_PY['git'] = '''
> ++LONG_VERSION_PY[
> ++    "git"
> ++] = r'''
> + # This file helps to compute a version number in source trees obtained from
> + # git-archive tarball (such as those provided by githubs download-from-tag
> + # feature). Distribution tarballs (built by setup.py sdist) and build
> + # directories (produced by setup.py build) will contain a much shorter file
> + # that just contains the computed version number.
> +
> +-# This file is released into the public domain. Generated by
> +-# versioneer-0.18 (https://github.com/warner/python-versioneer)
> ++# This file is released into the public domain.
> ++# Generated by versioneer-0.29
> ++# https://github.com/python-versioneer/python-versioneer
> +
> + """Git implementation of _version.py."""
> +
> +@@ -435,9 +534,11 @@ import os
> + import re
> + import subprocess
> + import sys
> ++from typing import Any, Callable, Dict, List, Optional, Tuple
> ++import functools
> +
> +
> +-def get_keywords():
> ++def get_keywords() -> Dict[str, str]:
> +     """Get the keywords needed to look up the version information."""
> +     # these strings will be replaced by git during git-archive.
> +     # setup.py/versioneer.py will grep for the variable names, so they must
> +@@ -453,8 +554,15 @@ def get_keywords():
> + class VersioneerConfig:
> +     """Container for Versioneer configuration parameters."""
> +
> ++    VCS: str
> ++    style: str
> ++    tag_prefix: str
> ++    parentdir_prefix: str
> ++    versionfile_source: str
> ++    verbose: bool
> ++
> +
> +-def get_config():
> ++def get_config() -> VersioneerConfig:
> +     """Create, populate and return the VersioneerConfig() object."""
> +     # these strings are filled in when 'setup.py versioneer' creates
> +     # _version.py
> +@@ -472,13 +580,13 @@ class NotThisMethod(Exception):
> +     """Exception raised if a method is not valid for the current scenario."""
> +
> +
> +-LONG_VERSION_PY = {}
> +-HANDLERS = {}
> ++LONG_VERSION_PY: Dict[str, str] = {}
> ++HANDLERS: Dict[str, Dict[str, Callable]] = {}
> +
> +
> +-def register_vcs_handler(vcs, method):  # decorator
> +-    """Decorator to mark a method as the handler for a particular VCS."""
> +-    def decorate(f):
> ++def register_vcs_handler(vcs: str, method: str) -> Callable:  # decorator
> ++    """Create decorator to mark a method as the handler of a VCS."""
> ++    def decorate(f: Callable) -> Callable:
> +         """Store f in HANDLERS[vcs][method]."""
> +         if vcs not in HANDLERS:
> +             HANDLERS[vcs] = {}
> +@@ -487,22 +595,35 @@ def register_vcs_handler(vcs, method):  # decorator
> +     return decorate
> +
> +
> +-def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False,
> +-                env=None):
> ++def run_command(
> ++    commands: List[str],
> ++    args: List[str],
> ++    cwd: Optional[str] = None,
> ++    verbose: bool = False,
> ++    hide_stderr: bool = False,
> ++    env: Optional[Dict[str, str]] = None,
> ++) -> Tuple[Optional[str], Optional[int]]:
> +     """Call the given command(s)."""
> +     assert isinstance(commands, list)
> +-    p = None
> +-    for c in commands:
> ++    process = None
> ++
> ++    popen_kwargs: Dict[str, Any] = {}
> ++    if sys.platform == "win32":
> ++        # This hides the console window if pythonw.exe is used
> ++        startupinfo = subprocess.STARTUPINFO()
> ++        startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
> ++        popen_kwargs["startupinfo"] = startupinfo
> ++
> ++    for command in commands:
> +         try:
> +-            dispcmd = str([c] + args)
> ++            dispcmd = str([command] + args)
> +             # remember shell=False, so use git.cmd on windows, not just git
> +-            p = subprocess.Popen([c] + args, cwd=cwd, env=env,
> +-                                 stdout=subprocess.PIPE,
> +-                                 stderr=(subprocess.PIPE if hide_stderr
> +-                                         else None))
> ++            process = subprocess.Popen([command] + args, cwd=cwd, env=env,
> ++                                       stdout=subprocess.PIPE,
> ++                                       stderr=(subprocess.PIPE if hide_stderr
> ++                                               else None), **popen_kwargs)
> +             break
> +-        except EnvironmentError:
> +-            e = sys.exc_info()[1]
> ++        except OSError as e:
> +             if e.errno == errno.ENOENT:
> +                 continue
> +             if verbose:
> +@@ -513,18 +634,20 @@ def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False,
> +         if verbose:
> +             print("unable to find command, tried %%s" %% (commands,))
> +         return None, None
> +-    stdout = p.communicate()[0].strip()
> +-    if sys.version_info[0] >= 3:
> +-        stdout = stdout.decode()
> +-    if p.returncode != 0:
> ++    stdout = process.communicate()[0].strip().decode()
> ++    if process.returncode != 0:
> +         if verbose:
> +             print("unable to run %%s (error)" %% dispcmd)
> +             print("stdout was %%s" %% stdout)
> +-        return None, p.returncode
> +-    return stdout, p.returncode
> ++        return None, process.returncode
> ++    return stdout, process.returncode
> +
> +
> +-def versions_from_parentdir(parentdir_prefix, root, verbose):
> ++def versions_from_parentdir(
> ++    parentdir_prefix: str,
> ++    root: str,
> ++    verbose: bool,
> ++) -> Dict[str, Any]:
> +     """Try to determine the version from the parent directory name.
> +
> +     Source tarballs conventionally unpack into a directory that includes both
> +@@ -533,15 +656,14 @@ def versions_from_parentdir(parentdir_prefix, root, verbose):
> +     """
> +     rootdirs = []
> +
> +-    for i in range(3):
> ++    for _ in range(3):
> +         dirname = os.path.basename(root)
> +         if dirname.startswith(parentdir_prefix):
> +             return {"version": dirname[len(parentdir_prefix):],
> +                     "full-revisionid": None,
> +                     "dirty": False, "error": None, "date": None}
> +-        else:
> +-            rootdirs.append(root)
> +-            root = os.path.dirname(root)  # up a level
> ++        rootdirs.append(root)
> ++        root = os.path.dirname(root)  # up a level
> +
> +     if verbose:
> +         print("Tried directories %%s but none started with prefix %%s" %%
> +@@ -550,41 +672,48 @@ def versions_from_parentdir(parentdir_prefix, root, verbose):
> +
> +
> + @register_vcs_handler("git", "get_keywords")
> +-def git_get_keywords(versionfile_abs):
> ++def git_get_keywords(versionfile_abs: str) -> Dict[str, str]:
> +     """Extract version information from the given file."""
> +     # the code embedded in _version.py can just fetch the value of these
> +     # keywords. When used from setup.py, we don't want to import _version.py,
> +     # so we do it with a regexp instead. This function is not used from
> +     # _version.py.
> +-    keywords = {}
> ++    keywords: Dict[str, str] = {}
> +     try:
> +-        f = open(versionfile_abs, "r")
> +-        for line in f.readlines():
> +-            if line.strip().startswith("git_refnames ="):
> +-                mo = re.search(r'=\s*"(.*)"', line)
> +-                if mo:
> +-                    keywords["refnames"] = mo.group(1)
> +-            if line.strip().startswith("git_full ="):
> +-                mo = re.search(r'=\s*"(.*)"', line)
> +-                if mo:
> +-                    keywords["full"] = mo.group(1)
> +-            if line.strip().startswith("git_date ="):
> +-                mo = re.search(r'=\s*"(.*)"', line)
> +-                if mo:
> +-                    keywords["date"] = mo.group(1)
> +-        f.close()
> +-    except EnvironmentError:
> ++        with open(versionfile_abs, "r") as fobj:
> ++            for line in fobj:
> ++                if line.strip().startswith("git_refnames ="):
> ++                    mo = re.search(r'=\s*"(.*)"', line)
> ++                    if mo:
> ++                        keywords["refnames"] = mo.group(1)
> ++                if line.strip().startswith("git_full ="):
> ++                    mo = re.search(r'=\s*"(.*)"', line)
> ++                    if mo:
> ++                        keywords["full"] = mo.group(1)
> ++                if line.strip().startswith("git_date ="):
> ++                    mo = re.search(r'=\s*"(.*)"', line)
> ++                    if mo:
> ++                        keywords["date"] = mo.group(1)
> ++    except OSError:
> +         pass
> +     return keywords
> +
> +
> + @register_vcs_handler("git", "keywords")
> +-def git_versions_from_keywords(keywords, tag_prefix, verbose):
> ++def git_versions_from_keywords(
> ++    keywords: Dict[str, str],
> ++    tag_prefix: str,
> ++    verbose: bool,
> ++) -> Dict[str, Any]:
> +     """Get version information from git keywords."""
> +-    if not keywords:
> +-        raise NotThisMethod("no keywords at all, weird")
> ++    if "refnames" not in keywords:
> ++        raise NotThisMethod("Short version file found")
> +     date = keywords.get("date")
> +     if date is not None:
> ++        # Use only the last line.  Previous lines may contain GPG signature
> ++        # information.
> ++        date = date.splitlines()[-1]
> ++
> +         # git-2.2.0 added "%%cI", which expands to an ISO-8601 -compliant
> +         # datestamp. However we prefer "%%ci" (which expands to an "ISO-8601
> +         # -like" string, which we must then edit to make compliant), because
> +@@ -597,11 +726,11 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose):
> +         if verbose:
> +             print("keywords are unexpanded, not using")
> +         raise NotThisMethod("unexpanded keywords, not a git-archive tarball")
> +-    refs = set([r.strip() for r in refnames.strip("()").split(",")])
> ++    refs = {r.strip() for r in refnames.strip("()").split(",")}
> +     # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of
> +     # just "foo-1.0". If we see a "tag: " prefix, prefer those.
> +     TAG = "tag: "
> +-    tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)])
> ++    tags = {r[len(TAG):] for r in refs if r.startswith(TAG)}
> +     if not tags:
> +         # Either we're using git < 1.8.3, or there really are no tags. We use
> +         # a heuristic: assume all version tags have a digit. The old git %%d
> +@@ -610,7 +739,7 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose):
> +         # between branches and tags. By ignoring refnames without digits, we
> +         # filter out many common branch names like "release" and
> +         # "stabilization", as well as "HEAD" and "master".
> +-        tags = set([r for r in refs if re.search(r'\d', r)])
> ++        tags = {r for r in refs if re.search(r'\d', r)}
> +         if verbose:
> +             print("discarding '%%s', no digits" %% ",".join(refs - tags))
> +     if verbose:
> +@@ -619,6 +748,11 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose):
> +         # sorting will prefer e.g. "2.0" over "2.0rc1"
> +         if ref.startswith(tag_prefix):
> +             r = ref[len(tag_prefix):]
> ++            # Filter out refs that exactly match prefix or that don't start
> ++            # with a number once the prefix is stripped (mostly a concern
> ++            # when prefix is '')
> ++            if not re.match(r'\d', r):
> ++                continue
> +             if verbose:
> +                 print("picking %%s" %% r)
> +             return {"version": r,
> +@@ -634,7 +768,12 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose):
> +
> +
> + @register_vcs_handler("git", "pieces_from_vcs")
> +-def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
> ++def git_pieces_from_vcs(
> ++    tag_prefix: str,
> ++    root: str,
> ++    verbose: bool,
> ++    runner: Callable = run_command
> ++) -> Dict[str, Any]:
> +     """Get version from 'git describe' in the root of the source tree.
> +
> +     This only gets called if the git-archive 'subst' keywords were *not*
> +@@ -645,8 +784,15 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
> +     if sys.platform == "win32":
> +         GITS = ["git.cmd", "git.exe"]
> +
> +-    out, rc = run_command(GITS, ["rev-parse", "--git-dir"], cwd=root,
> +-                          hide_stderr=True)
> ++    # GIT_DIR can interfere with correct operation of Versioneer.
> ++    # It may be intended to be passed to the Versioneer-versioned project,
> ++    # but that should not change where we get our version from.
> ++    env = os.environ.copy()
> ++    env.pop("GIT_DIR", None)
> ++    runner = functools.partial(runner, env=env)
> ++
> ++    _, rc = runner(GITS, ["rev-parse", "--git-dir"], cwd=root,
> ++                   hide_stderr=not verbose)
> +     if rc != 0:
> +         if verbose:
> +             print("Directory %%s not under git control" %% root)
> +@@ -654,24 +800,57 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
> +
> +     # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty]
> +     # if there isn't one, this yields HEX[-dirty] (no NUM)
> +-    describe_out, rc = run_command(GITS, ["describe", "--tags", "--dirty",
> +-                                          "--always", "--long",
> +-                                          "--match", "%%s*" %% tag_prefix],
> +-                                   cwd=root)
> ++    describe_out, rc = runner(GITS, [
> ++        "describe", "--tags", "--dirty", "--always", "--long",
> ++        "--match", f"{tag_prefix}[[:digit:]]*"
> ++    ], cwd=root)
> +     # --long was added in git-1.5.5
> +     if describe_out is None:
> +         raise NotThisMethod("'git describe' failed")
> +     describe_out = describe_out.strip()
> +-    full_out, rc = run_command(GITS, ["rev-parse", "HEAD"], cwd=root)
> ++    full_out, rc = runner(GITS, ["rev-parse", "HEAD"], cwd=root)
> +     if full_out is None:
> +         raise NotThisMethod("'git rev-parse' failed")
> +     full_out = full_out.strip()
> +
> +-    pieces = {}
> ++    pieces: Dict[str, Any] = {}
> +     pieces["long"] = full_out
> +     pieces["short"] = full_out[:7]  # maybe improved later
> +     pieces["error"] = None
> +
> ++    branch_name, rc = runner(GITS, ["rev-parse", "--abbrev-ref", "HEAD"],
> ++                             cwd=root)
> ++    # --abbrev-ref was added in git-1.6.3
> ++    if rc != 0 or branch_name is None:
> ++        raise NotThisMethod("'git rev-parse --abbrev-ref' returned error")
> ++    branch_name = branch_name.strip()
> ++
> ++    if branch_name == "HEAD":
> ++        # If we aren't exactly on a branch, pick a branch which represents
> ++        # the current commit. If all else fails, we are on a branchless
> ++        # commit.
> ++        branches, rc = runner(GITS, ["branch", "--contains"], cwd=root)
> ++        # --contains was added in git-1.5.4
> ++        if rc != 0 or branches is None:
> ++            raise NotThisMethod("'git branch --contains' returned error")
> ++        branches = branches.split("\n")
> ++
> ++        # Remove the first line if we're running detached
> ++        if "(" in branches[0]:
> ++            branches.pop(0)
> ++
> ++        # Strip off the leading "* " from the list of branches.
> ++        branches = [branch[2:] for branch in branches]
> ++        if "master" in branches:
> ++            branch_name = "master"
> ++        elif not branches:
> ++            branch_name = None
> ++        else:
> ++            # Pick the first branch that is returned. Good or bad.
> ++            branch_name = branches[0]
> ++
> ++    pieces["branch"] = branch_name
> ++
> +     # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty]
> +     # TAG might have hyphens.
> +     git_describe = describe_out
> +@@ -688,7 +867,7 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
> +         # TAG-NUM-gHEX
> +         mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe)
> +         if not mo:
> +-            # unparseable. Maybe git-describe is misbehaving?
> ++            # unparsable. Maybe git-describe is misbehaving?
> +             pieces["error"] = ("unable to parse git-describe output: '%%s'"
> +                                %% describe_out)
> +             return pieces
> +@@ -713,26 +892,27 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
> +     else:
> +         # HEX: no tags
> +         pieces["closest-tag"] = None
> +-        count_out, rc = run_command(GITS, ["rev-list", "HEAD", "--count"],
> +-                                    cwd=root)
> +-        pieces["distance"] = int(count_out)  # total number of commits
> ++        out, rc = runner(GITS, ["rev-list", "HEAD", "--left-right"], cwd=root)
> ++        pieces["distance"] = len(out.split())  # total number of commits
> +
> +     # commit date: see ISO-8601 comment in git_versions_from_keywords()
> +-    date = run_command(GITS, ["show", "-s", "--format=%%ci", "HEAD"],
> +-                       cwd=root)[0].strip()
> ++    date = runner(GITS, ["show", "-s", "--format=%%ci", "HEAD"], cwd=root)[0].strip()
> ++    # Use only the last line.  Previous lines may contain GPG signature
> ++    # information.
> ++    date = date.splitlines()[-1]
> +     pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1)
> +
> +     return pieces
> +
> +
> +-def plus_or_dot(pieces):
> ++def plus_or_dot(pieces: Dict[str, Any]) -> str:
> +     """Return a + if we don't already have one, else return a ."""
> +     if "+" in pieces.get("closest-tag", ""):
> +         return "."
> +     return "+"
> +
> +
> +-def render_pep440(pieces):
> ++def render_pep440(pieces: Dict[str, Any]) -> str:
> +     """Build up version string, with post-release "local version identifier".
> +
> +     Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you
> +@@ -757,23 +937,71 @@ def render_pep440(pieces):
> +     return rendered
> +
> +
> +-def render_pep440_pre(pieces):
> +-    """TAG[.post.devDISTANCE] -- No -dirty.
> ++def render_pep440_branch(pieces: Dict[str, Any]) -> str:
> ++    """TAG[[.dev0]+DISTANCE.gHEX[.dirty]] .
> ++
> ++    The ".dev0" means not master branch. Note that .dev0 sorts backwards
> ++    (a feature branch will appear "older" than the master branch).
> +
> +     Exceptions:
> +-    1: no tags. 0.post.devDISTANCE
> ++    1: no tags. 0[.dev0]+untagged.DISTANCE.gHEX[.dirty]
> +     """
> +     if pieces["closest-tag"]:
> +         rendered = pieces["closest-tag"]
> ++        if pieces["distance"] or pieces["dirty"]:
> ++            if pieces["branch"] != "master":
> ++                rendered += ".dev0"
> ++            rendered += plus_or_dot(pieces)
> ++            rendered += "%%d.g%%s" %% (pieces["distance"], pieces["short"])
> ++            if pieces["dirty"]:
> ++                rendered += ".dirty"
> ++    else:
> ++        # exception #1
> ++        rendered = "0"
> ++        if pieces["branch"] != "master":
> ++            rendered += ".dev0"
> ++        rendered += "+untagged.%%d.g%%s" %% (pieces["distance"],
> ++                                          pieces["short"])
> ++        if pieces["dirty"]:
> ++            rendered += ".dirty"
> ++    return rendered
> ++
> ++
> ++def pep440_split_post(ver: str) -> Tuple[str, Optional[int]]:
> ++    """Split pep440 version string at the post-release segment.
> ++
> ++    Returns the release segments before the post-release and the
> ++    post-release version number (or -1 if no post-release segment is present).
> ++    """
> ++    vc = str.split(ver, ".post")
> ++    return vc[0], int(vc[1] or 0) if len(vc) == 2 else None
> ++
> ++
> ++def render_pep440_pre(pieces: Dict[str, Any]) -> str:
> ++    """TAG[.postN.devDISTANCE] -- No -dirty.
> ++
> ++    Exceptions:
> ++    1: no tags. 0.post0.devDISTANCE
> ++    """
> ++    if pieces["closest-tag"]:
> +         if pieces["distance"]:
> +-            rendered += ".post.dev%%d" %% pieces["distance"]
> ++            # update the post release segment
> ++            tag_version, post_version = pep440_split_post(pieces["closest-tag"])
> ++            rendered = tag_version
> ++            if post_version is not None:
> ++                rendered += ".post%%d.dev%%d" %% (post_version + 1, pieces["distance"])
> ++            else:
> ++                rendered += ".post0.dev%%d" %% (pieces["distance"])
> ++        else:
> ++            # no commits, use the tag as the version
> ++            rendered = pieces["closest-tag"]
> +     else:
> +         # exception #1
> +-        rendered = "0.post.dev%%d" %% pieces["distance"]
> ++        rendered = "0.post0.dev%%d" %% pieces["distance"]
> +     return rendered
> +
> +
> +-def render_pep440_post(pieces):
> ++def render_pep440_post(pieces: Dict[str, Any]) -> str:
> +     """TAG[.postDISTANCE[.dev0]+gHEX] .
> +
> +     The ".dev0" means dirty. Note that .dev0 sorts backwards
> +@@ -800,12 +1028,41 @@ def render_pep440_post(pieces):
> +     return rendered
> +
> +
> +-def render_pep440_old(pieces):
> ++def render_pep440_post_branch(pieces: Dict[str, Any]) -> str:
> ++    """TAG[.postDISTANCE[.dev0]+gHEX[.dirty]] .
> ++
> ++    The ".dev0" means not master branch.
> ++
> ++    Exceptions:
> ++    1: no tags. 0.postDISTANCE[.dev0]+gHEX[.dirty]
> ++    """
> ++    if pieces["closest-tag"]:
> ++        rendered = pieces["closest-tag"]
> ++        if pieces["distance"] or pieces["dirty"]:
> ++            rendered += ".post%%d" %% pieces["distance"]
> ++            if pieces["branch"] != "master":
> ++                rendered += ".dev0"
> ++            rendered += plus_or_dot(pieces)
> ++            rendered += "g%%s" %% pieces["short"]
> ++            if pieces["dirty"]:
> ++                rendered += ".dirty"
> ++    else:
> ++        # exception #1
> ++        rendered = "0.post%%d" %% pieces["distance"]
> ++        if pieces["branch"] != "master":
> ++            rendered += ".dev0"
> ++        rendered += "+g%%s" %% pieces["short"]
> ++        if pieces["dirty"]:
> ++            rendered += ".dirty"
> ++    return rendered
> ++
> ++
> ++def render_pep440_old(pieces: Dict[str, Any]) -> str:
> +     """TAG[.postDISTANCE[.dev0]] .
> +
> +     The ".dev0" means dirty.
> +
> +-    Eexceptions:
> ++    Exceptions:
> +     1: no tags. 0.postDISTANCE[.dev0]
> +     """
> +     if pieces["closest-tag"]:
> +@@ -822,7 +1079,7 @@ def render_pep440_old(pieces):
> +     return rendered
> +
> +
> +-def render_git_describe(pieces):
> ++def render_git_describe(pieces: Dict[str, Any]) -> str:
> +     """TAG[-DISTANCE-gHEX][-dirty].
> +
> +     Like 'git describe --tags --dirty --always'.
> +@@ -842,7 +1099,7 @@ def render_git_describe(pieces):
> +     return rendered
> +
> +
> +-def render_git_describe_long(pieces):
> ++def render_git_describe_long(pieces: Dict[str, Any]) -> str:
> +     """TAG-DISTANCE-gHEX[-dirty].
> +
> +     Like 'git describe --tags --dirty --always -long'.
> +@@ -862,7 +1119,7 @@ def render_git_describe_long(pieces):
> +     return rendered
> +
> +
> +-def render(pieces, style):
> ++def render(pieces: Dict[str, Any], style: str) -> Dict[str, Any]:
> +     """Render the given version pieces into the requested style."""
> +     if pieces["error"]:
> +         return {"version": "unknown",
> +@@ -876,10 +1133,14 @@ def render(pieces, style):
> +
> +     if style == "pep440":
> +         rendered = render_pep440(pieces)
> ++    elif style == "pep440-branch":
> ++        rendered = render_pep440_branch(pieces)
> +     elif style == "pep440-pre":
> +         rendered = render_pep440_pre(pieces)
> +     elif style == "pep440-post":
> +         rendered = render_pep440_post(pieces)
> ++    elif style == "pep440-post-branch":
> ++        rendered = render_pep440_post_branch(pieces)
> +     elif style == "pep440-old":
> +         rendered = render_pep440_old(pieces)
> +     elif style == "git-describe":
> +@@ -894,7 +1155,7 @@ def render(pieces, style):
> +             "date": pieces.get("date")}
> +
> +
> +-def get_versions():
> ++def get_versions() -> Dict[str, Any]:
> +     """Get version information or return default if unable to do so."""
> +     # I am in _version.py, which lives at ROOT/VERSIONFILE_SOURCE. If we have
> +     # __file__, we can work backwards from there to the root. Some
> +@@ -915,7 +1176,7 @@ def get_versions():
> +         # versionfile_source is the relative path from the top of the source
> +         # tree (where the .git directory might live) to this file. Invert
> +         # this to find the root from __file__.
> +-        for i in cfg.versionfile_source.split('/'):
> ++        for _ in cfg.versionfile_source.split('/'):
> +             root = os.path.dirname(root)
> +     except NameError:
> +         return {"version": "0+unknown", "full-revisionid": None,
> +@@ -942,41 +1203,48 @@ def get_versions():
> +
> +
> + @register_vcs_handler("git", "get_keywords")
> +-def git_get_keywords(versionfile_abs):
> ++def git_get_keywords(versionfile_abs: str) -> Dict[str, str]:
> +     """Extract version information from the given file."""
> +     # the code embedded in _version.py can just fetch the value of these
> +     # keywords. When used from setup.py, we don't want to import _version.py,
> +     # so we do it with a regexp instead. This function is not used from
> +     # _version.py.
> +-    keywords = {}
> ++    keywords: Dict[str, str] = {}
> +     try:
> +-        f = open(versionfile_abs, "r")
> +-        for line in f.readlines():
> +-            if line.strip().startswith("git_refnames ="):
> +-                mo = re.search(r'=\s*"(.*)"', line)
> +-                if mo:
> +-                    keywords["refnames"] = mo.group(1)
> +-            if line.strip().startswith("git_full ="):
> +-                mo = re.search(r'=\s*"(.*)"', line)
> +-                if mo:
> +-                    keywords["full"] = mo.group(1)
> +-            if line.strip().startswith("git_date ="):
> +-                mo = re.search(r'=\s*"(.*)"', line)
> +-                if mo:
> +-                    keywords["date"] = mo.group(1)
> +-        f.close()
> +-    except EnvironmentError:
> ++        with open(versionfile_abs, "r") as fobj:
> ++            for line in fobj:
> ++                if line.strip().startswith("git_refnames ="):
> ++                    mo = re.search(r'=\s*"(.*)"', line)
> ++                    if mo:
> ++                        keywords["refnames"] = mo.group(1)
> ++                if line.strip().startswith("git_full ="):
> ++                    mo = re.search(r'=\s*"(.*)"', line)
> ++                    if mo:
> ++                        keywords["full"] = mo.group(1)
> ++                if line.strip().startswith("git_date ="):
> ++                    mo = re.search(r'=\s*"(.*)"', line)
> ++                    if mo:
> ++                        keywords["date"] = mo.group(1)
> ++    except OSError:
> +         pass
> +     return keywords
> +
> +
> + @register_vcs_handler("git", "keywords")
> +-def git_versions_from_keywords(keywords, tag_prefix, verbose):
> ++def git_versions_from_keywords(
> ++    keywords: Dict[str, str],
> ++    tag_prefix: str,
> ++    verbose: bool,
> ++) -> Dict[str, Any]:
> +     """Get version information from git keywords."""
> +-    if not keywords:
> +-        raise NotThisMethod("no keywords at all, weird")
> ++    if "refnames" not in keywords:
> ++        raise NotThisMethod("Short version file found")
> +     date = keywords.get("date")
> +     if date is not None:
> ++        # Use only the last line.  Previous lines may contain GPG signature
> ++        # information.
> ++        date = date.splitlines()[-1]
> ++
> +         # git-2.2.0 added "%cI", which expands to an ISO-8601 -compliant
> +         # datestamp. However we prefer "%ci" (which expands to an "ISO-8601
> +         # -like" string, which we must then edit to make compliant), because
> +@@ -989,11 +1257,11 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose):
> +         if verbose:
> +             print("keywords are unexpanded, not using")
> +         raise NotThisMethod("unexpanded keywords, not a git-archive tarball")
> +-    refs = set([r.strip() for r in refnames.strip("()").split(",")])
> ++    refs = {r.strip() for r in refnames.strip("()").split(",")}
> +     # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of
> +     # just "foo-1.0". If we see a "tag: " prefix, prefer those.
> +     TAG = "tag: "
> +-    tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)])
> ++    tags = {r[len(TAG) :] for r in refs if r.startswith(TAG)}
> +     if not tags:
> +         # Either we're using git < 1.8.3, or there really are no tags. We use
> +         # a heuristic: assume all version tags have a digit. The old git %d
> +@@ -1002,7 +1270,7 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose):
> +         # between branches and tags. By ignoring refnames without digits, we
> +         # filter out many common branch names like "release" and
> +         # "stabilization", as well as "HEAD" and "master".
> +-        tags = set([r for r in refs if re.search(r'\d', r)])
> ++        tags = {r for r in refs if re.search(r"\d", r)}
> +         if verbose:
> +             print("discarding '%s', no digits" % ",".join(refs - tags))
> +     if verbose:
> +@@ -1010,23 +1278,37 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose):
> +     for ref in sorted(tags):
> +         # sorting will prefer e.g. "2.0" over "2.0rc1"
> +         if ref.startswith(tag_prefix):
> +-            r = ref[len(tag_prefix):]
> ++            r = ref[len(tag_prefix) :]
> ++            # Filter out refs that exactly match prefix or that don't start
> ++            # with a number once the prefix is stripped (mostly a concern
> ++            # when prefix is '')
> ++            if not re.match(r"\d", r):
> ++                continue
> +             if verbose:
> +                 print("picking %s" % r)
> +-            return {"version": r,
> +-                    "full-revisionid": keywords["full"].strip(),
> +-                    "dirty": False, "error": None,
> +-                    "date": date}
> ++            return {
> ++                "version": r,
> ++                "full-revisionid": keywords["full"].strip(),
> ++                "dirty": False,
> ++                "error": None,
> ++                "date": date,
> ++            }
> +     # no suitable tags, so version is "0+unknown", but full hex is still there
> +     if verbose:
> +         print("no suitable tags, using unknown + full revision id")
> +-    return {"version": "0+unknown",
> +-            "full-revisionid": keywords["full"].strip(),
> +-            "dirty": False, "error": "no suitable tags", "date": None}
> ++    return {
> ++        "version": "0+unknown",
> ++        "full-revisionid": keywords["full"].strip(),
> ++        "dirty": False,
> ++        "error": "no suitable tags",
> ++        "date": None,
> ++    }
> +
> +
> + @register_vcs_handler("git", "pieces_from_vcs")
> +-def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
> ++def git_pieces_from_vcs(
> ++    tag_prefix: str, root: str, verbose: bool, runner: Callable = run_command
> ++) -> Dict[str, Any]:
> +     """Get version from 'git describe' in the root of the source tree.
> +
> +     This only gets called if the git-archive 'subst' keywords were *not*
> +@@ -1037,8 +1319,14 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
> +     if sys.platform == "win32":
> +         GITS = ["git.cmd", "git.exe"]
> +
> +-    out, rc = run_command(GITS, ["rev-parse", "--git-dir"], cwd=root,
> +-                          hide_stderr=True)
> ++    # GIT_DIR can interfere with correct operation of Versioneer.
> ++    # It may be intended to be passed to the Versioneer-versioned project,
> ++    # but that should not change where we get our version from.
> ++    env = os.environ.copy()
> ++    env.pop("GIT_DIR", None)
> ++    runner = functools.partial(runner, env=env)
> ++
> ++    _, rc = runner(GITS, ["rev-parse", "--git-dir"], cwd=root, hide_stderr=not verbose)
> +     if rc != 0:
> +         if verbose:
> +             print("Directory %s not under git control" % root)
> +@@ -1046,24 +1334,65 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
> +
> +     # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty]
> +     # if there isn't one, this yields HEX[-dirty] (no NUM)
> +-    describe_out, rc = run_command(GITS, ["describe", "--tags", "--dirty",
> +-                                          "--always", "--long",
> +-                                          "--match", "%s*" % tag_prefix],
> +-                                   cwd=root)
> ++    describe_out, rc = runner(
> ++        GITS,
> ++        [
> ++            "describe",
> ++            "--tags",
> ++            "--dirty",
> ++            "--always",
> ++            "--long",
> ++            "--match",
> ++            f"{tag_prefix}[[:digit:]]*",
> ++        ],
> ++        cwd=root,
> ++    )
> +     # --long was added in git-1.5.5
> +     if describe_out is None:
> +         raise NotThisMethod("'git describe' failed")
> +     describe_out = describe_out.strip()
> +-    full_out, rc = run_command(GITS, ["rev-parse", "HEAD"], cwd=root)
> ++    full_out, rc = runner(GITS, ["rev-parse", "HEAD"], cwd=root)
> +     if full_out is None:
> +         raise NotThisMethod("'git rev-parse' failed")
> +     full_out = full_out.strip()
> +
> +-    pieces = {}
> ++    pieces: Dict[str, Any] = {}
> +     pieces["long"] = full_out
> +     pieces["short"] = full_out[:7]  # maybe improved later
> +     pieces["error"] = None
> +
> ++    branch_name, rc = runner(GITS, ["rev-parse", "--abbrev-ref", "HEAD"], cwd=root)
> ++    # --abbrev-ref was added in git-1.6.3
> ++    if rc != 0 or branch_name is None:
> ++        raise NotThisMethod("'git rev-parse --abbrev-ref' returned error")
> ++    branch_name = branch_name.strip()
> ++
> ++    if branch_name == "HEAD":
> ++        # If we aren't exactly on a branch, pick a branch which represents
> ++        # the current commit. If all else fails, we are on a branchless
> ++        # commit.
> ++        branches, rc = runner(GITS, ["branch", "--contains"], cwd=root)
> ++        # --contains was added in git-1.5.4
> ++        if rc != 0 or branches is None:
> ++            raise NotThisMethod("'git branch --contains' returned error")
> ++        branches = branches.split("\n")
> ++
> ++        # Remove the first line if we're running detached
> ++        if "(" in branches[0]:
> ++            branches.pop(0)
> ++
> ++        # Strip off the leading "* " from the list of branches.
> ++        branches = [branch[2:] for branch in branches]
> ++        if "master" in branches:
> ++            branch_name = "master"
> ++        elif not branches:
> ++            branch_name = None
> ++        else:
> ++            # Pick the first branch that is returned. Good or bad.
> ++            branch_name = branches[0]
> ++
> ++    pieces["branch"] = branch_name
> ++
> +     # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty]
> +     # TAG might have hyphens.
> +     git_describe = describe_out
> +@@ -1072,17 +1401,16 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
> +     dirty = git_describe.endswith("-dirty")
> +     pieces["dirty"] = dirty
> +     if dirty:
> +-        git_describe = git_describe[:git_describe.rindex("-dirty")]
> ++        git_describe = git_describe[: git_describe.rindex("-dirty")]
> +
> +     # now we have TAG-NUM-gHEX or HEX
> +
> +     if "-" in git_describe:
> +         # TAG-NUM-gHEX
> +-        mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe)
> ++        mo = re.search(r"^(.+)-(\d+)-g([0-9a-f]+)$", git_describe)
> +         if not mo:
> +-            # unparseable. Maybe git-describe is misbehaving?
> +-            pieces["error"] = ("unable to parse git-describe output: '%s'"
> +-                               % describe_out)
> ++            # unparsable. Maybe git-describe is misbehaving?
> ++            pieces["error"] = "unable to parse git-describe output: '%s'" % describe_out
> +             return pieces
> +
> +         # tag
> +@@ -1091,10 +1419,12 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
> +             if verbose:
> +                 fmt = "tag '%s' doesn't start with prefix '%s'"
> +                 print(fmt % (full_tag, tag_prefix))
> +-            pieces["error"] = ("tag '%s' doesn't start with prefix '%s'"
> +-                               % (full_tag, tag_prefix))
> ++            pieces["error"] = "tag '%s' doesn't start with prefix '%s'" % (
> ++                full_tag,
> ++                tag_prefix,
> ++            )
> +             return pieces
> +-        pieces["closest-tag"] = full_tag[len(tag_prefix):]
> ++        pieces["closest-tag"] = full_tag[len(tag_prefix) :]
> +
> +         # distance: number of commits since tag
> +         pieces["distance"] = int(mo.group(2))
> +@@ -1105,19 +1435,20 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
> +     else:
> +         # HEX: no tags
> +         pieces["closest-tag"] = None
> +-        count_out, rc = run_command(GITS, ["rev-list", "HEAD", "--count"],
> +-                                    cwd=root)
> +-        pieces["distance"] = int(count_out)  # total number of commits
> ++        out, rc = runner(GITS, ["rev-list", "HEAD", "--left-right"], cwd=root)
> ++        pieces["distance"] = len(out.split())  # total number of commits
> +
> +     # commit date: see ISO-8601 comment in git_versions_from_keywords()
> +-    date = run_command(GITS, ["show", "-s", "--format=%ci", "HEAD"],
> +-                       cwd=root)[0].strip()
> ++    date = runner(GITS, ["show", "-s", "--format=%ci", "HEAD"], cwd=root)[0].strip()
> ++    # Use only the last line.  Previous lines may contain GPG signature
> ++    # information.
> ++    date = date.splitlines()[-1]
> +     pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1)
> +
> +     return pieces
> +
> +
> +-def do_vcs_install(manifest_in, versionfile_source, ipy):
> ++def do_vcs_install(versionfile_source: str, ipy: Optional[str]) -> None:
> +     """Git-specific installation logic for Versioneer.
> +
> +     For Git, this means creating/changing .gitattributes to mark _version.py
> +@@ -1126,36 +1457,40 @@ def do_vcs_install(manifest_in, versionfile_source, ipy):
> +     GITS = ["git"]
> +     if sys.platform == "win32":
> +         GITS = ["git.cmd", "git.exe"]
> +-    files = [manifest_in, versionfile_source]
> ++    files = [versionfile_source]
> +     if ipy:
> +         files.append(ipy)
> +-    try:
> +-        me = __file__
> +-        if me.endswith(".pyc") or me.endswith(".pyo"):
> +-            me = os.path.splitext(me)[0] + ".py"
> +-        versioneer_file = os.path.relpath(me)
> +-    except NameError:
> +-        versioneer_file = "versioneer.py"
> +-    files.append(versioneer_file)
> ++    if "VERSIONEER_PEP518" not in globals():
> ++        try:
> ++            my_path = __file__
> ++            if my_path.endswith((".pyc", ".pyo")):
> ++                my_path = os.path.splitext(my_path)[0] + ".py"
> ++            versioneer_file = os.path.relpath(my_path)
> ++        except NameError:
> ++            versioneer_file = "versioneer.py"
> ++        files.append(versioneer_file)
> +     present = False
> +     try:
> +-        f = open(".gitattributes", "r")
> +-        for line in f.readlines():
> +-            if line.strip().startswith(versionfile_source):
> +-                if "export-subst" in line.strip().split()[1:]:
> +-                    present = True
> +-        f.close()
> +-    except EnvironmentError:
> ++        with open(".gitattributes", "r") as fobj:
> ++            for line in fobj:
> ++                if line.strip().startswith(versionfile_source):
> ++                    if "export-subst" in line.strip().split()[1:]:
> ++                        present = True
> ++                        break
> ++    except OSError:
> +         pass
> +     if not present:
> +-        f = open(".gitattributes", "a+")
> +-        f.write("%s export-subst\n" % versionfile_source)
> +-        f.close()
> ++        with open(".gitattributes", "a+") as fobj:
> ++            fobj.write(f"{versionfile_source} export-subst\n")
> +         files.append(".gitattributes")
> +     run_command(GITS, ["add", "--"] + files)
> +
> +
> +-def versions_from_parentdir(parentdir_prefix, root, verbose):
> ++def versions_from_parentdir(
> ++    parentdir_prefix: str,
> ++    root: str,
> ++    verbose: bool,
> ++) -> Dict[str, Any]:
> +     """Try to determine the version from the parent directory name.
> +
> +     Source tarballs conventionally unpack into a directory that includes both
> +@@ -1164,24 +1499,29 @@ def versions_from_parentdir(parentdir_prefix, root, verbose):
> +     """
> +     rootdirs = []
> +
> +-    for i in range(3):
> ++    for _ in range(3):
> +         dirname = os.path.basename(root)
> +         if dirname.startswith(parentdir_prefix):
> +-            return {"version": dirname[len(parentdir_prefix):],
> +-                    "full-revisionid": None,
> +-                    "dirty": False, "error": None, "date": None}
> +-        else:
> +-            rootdirs.append(root)
> +-            root = os.path.dirname(root)  # up a level
> ++            return {
> ++                "version": dirname[len(parentdir_prefix) :],
> ++                "full-revisionid": None,
> ++                "dirty": False,
> ++                "error": None,
> ++                "date": None,
> ++            }
> ++        rootdirs.append(root)
> ++        root = os.path.dirname(root)  # up a level
> +
> +     if verbose:
> +-        print("Tried directories %s but none started with prefix %s" %
> +-              (str(rootdirs), parentdir_prefix))
> ++        print(
> ++            "Tried directories %s but none started with prefix %s"
> ++            % (str(rootdirs), parentdir_prefix)
> ++        )
> +     raise NotThisMethod("rootdir doesn't start with parentdir_prefix")
> +
> +
> + SHORT_VERSION_PY = """
> +-# This file was generated by 'versioneer.py' (0.18) from
> ++# This file was generated by 'versioneer.py' (0.29) from
> + # revision-control system data, or from the parent directory name of an
> + # unpacked source archive. Distribution tarballs contain a pre-generated copy
> + # of this file.
> +@@ -1198,42 +1538,42 @@ def get_versions():
> + """
> +
> +
> +-def versions_from_file(filename):
> ++def versions_from_file(filename: str) -> Dict[str, Any]:
> +     """Try to determine the version from _version.py if present."""
> +     try:
> +         with open(filename) as f:
> +             contents = f.read()
> +-    except EnvironmentError:
> ++    except OSError:
> +         raise NotThisMethod("unable to read _version.py")
> +-    mo = re.search(r"version_json = '''\n(.*)'''  # END VERSION_JSON",
> +-                   contents, re.M | re.S)
> ++    mo = re.search(
> ++        r"version_json = '''\n(.*)'''  # END VERSION_JSON", contents, re.M | re.S
> ++    )
> +     if not mo:
> +-        mo = re.search(r"version_json = '''\r\n(.*)'''  # END VERSION_JSON",
> +-                       contents, re.M | re.S)
> ++        mo = re.search(
> ++            r"version_json = '''\r\n(.*)'''  # END VERSION_JSON", contents, re.M | re.S
> ++        )
> +     if not mo:
> +         raise NotThisMethod("no version_json in _version.py")
> +     return json.loads(mo.group(1))
> +
> +
> +-def write_to_version_file(filename, versions):
> ++def write_to_version_file(filename: str, versions: Dict[str, Any]) -> None:
> +     """Write the given version number to the given _version.py file."""
> +-    os.unlink(filename)
> +-    contents = json.dumps(versions, sort_keys=True,
> +-                          indent=1, separators=(",", ": "))
> ++    contents = json.dumps(versions, sort_keys=True, indent=1, separators=(",", ": "))
> +     with open(filename, "w") as f:
> +         f.write(SHORT_VERSION_PY % contents)
> +
> +     print("set %s to '%s'" % (filename, versions["version"]))
> +
> +
> +-def plus_or_dot(pieces):
> ++def plus_or_dot(pieces: Dict[str, Any]) -> str:
> +     """Return a + if we don't already have one, else return a ."""
> +     if "+" in pieces.get("closest-tag", ""):
> +         return "."
> +     return "+"
> +
> +
> +-def render_pep440(pieces):
> ++def render_pep440(pieces: Dict[str, Any]) -> str:
> +     """Build up version string, with post-release "local version identifier".
> +
> +     Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you
> +@@ -1251,30 +1591,76 @@ def render_pep440(pieces):
> +                 rendered += ".dirty"
> +     else:
> +         # exception #1
> +-        rendered = "0+untagged.%d.g%s" % (pieces["distance"],
> +-                                          pieces["short"])
> ++        rendered = "0+untagged.%d.g%s" % (pieces["distance"], pieces["short"])
> +         if pieces["dirty"]:
> +             rendered += ".dirty"
> +     return rendered
> +
> +
> +-def render_pep440_pre(pieces):
> +-    """TAG[.post.devDISTANCE] -- No -dirty.
> ++def render_pep440_branch(pieces: Dict[str, Any]) -> str:
> ++    """TAG[[.dev0]+DISTANCE.gHEX[.dirty]] .
> ++
> ++    The ".dev0" means not master branch. Note that .dev0 sorts backwards
> ++    (a feature branch will appear "older" than the master branch).
> +
> +     Exceptions:
> +-    1: no tags. 0.post.devDISTANCE
> ++    1: no tags. 0[.dev0]+untagged.DISTANCE.gHEX[.dirty]
> +     """
> +     if pieces["closest-tag"]:
> +         rendered = pieces["closest-tag"]
> ++        if pieces["distance"] or pieces["dirty"]:
> ++            if pieces["branch"] != "master":
> ++                rendered += ".dev0"
> ++            rendered += plus_or_dot(pieces)
> ++            rendered += "%d.g%s" % (pieces["distance"], pieces["short"])
> ++            if pieces["dirty"]:
> ++                rendered += ".dirty"
> ++    else:
> ++        # exception #1
> ++        rendered = "0"
> ++        if pieces["branch"] != "master":
> ++            rendered += ".dev0"
> ++        rendered += "+untagged.%d.g%s" % (pieces["distance"], pieces["short"])
> ++        if pieces["dirty"]:
> ++            rendered += ".dirty"
> ++    return rendered
> ++
> ++
> ++def pep440_split_post(ver: str) -> Tuple[str, Optional[int]]:
> ++    """Split pep440 version string at the post-release segment.
> ++
> ++    Returns the release segments before the post-release and the
> ++    post-release version number (or -1 if no post-release segment is present).
> ++    """
> ++    vc = str.split(ver, ".post")
> ++    return vc[0], int(vc[1] or 0) if len(vc) == 2 else None
> ++
> ++
> ++def render_pep440_pre(pieces: Dict[str, Any]) -> str:
> ++    """TAG[.postN.devDISTANCE] -- No -dirty.
> ++
> ++    Exceptions:
> ++    1: no tags. 0.post0.devDISTANCE
> ++    """
> ++    if pieces["closest-tag"]:
> +         if pieces["distance"]:
> +-            rendered += ".post.dev%d" % pieces["distance"]
> ++            # update the post release segment
> ++            tag_version, post_version = pep440_split_post(pieces["closest-tag"])
> ++            rendered = tag_version
> ++            if post_version is not None:
> ++                rendered += ".post%d.dev%d" % (post_version + 1, pieces["distance"])
> ++            else:
> ++                rendered += ".post0.dev%d" % (pieces["distance"])
> ++        else:
> ++            # no commits, use the tag as the version
> ++            rendered = pieces["closest-tag"]
> +     else:
> +         # exception #1
> +-        rendered = "0.post.dev%d" % pieces["distance"]
> ++        rendered = "0.post0.dev%d" % pieces["distance"]
> +     return rendered
> +
> +
> +-def render_pep440_post(pieces):
> ++def render_pep440_post(pieces: Dict[str, Any]) -> str:
> +     """TAG[.postDISTANCE[.dev0]+gHEX] .
> +
> +     The ".dev0" means dirty. Note that .dev0 sorts backwards
> +@@ -1301,12 +1687,41 @@ def render_pep440_post(pieces):
> +     return rendered
> +
> +
> +-def render_pep440_old(pieces):
> ++def render_pep440_post_branch(pieces: Dict[str, Any]) -> str:
> ++    """TAG[.postDISTANCE[.dev0]+gHEX[.dirty]] .
> ++
> ++    The ".dev0" means not master branch.
> ++
> ++    Exceptions:
> ++    1: no tags. 0.postDISTANCE[.dev0]+gHEX[.dirty]
> ++    """
> ++    if pieces["closest-tag"]:
> ++        rendered = pieces["closest-tag"]
> ++        if pieces["distance"] or pieces["dirty"]:
> ++            rendered += ".post%d" % pieces["distance"]
> ++            if pieces["branch"] != "master":
> ++                rendered += ".dev0"
> ++            rendered += plus_or_dot(pieces)
> ++            rendered += "g%s" % pieces["short"]
> ++            if pieces["dirty"]:
> ++                rendered += ".dirty"
> ++    else:
> ++        # exception #1
> ++        rendered = "0.post%d" % pieces["distance"]
> ++        if pieces["branch"] != "master":
> ++            rendered += ".dev0"
> ++        rendered += "+g%s" % pieces["short"]
> ++        if pieces["dirty"]:
> ++            rendered += ".dirty"
> ++    return rendered
> ++
> ++
> ++def render_pep440_old(pieces: Dict[str, Any]) -> str:
> +     """TAG[.postDISTANCE[.dev0]] .
> +
> +     The ".dev0" means dirty.
> +
> +-    Eexceptions:
> ++    Exceptions:
> +     1: no tags. 0.postDISTANCE[.dev0]
> +     """
> +     if pieces["closest-tag"]:
> +@@ -1323,7 +1738,7 @@ def render_pep440_old(pieces):
> +     return rendered
> +
> +
> +-def render_git_describe(pieces):
> ++def render_git_describe(pieces: Dict[str, Any]) -> str:
> +     """TAG[-DISTANCE-gHEX][-dirty].
> +
> +     Like 'git describe --tags --dirty --always'.
> +@@ -1343,7 +1758,7 @@ def render_git_describe(pieces):
> +     return rendered
> +
> +
> +-def render_git_describe_long(pieces):
> ++def render_git_describe_long(pieces: Dict[str, Any]) -> str:
> +     """TAG-DISTANCE-gHEX[-dirty].
> +
> +     Like 'git describe --tags --dirty --always -long'.
> +@@ -1363,24 +1778,30 @@ def render_git_describe_long(pieces):
> +     return rendered
> +
> +
> +-def render(pieces, style):
> ++def render(pieces: Dict[str, Any], style: str) -> Dict[str, Any]:
> +     """Render the given version pieces into the requested style."""
> +     if pieces["error"]:
> +-        return {"version": "unknown",
> +-                "full-revisionid": pieces.get("long"),
> +-                "dirty": None,
> +-                "error": pieces["error"],
> +-                "date": None}
> ++        return {
> ++            "version": "unknown",
> ++            "full-revisionid": pieces.get("long"),
> ++            "dirty": None,
> ++            "error": pieces["error"],
> ++            "date": None,
> ++        }
> +
> +     if not style or style == "default":
> +         style = "pep440"  # the default
> +
> +     if style == "pep440":
> +         rendered = render_pep440(pieces)
> ++    elif style == "pep440-branch":
> ++        rendered = render_pep440_branch(pieces)
> +     elif style == "pep440-pre":
> +         rendered = render_pep440_pre(pieces)
> +     elif style == "pep440-post":
> +         rendered = render_pep440_post(pieces)
> ++    elif style == "pep440-post-branch":
> ++        rendered = render_pep440_post_branch(pieces)
> +     elif style == "pep440-old":
> +         rendered = render_pep440_old(pieces)
> +     elif style == "git-describe":
> +@@ -1390,16 +1811,20 @@ def render(pieces, style):
> +     else:
> +         raise ValueError("unknown style '%s'" % style)
> +
> +-    return {"version": rendered, "full-revisionid": pieces["long"],
> +-            "dirty": pieces["dirty"], "error": None,
> +-            "date": pieces.get("date")}
> ++    return {
> ++        "version": rendered,
> ++        "full-revisionid": pieces["long"],
> ++        "dirty": pieces["dirty"],
> ++        "error": None,
> ++        "date": pieces.get("date"),
> ++    }
> +
> +
> + class VersioneerBadRootError(Exception):
> +     """The project root directory is unknown or missing key files."""
> +
> +
> +-def get_versions(verbose=False):
> ++def get_versions(verbose: bool = False) -> Dict[str, Any]:
> +     """Get the project version from whatever source is available.
> +
> +     Returns dict with two keys: 'version' and 'full'.
> +@@ -1414,9 +1839,10 @@ def get_versions(verbose=False):
> +     assert cfg.VCS is not None, "please set [versioneer]VCS= in setup.cfg"
> +     handlers = HANDLERS.get(cfg.VCS)
> +     assert handlers, "unrecognized VCS '%s'" % cfg.VCS
> +-    verbose = verbose or cfg.verbose
> +-    assert cfg.versionfile_source is not None, \
> +-        "please set versioneer.versionfile_source"
> ++    verbose = verbose or bool(cfg.verbose)  # `bool()` used to avoid `None`
> ++    assert (
> ++        cfg.versionfile_source is not None
> ++    ), "please set versioneer.versionfile_source"
> +     assert cfg.tag_prefix is not None, "please set versioneer.tag_prefix"
> +
> +     versionfile_abs = os.path.join(root, cfg.versionfile_source)
> +@@ -1470,18 +1896,26 @@ def get_versions(verbose=False):
> +     if verbose:
> +         print("unable to compute version")
> +
> +-    return {"version": "0+unknown", "full-revisionid": None,
> +-            "dirty": None, "error": "unable to compute version",
> +-            "date": None}
> ++    return {
> ++        "version": "0+unknown",
> ++        "full-revisionid": None,
> ++        "dirty": None,
> ++        "error": "unable to compute version",
> ++        "date": None,
> ++    }
> +
> +
> +-def get_version():
> ++def get_version() -> str:
> +     """Get the short version string for this project."""
> +     return get_versions()["version"]
> +
> +
> +-def get_cmdclass():
> +-    """Get the custom setuptools/distutils subclasses used by Versioneer."""
> ++def get_cmdclass(cmdclass: Optional[Dict[str, Any]] = None):
> ++    """Get the custom setuptools subclasses used by Versioneer.
> ++
> ++    If the package uses a different cmdclass (e.g. one from numpy), it
> ++    should be provide as an argument.
> ++    """
> +     if "versioneer" in sys.modules:
> +         del sys.modules["versioneer"]
> +         # this fixes the "python setup.py develop" case (also 'install' and
> +@@ -1495,25 +1929,25 @@ def get_cmdclass():
> +         # parent is protected against the child's "import versioneer". By
> +         # removing ourselves from sys.modules here, before the child build
> +         # happens, we protect the child from the parent's versioneer too.
> +-        # Also see https://github.com/warner/python-versioneer/issues/52
> ++        # Also see https://github.com/python-versioneer/python-versioneer/issues/52
> +
> +-    cmds = {}
> ++    cmds = {} if cmdclass is None else cmdclass.copy()
> +
> +-    # we add "version" to both distutils and setuptools
> +-    from distutils.core import Command
> ++    # we add "version" to setuptools
> ++    from setuptools import Command
> +
> +     class cmd_version(Command):
> +         description = "report generated version string"
> +-        user_options = []
> +-        boolean_options = []
> ++        user_options: List[Tuple[str, str, str]] = []
> ++        boolean_options: List[str] = []
> +
> +-        def initialize_options(self):
> ++        def initialize_options(self) -> None:
> +             pass
> +
> +-        def finalize_options(self):
> ++        def finalize_options(self) -> None:
> +             pass
> +
> +-        def run(self):
> ++        def run(self) -> None:
> +             vers = get_versions(verbose=True)
> +             print("Version: %s" % vers["version"])
> +             print(" full-revisionid: %s" % vers.get("full-revisionid"))
> +@@ -1521,9 +1955,10 @@ def get_cmdclass():
> +             print(" date: %s" % vers.get("date"))
> +             if vers["error"]:
> +                 print(" error: %s" % vers["error"])
> ++
> +     cmds["version"] = cmd_version
> +
> +-    # we override "build_py" in both distutils and setuptools
> ++    # we override "build_py" in setuptools
> +     #
> +     # most invocation pathways end up running build_py:
> +     #  distutils/build -> build_py
> +@@ -1538,29 +1973,71 @@ def get_cmdclass():
> +     #   then does setup.py bdist_wheel, or sometimes setup.py install
> +     #  setup.py egg_info -> ?
> +
> ++    # pip install -e . and setuptool/editable_wheel will invoke build_py
> ++    # but the build_py command is not expected to copy any files.
> ++
> +     # we override different "build_py" commands for both environments
> +-    if "setuptools" in sys.modules:
> +-        from setuptools.command.build_py import build_py as _build_py
> ++    if "build_py" in cmds:
> ++        _build_py: Any = cmds["build_py"]
> +     else:
> +-        from distutils.command.build_py import build_py as _build_py
> ++        from setuptools.command.build_py import build_py as _build_py
> +
> +     class cmd_build_py(_build_py):
> +-        def run(self):
> ++        def run(self) -> None:
> +             root = get_root()
> +             cfg = get_config_from_root(root)
> +             versions = get_versions()
> +             _build_py.run(self)
> ++            if getattr(self, "editable_mode", False):
> ++                # During editable installs `.py` and data files are
> ++                # not copied to build_lib
> ++                return
> +             # now locate _version.py in the new build/ directory and replace
> +             # it with an updated value
> +             if cfg.versionfile_build:
> +-                target_versionfile = os.path.join(self.build_lib,
> +-                                                  cfg.versionfile_build)
> ++                target_versionfile = os.path.join(self.build_lib, cfg.versionfile_build)
> +                 print("UPDATING %s" % target_versionfile)
> +                 write_to_version_file(target_versionfile, versions)
> ++
> +     cmds["build_py"] = cmd_build_py
> +
> ++    if "build_ext" in cmds:
> ++        _build_ext: Any = cmds["build_ext"]
> ++    else:
> ++        from setuptools.command.build_ext import build_ext as _build_ext
> ++
> ++    class cmd_build_ext(_build_ext):
> ++        def run(self) -> None:
> ++            root = get_root()
> ++            cfg = get_config_from_root(root)
> ++            versions = get_versions()
> ++            _build_ext.run(self)
> ++            if self.inplace:
> ++                # build_ext --inplace will only build extensions in
> ++                # build/lib<..> dir with no _version.py to write to.
> ++                # As in place builds will already have a _version.py
> ++                # in the module dir, we do not need to write one.
> ++                return
> ++            # now locate _version.py in the new build/ directory and replace
> ++            # it with an updated value
> ++            if not cfg.versionfile_build:
> ++                return
> ++            target_versionfile = os.path.join(self.build_lib, cfg.versionfile_build)
> ++            if not os.path.exists(target_versionfile):
> ++                print(
> ++                    f"Warning: {target_versionfile} does not exist, skipping "
> ++                    "version update. This can happen if you are running build_ext "
> ++                    "without first running build_py."
> ++                )
> ++                return
> ++            print("UPDATING %s" % target_versionfile)
> ++            write_to_version_file(target_versionfile, versions)
> ++
> ++    cmds["build_ext"] = cmd_build_ext
> ++
> +     if "cx_Freeze" in sys.modules:  # cx_freeze enabled?
> +-        from cx_Freeze.dist import build_exe as _build_exe
> ++        from cx_Freeze.dist import build_exe as _build_exe  # type: ignore
> ++
> +         # nczeczulin reports that py2exe won't like the pep440-style string
> +         # as FILEVERSION, but it can be used for PRODUCTVERSION, e.g.
> +         # setup(console=[{
> +@@ -1569,7 +2046,7 @@ def get_cmdclass():
> +         #   ...
> +
> +         class cmd_build_exe(_build_exe):
> +-            def run(self):
> ++            def run(self) -> None:
> +                 root = get_root()
> +                 cfg = get_config_from_root(root)
> +                 versions = get_versions()
> +@@ -1581,24 +2058,28 @@ def get_cmdclass():
> +                 os.unlink(target_versionfile)
> +                 with open(cfg.versionfile_source, "w") as f:
> +                     LONG = LONG_VERSION_PY[cfg.VCS]
> +-                    f.write(LONG %
> +-                            {"DOLLAR": "$",
> +-                             "STYLE": cfg.style,
> +-                             "TAG_PREFIX": cfg.tag_prefix,
> +-                             "PARENTDIR_PREFIX": cfg.parentdir_prefix,
> +-                             "VERSIONFILE_SOURCE": cfg.versionfile_source,
> +-                             })
> ++                    f.write(
> ++                        LONG
> ++                        % {
> ++                            "DOLLAR": "$",
> ++                            "STYLE": cfg.style,
> ++                            "TAG_PREFIX": cfg.tag_prefix,
> ++                            "PARENTDIR_PREFIX": cfg.parentdir_prefix,
> ++                            "VERSIONFILE_SOURCE": cfg.versionfile_source,
> ++                        }
> ++                    )
> ++
> +         cmds["build_exe"] = cmd_build_exe
> +         del cmds["build_py"]
> +
> +-    if 'py2exe' in sys.modules:  # py2exe enabled?
> ++    if "py2exe" in sys.modules:  # py2exe enabled?
> +         try:
> +-            from py2exe.distutils_buildexe import py2exe as _py2exe  # py3
> ++            from py2exe.setuptools_buildexe import py2exe as _py2exe  # type: ignore
> +         except ImportError:
> +-            from py2exe.build_exe import py2exe as _py2exe  # py2
> ++            from py2exe.distutils_buildexe import py2exe as _py2exe  # type: ignore
> +
> +         class cmd_py2exe(_py2exe):
> +-            def run(self):
> ++            def run(self) -> None:
> +                 root = get_root()
> +                 cfg = get_config_from_root(root)
> +                 versions = get_versions()
> +@@ -1610,23 +2091,67 @@ def get_cmdclass():
> +                 os.unlink(target_versionfile)
> +                 with open(cfg.versionfile_source, "w") as f:
> +                     LONG = LONG_VERSION_PY[cfg.VCS]
> +-                    f.write(LONG %
> +-                            {"DOLLAR": "$",
> +-                             "STYLE": cfg.style,
> +-                             "TAG_PREFIX": cfg.tag_prefix,
> +-                             "PARENTDIR_PREFIX": cfg.parentdir_prefix,
> +-                             "VERSIONFILE_SOURCE": cfg.versionfile_source,
> +-                             })
> ++                    f.write(
> ++                        LONG
> ++                        % {
> ++                            "DOLLAR": "$",
> ++                            "STYLE": cfg.style,
> ++                            "TAG_PREFIX": cfg.tag_prefix,
> ++                            "PARENTDIR_PREFIX": cfg.parentdir_prefix,
> ++                            "VERSIONFILE_SOURCE": cfg.versionfile_source,
> ++                        }
> ++                    )
> ++
> +         cmds["py2exe"] = cmd_py2exe
> +
> ++    # sdist farms its file list building out to egg_info
> ++    if "egg_info" in cmds:
> ++        _egg_info: Any = cmds["egg_info"]
> ++    else:
> ++        from setuptools.command.egg_info import egg_info as _egg_info
> ++
> ++    class cmd_egg_info(_egg_info):
> ++        def find_sources(self) -> None:
> ++            # egg_info.find_sources builds the manifest list and writes it
> ++            # in one shot
> ++            super().find_sources()
> ++
> ++            # Modify the filelist and normalize it
> ++            root = get_root()
> ++            cfg = get_config_from_root(root)
> ++            self.filelist.append("versioneer.py")
> ++            if cfg.versionfile_source:
> ++                # There are rare cases where versionfile_source might not be
> ++                # included by default, so we must be explicit
> ++                self.filelist.append(cfg.versionfile_source)
> ++            self.filelist.sort()
> ++            self.filelist.remove_duplicates()
> ++
> ++            # The write method is hidden in the manifest_maker instance that
> ++            # generated the filelist and was thrown away
> ++            # We will instead replicate their final normalization (to unicode,
> ++            # and POSIX-style paths)
> ++            from setuptools import unicode_utils
> ++
> ++            normalized = [
> ++                unicode_utils.filesys_decode(f).replace(os.sep, "/")
> ++                for f in self.filelist.files
> ++            ]
> ++
> ++            manifest_filename = os.path.join(self.egg_info, "SOURCES.txt")
> ++            with open(manifest_filename, "w") as fobj:
> ++                fobj.write("\n".join(normalized))
> ++
> ++    cmds["egg_info"] = cmd_egg_info
> ++
> +     # we override different "sdist" commands for both environments
> +-    if "setuptools" in sys.modules:
> +-        from setuptools.command.sdist import sdist as _sdist
> ++    if "sdist" in cmds:
> ++        _sdist: Any = cmds["sdist"]
> +     else:
> +-        from distutils.command.sdist import sdist as _sdist
> ++        from setuptools.command.sdist import sdist as _sdist
> +
> +     class cmd_sdist(_sdist):
> +-        def run(self):
> ++        def run(self) -> None:
> +             versions = get_versions()
> +             self._versioneer_generated_versions = versions
> +             # unless we update this, the command will keep using the old
> +@@ -1634,7 +2159,7 @@ def get_cmdclass():
> +             self.distribution.metadata.version = versions["version"]
> +             return _sdist.run(self)
> +
> +-        def make_release_tree(self, base_dir, files):
> ++        def make_release_tree(self, base_dir: str, files: List[str]) -> None:
> +             root = get_root()
> +             cfg = get_config_from_root(root)
> +             _sdist.make_release_tree(self, base_dir, files)
> +@@ -1643,8 +2168,10 @@ def get_cmdclass():
> +             # updated value
> +             target_versionfile = os.path.join(base_dir, cfg.versionfile_source)
> +             print("UPDATING %s" % target_versionfile)
> +-            write_to_version_file(target_versionfile,
> +-                                  self._versioneer_generated_versions)
> ++            write_to_version_file(
> ++                target_versionfile, self._versioneer_generated_versions
> ++            )
> ++
> +     cmds["sdist"] = cmd_sdist
> +
> +     return cmds
> +@@ -1687,23 +2214,26 @@ SAMPLE_CONFIG = """
> +
> + """
> +
> +-INIT_PY_SNIPPET = """
> ++OLD_SNIPPET = """
> + from ._version import get_versions
> + __version__ = get_versions()['version']
> + del get_versions
> + """
> +
> ++INIT_PY_SNIPPET = """
> ++from . import {0}
> ++__version__ = {0}.get_versions()['version']
> ++"""
> ++
> +
> +-def do_setup():
> +-    """Main VCS-independent setup function for installing Versioneer."""
> ++def do_setup() -> int:
> ++    """Do main VCS-independent setup function for installing Versioneer."""
> +     root = get_root()
> +     try:
> +         cfg = get_config_from_root(root)
> +-    except (EnvironmentError, configparser.NoSectionError,
> +-            configparser.NoOptionError) as e:
> +-        if isinstance(e, (EnvironmentError, configparser.NoSectionError)):
> +-            print("Adding sample versioneer config to setup.cfg",
> +-                  file=sys.stderr)
> ++    except (OSError, configparser.NoSectionError, configparser.NoOptionError) as e:
> ++        if isinstance(e, (OSError, configparser.NoSectionError)):
> ++            print("Adding sample versioneer config to setup.cfg", file=sys.stderr)
> +             with open(os.path.join(root, "setup.cfg"), "a") as f:
> +                 f.write(SAMPLE_CONFIG)
> +         print(CONFIG_ERROR, file=sys.stderr)
> +@@ -1712,71 +2242,49 @@ def do_setup():
> +     print(" creating %s" % cfg.versionfile_source)
> +     with open(cfg.versionfile_source, "w") as f:
> +         LONG = LONG_VERSION_PY[cfg.VCS]
> +-        f.write(LONG % {"DOLLAR": "$",
> +-                        "STYLE": cfg.style,
> +-                        "TAG_PREFIX": cfg.tag_prefix,
> +-                        "PARENTDIR_PREFIX": cfg.parentdir_prefix,
> +-                        "VERSIONFILE_SOURCE": cfg.versionfile_source,
> +-                        })
> +-
> +-    ipy = os.path.join(os.path.dirname(cfg.versionfile_source),
> +-                       "__init__.py")
> ++        f.write(
> ++            LONG
> ++            % {
> ++                "DOLLAR": "$",
> ++                "STYLE": cfg.style,
> ++                "TAG_PREFIX": cfg.tag_prefix,
> ++                "PARENTDIR_PREFIX": cfg.parentdir_prefix,
> ++                "VERSIONFILE_SOURCE": cfg.versionfile_source,
> ++            }
> ++        )
> ++
> ++    ipy = os.path.join(os.path.dirname(cfg.versionfile_source), "__init__.py")
> ++    maybe_ipy: Optional[str] = ipy
> +     if os.path.exists(ipy):
> +         try:
> +             with open(ipy, "r") as f:
> +                 old = f.read()
> +-        except EnvironmentError:
> ++        except OSError:
> +             old = ""
> +-        if INIT_PY_SNIPPET not in old:
> ++        module = os.path.splitext(os.path.basename(cfg.versionfile_source))[0]
> ++        snippet = INIT_PY_SNIPPET.format(module)
> ++        if OLD_SNIPPET in old:
> ++            print(" replacing boilerplate in %s" % ipy)
> ++            with open(ipy, "w") as f:
> ++                f.write(old.replace(OLD_SNIPPET, snippet))
> ++        elif snippet not in old:
> +             print(" appending to %s" % ipy)
> +             with open(ipy, "a") as f:
> +-                f.write(INIT_PY_SNIPPET)
> ++                f.write(snippet)
> +         else:
> +             print(" %s unmodified" % ipy)
> +     else:
> +         print(" %s doesn't exist, ok" % ipy)
> +-        ipy = None
> +-
> +-    # Make sure both the top-level "versioneer.py" and versionfile_source
> +-    # (PKG/_version.py, used by runtime code) are in MANIFEST.in, so
> +-    # they'll be copied into source distributions. Pip won't be able to
> +-    # install the package without this.
> +-    manifest_in = os.path.join(root, "MANIFEST.in")
> +-    simple_includes = set()
> +-    try:
> +-        with open(manifest_in, "r") as f:
> +-            for line in f:
> +-                if line.startswith("include "):
> +-                    for include in line.split()[1:]:
> +-                        simple_includes.add(include)
> +-    except EnvironmentError:
> +-        pass
> +-    # That doesn't cover everything MANIFEST.in can do
> +-    # (http://docs.python.org/2/distutils/sourcedist.html#commands), so
> +-    # it might give some false negatives. Appending redundant 'include'
> +-    # lines is safe, though.
> +-    if "versioneer.py" not in simple_includes:
> +-        print(" appending 'versioneer.py' to MANIFEST.in")
> +-        with open(manifest_in, "a") as f:
> +-            f.write("include versioneer.py\n")
> +-    else:
> +-        print(" 'versioneer.py' already in MANIFEST.in")
> +-    if cfg.versionfile_source not in simple_includes:
> +-        print(" appending versionfile_source ('%s') to MANIFEST.in" %
> +-              cfg.versionfile_source)
> +-        with open(manifest_in, "a") as f:
> +-            f.write("include %s\n" % cfg.versionfile_source)
> +-    else:
> +-        print(" versionfile_source already in MANIFEST.in")
> ++        maybe_ipy = None
> +
> +     # Make VCS-specific changes. For git, this means creating/changing
> +     # .gitattributes to mark _version.py for export-subst keyword
> +     # substitution.
> +-    do_vcs_install(manifest_in, cfg.versionfile_source, ipy)
> ++    do_vcs_install(cfg.versionfile_source, maybe_ipy)
> +     return 0
> +
> +
> +-def scan_setup_py():
> ++def scan_setup_py() -> int:
> +     """Validate the contents of setup.py against Versioneer's expectations."""
> +     found = set()
> +     setters = False
> +@@ -1813,10 +2321,14 @@ def scan_setup_py():
> +     return errors
> +
> +
> ++def setup_command() -> NoReturn:
> ++    """Set up Versioneer and exit with appropriate error code."""
> ++    errors = do_setup()
> ++    errors += scan_setup_py()
> ++    sys.exit(1 if errors else 0)
> ++
> ++
> + if __name__ == "__main__":
> +     cmd = sys.argv[1]
> +     if cmd == "setup":
> +-        errors = do_setup()
> +-        errors += scan_setup_py()
> +-        if errors:
> +-            sys.exit(1)
> ++        setup_command()
> +--
> +2.41.0
> +

_______________________________________________
buildroot mailing list
buildroot@buildroot.org
https://lists.buildroot.org/mailman/listinfo/buildroot


^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: [Buildroot] [PATCH 25/30] package/python-iptables: use sysconfig.get_path instead of get_python_lib
  2023-10-26  9:26 ` [Buildroot] [PATCH 25/30] package/python-iptables: use sysconfig.get_path instead of get_python_lib Adam Duskett
@ 2023-11-04 21:36   ` Arnout Vandecappelle via buildroot
  0 siblings, 0 replies; 54+ messages in thread
From: Arnout Vandecappelle via buildroot @ 2023-11-04 21:36 UTC (permalink / raw)
  To: Adam Duskett, buildroot
  Cc: Andrey Smirnov, James Hilliard, Asaf Kahlon, Julien Olivain,
	Thomas Petazzoni, Mauro Condarelli



On 26/10/2023 11:26, Adam Duskett wrote:
> distutils is removed in Python 3.12.0. Switch to using sysconfig.get_path to
> facilitate the migration.
> 
> Signed-off-by: Adam Duskett <adam.duskett@amarulasolutions.com>

  Applied to master, thanks.

  Regards,
  Arnout

> ---
>   ...g-get_path-instead-of-get_python_lib.patch | 40 +++++++++++++++++++
>   1 file changed, 40 insertions(+)
>   create mode 100644 package/python-iptables/0001-use-sysconfig-get_path-instead-of-get_python_lib.patch
> 
> diff --git a/package/python-iptables/0001-use-sysconfig-get_path-instead-of-get_python_lib.patch b/package/python-iptables/0001-use-sysconfig-get_path-instead-of-get_python_lib.patch
> new file mode 100644
> index 0000000000..2a4de3daf2
> --- /dev/null
> +++ b/package/python-iptables/0001-use-sysconfig-get_path-instead-of-get_python_lib.patch
> @@ -0,0 +1,40 @@
> +From fd415a3613fad872062fb7cb4e271ac1476402ef Mon Sep 17 00:00:00 2001
> +From: Adam Duskett <adam.duskett@amarulasolutions.com>
> +Date: Tue, 24 Oct 2023 08:47:12 +0200
> +Subject: [PATCH] use sysconfig.get_path instead of get_python_lib
> +
> +Distutils has been removed from python 3.12.0. Use sysconfig.get_path instead
> +of get_python_lib.
> +
> +Upstream: https://github.com/ldx/python-iptables/pull/340
> +
> +Signed-off-by: Adam Duskett <adam.duskett@amarulasolutions.com>
> +---
> + iptc/util.py | 4 ++--
> + 1 file changed, 2 insertions(+), 2 deletions(-)
> +
> +diff --git a/iptc/util.py b/iptc/util.py
> +index 04fe905..94befc5 100644
> +--- a/iptc/util.py
> ++++ b/iptc/util.py
> +@@ -3,7 +3,7 @@ import os
> + import sys
> + import ctypes
> + import ctypes.util
> +-from distutils.sysconfig import get_python_lib
> ++import sysconfig
> + from itertools import product
> + from subprocess import Popen, PIPE
> + from sys import version_info
> +@@ -64,7 +64,7 @@ def _do_find_library(name):
> +
> +     # probably we have been installed in a virtualenv
> +     try:
> +-        lib = ctypes.CDLL(os.path.join(get_python_lib(), name),
> ++        lib = ctypes.CDLL(os.path.join(sysconfig.get_path("purelib"), name),
> +                           mode=ctypes.RTLD_GLOBAL)
> +         return lib
> +     except:
> +--
> +2.41.0
> +
_______________________________________________
buildroot mailing list
buildroot@buildroot.org
https://lists.buildroot.org/mailman/listinfo/buildroot

^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: [Buildroot] [PATCH 26/30] package/python-pathtools: add 0001-replace-imp.patch
  2023-10-26  9:26 ` [Buildroot] [PATCH 26/30] package/python-pathtools: add 0001-replace-imp.patch Adam Duskett
@ 2023-11-04 21:36   ` Arnout Vandecappelle via buildroot
  0 siblings, 0 replies; 54+ messages in thread
From: Arnout Vandecappelle via buildroot @ 2023-11-04 21:36 UTC (permalink / raw)
  To: Adam Duskett, buildroot
  Cc: Andrey Smirnov, James Hilliard, Asaf Kahlon, Julien Olivain,
	Thomas Petazzoni, Mauro Condarelli



On 26/10/2023 11:26, Adam Duskett wrote:
> In preperation of Python 3.12.0, add a patch to python-pathtools that removes
> the reliance on the imp module following the instructions found here:
> https://docs.python.org/3.12/whatsnew/3.12.html#removed
> 
> Also tested with Python 3.9.2 on Debian 11.
> 
> Signed-off-by: Adam Duskett <adam.duskett@amarulasolutions.com>

  Wow, this hasn't had a commit in 7 years...

  Applied to master, thanks.

  Regards,
  Arnout

> ---
>   .../python-pathtools/0001-replace-imp.patch   | 55 +++++++++++++++++++
>   1 file changed, 55 insertions(+)
>   create mode 100644 package/python-pathtools/0001-replace-imp.patch
> 
> diff --git a/package/python-pathtools/0001-replace-imp.patch b/package/python-pathtools/0001-replace-imp.patch
> new file mode 100644
> index 0000000000..a5bc240cea
> --- /dev/null
> +++ b/package/python-pathtools/0001-replace-imp.patch
> @@ -0,0 +1,55 @@
> +From e2372bbecdf46a100b09126f2951431c1929637b Mon Sep 17 00:00:00 2001
> +From: Adam Duskett <adam.duskett@amarulasolutions.com>
> +Date: Tue, 24 Oct 2023 08:59:21 +0200
> +Subject: [PATCH] Replace imp
> +
> +The imp module has been removed in python 3.12.0.
> +
> +This change has also been tested with Python 3.9.2 on Debian 11.
> +
> +From: https://docs.python.org/3.12/whatsnew/3.12.html#removed, follow the
> +instructions to add the load_source method back into setup.py.
> +
> +Upstream: https://github.com/gorakhargosh/pathtools/pull/14
> +
> +Signed-off-by: Adam Duskett <adam.duskett@amarulasolutions.com>
> +---
> + setup.py | 19 ++++++++++++++++---
> + 1 file changed, 16 insertions(+), 3 deletions(-)
> +
> +diff --git a/setup.py b/setup.py
> +index 4718885..1be0315 100644
> +--- a/setup.py
> ++++ b/setup.py
> +@@ -22,12 +22,25 @@
> + # THE SOFTWARE.
> +
> + import os
> +-import imp
> ++import importlib.util
> ++import importlib.machinery
> + from setuptools import setup
> +
> + PKG_DIR = 'pathtools'
> +-version = imp.load_source('version',
> +-                          os.path.join(PKG_DIR, 'version.py'))
> ++
> ++# From: https://docs.python.org/3.12/whatsnew/3.12.html#removed
> ++def load_source(modname, filename):
> ++    loader = importlib.machinery.SourceFileLoader(modname, filename)
> ++    spec = importlib.util.spec_from_file_location(modname, filename, loader=loader)
> ++    module = importlib.util.module_from_spec(spec)
> ++    # The module is always executed and not cached in sys.modules.
> ++    # Uncomment the following line to cache the module.
> ++    # sys.modules[module.__name__] = module
> ++    loader.exec_module(module)
> ++    return module
> ++
> ++version = load_source('version',
> ++                      os.path.join(PKG_DIR, 'version.py'))
> +
> + def read_file(filename):
> +     """
> +--
> +2.41.0
> +
_______________________________________________
buildroot mailing list
buildroot@buildroot.org
https://lists.buildroot.org/mailman/listinfo/buildroot

^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: [Buildroot] [PATCH 17/30] package/python-systemd: bump version to 235
  2023-11-04 17:59   ` Arnout Vandecappelle via buildroot
@ 2023-11-05 18:20     ` Adam Duskett
  0 siblings, 0 replies; 54+ messages in thread
From: Adam Duskett @ 2023-11-05 18:20 UTC (permalink / raw)
  To: Arnout Vandecappelle
  Cc: Andrey Smirnov, James Hilliard, Asaf Kahlon, Julien Olivain,
	Thomas Petazzoni, buildroot, Mauro Condarelli


[-- Attachment #1.1: Type: text/plain, Size: 3005 bytes --]

Arnout;

This is the latest version of python-systemd.
Here is a quick check I did: https://pypi.org/project/systemd-python/

Adam Duskett

Senior Embedded Systems Developer

M. +1208-515-8102

adam.duskett@amarulasolutions.com

__________________________________


Amarula Solutions BV

Joop Geesinkweg 125, 1114 AB, Amsterdam, NL

T. +31 (0)85 111 9170
info@amarulasolutions.com

www.amarulasolutions.com



On Sat, Nov 4, 2023 at 11:59 AM Arnout Vandecappelle <arnout@mind.be> wrote:

>
>
> On 26/10/2023 11:26, Adam Duskett wrote:
> > Signed-off-by: Adam Duskett <adam.duskett@amarulasolutions.com>
> > ---
> >   package/python-systemd/python-systemd.hash | 4 ++--
> >   package/python-systemd/python-systemd.mk   | 6 +++---
> >   2 files changed, 5 insertions(+), 5 deletions(-)
> >
> > diff --git a/package/python-systemd/python-systemd.hash
> b/package/python-systemd/python-systemd.hash
> > index 789b48590d..6cc6639218 100644
> > --- a/package/python-systemd/python-systemd.hash
> > +++ b/package/python-systemd/python-systemd.hash
> > @@ -1,5 +1,5 @@
> >   # md5 from https://pypi.python.org/pypi/systemd-python/
> > -md5  5071ea5bcb976186e92a3f5e75df221d  systemd-python-234.tar.gz
> > +md5  93f3ca09f35719ca6a4edd1d62d38dd4  systemd-python-235.tar.gz
> >   # Locally computed
> > -sha256
> fd0e44bf70eadae45aadc292cb0a7eb5b0b6372cd1b391228047d33895db83e7
> systemd-python-234.tar.gz
> > +sha256
> 4e57f39797fd5d9e2d22b8806a252d7c0106c936039d1e71c8c6b8008e695c0a
> systemd-python-235.tar.gz
> >   sha256
> dc626520dcd53a22f727af3ee42c770e56c97a64fe3adb063799d8ab032fe551
> LICENSE.txt
> > diff --git a/package/python-systemd/python-systemd.mk
> b/package/python-systemd/python-systemd.mk
> > index 3ccfd57ce5..24ba84df76 100644
> > --- a/package/python-systemd/python-systemd.mk
> > +++ b/package/python-systemd/python-systemd.mk
> > @@ -4,10 +4,10 @@
> >   #
> >
>  ################################################################################
> >
> > -PYTHON_SYSTEMD_VERSION = 234 # Should be kept in sync with
> $(SYSTEMD_VERSION)
> > +PYTHON_SYSTEMD_VERSION = 235 # Should be kept in sync with
> $(SYSTEMD_VERSION)
>
>   SYSTEMD_VERSION is 254.5, not 235... What gives?
>
>   (We should anyway probably put that comment in systemd.mk, not so much
> here...)
>
>   Regards,
>   Arnout
>
> >   PYTHON_SYSTEMD_SOURCE = systemd-python-$(PYTHON_SYSTEMD_VERSION).tar.gz
> > -PYTHON_SYSTEMD_SITE =
> https://pypi.python.org/packages/e8/a8/00ba0f605837a8f69523e6c3a4fb14675a6430c163f836540129c50b3aef
> > -PYTHON_SYSTEMD_SETUP_TYPE = distutils
> > +PYTHON_SYSTEMD_SITE =
> https://files.pythonhosted.org/packages/10/9e/ab4458e00367223bda2dd7ccf0849a72235ee3e29b36dce732685d9b7ad9
> > +PYTHON_SYSTEMD_SETUP_TYPE = setuptools
> >   PYTHON_SYSTEMD_LICENSE = LGPL-2.1
> >   PYTHON_SYSTEMD_LICENSE_FILES = LICENSE.txt
> >   PYTHON_SYSTEMD_DEPENDENCIES = systemd # To be able to link against
> libsystemd
>

[-- Attachment #1.2: Type: text/html, Size: 7645 bytes --]

[-- Attachment #2: Type: text/plain, Size: 150 bytes --]

_______________________________________________
buildroot mailing list
buildroot@buildroot.org
https://lists.buildroot.org/mailman/listinfo/buildroot

^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: [Buildroot] [PATCH 28/30] package/python-pygame: drop package
  2023-10-26 14:13   ` Marcus Hoffmann via buildroot
@ 2023-11-09 21:55     ` Yann E. MORIN
  2023-11-10  7:17       ` Yann E. MORIN
  2023-11-10 14:11     ` Peter Korsgaard
  1 sibling, 1 reply; 54+ messages in thread
From: Yann E. MORIN @ 2023-11-09 21:55 UTC (permalink / raw)
  To: Marcus Hoffmann; +Cc: buildroot

Marcus, All,

On 2023-10-26 16:13 +0200, Marcus Hoffmann via buildroot spake thusly:
> On 26.10.23 11:26, Adam Duskett wrote:
> >The python-pygame package has not recieved any update since
> >Sun May 1 22:15:17 2016 (commit: a9ec96e545102ae5ccd4280323d35360b0a5072d)
> >
> >As python 3.12.0 no longer supports distutils, drop the package as the package
> >is clearly unmaintained.
> 
> I'd like to update the package to a recent version of pygame instead.
> Updating the package should be fairly straightforward (famous last
> words...) but I've struggled to find a platform (real hardware or
> virtualized) where I can actually test this with actual graphical
> output. :-/

You just got an extension deadline to update this package, as I
committed a patch from James to switch to setuptools:

    e759e927b344  package/python-pygame: switch from distutils to setuptools

But Adam is right to suggest dropping the package, if it han't been
updated in more than 7 years...

Regards,
Yann E. MORIN.

> Marcus
> 
> >
> >Signed-off-by: Adam Duskett <adam.duskett@amarulasolutions.com>
> >---
> >  Config.in.legacy                         |   7 ++
> >  package/Config.in                        |   2 -
> >  package/python-pygame/Config.in          |  50 ----------
> >  package/python-pygame/python-pygame.hash |   3 -
> >  package/python-pygame/python-pygame.mk   | 111 -----------------------
> >  5 files changed, 7 insertions(+), 166 deletions(-)
> >  delete mode 100644 package/python-pygame/Config.in
> >  delete mode 100644 package/python-pygame/python-pygame.hash
> >  delete mode 100644 package/python-pygame/python-pygame.mk
> >
> >diff --git a/Config.in.legacy b/Config.in.legacy
> >index 8e406f2b13..f1d5f345c3 100644
> >--- a/Config.in.legacy
> >+++ b/Config.in.legacy
> >@@ -153,6 +153,13 @@ config BR2_PACKAGE_PYTHON_PYXB
> >        python-pyxb has been removed due to being abandoned and
> >        distutils no longer being supported in python 3.12.0.
> >
> >+config BR2_PACKAGE_PYTHON_PYGAME
> >+     bool "python-pygame removed"
> >+     select BR2_LEGACY
> >+     help
> >+       python-pygame has been removed due to being abandoned and
> >+       distutils no longer being supported in python 3.12.0.
> >+
> >  config BR2_KERNEL_HEADERS_6_4
> >      bool "kernel headers version 6.4.x are no longer supported"
> >      select BR2_LEGACY
> >diff --git a/package/Config.in b/package/Config.in
> >index 4e489c4706..d756094c4b 100644
> >--- a/package/Config.in
> >+++ b/package/Config.in
> >@@ -1228,7 +1228,6 @@ menu "External python modules"
> >      source "package/python-pydyf/Config.in"
> >      source "package/python-pyelftools/Config.in"
> >      source "package/python-pyftpdlib/Config.in"
> >-     source "package/python-pygame/Config.in"
> >      source "package/python-pygments/Config.in"
> >      source "package/python-pyhamcrest/Config.in"
> >      source "package/python-pyicu/Config.in"
> >@@ -1267,7 +1266,6 @@ menu "External python modules"
> >      source "package/python-pytz/Config.in"
> >      source "package/python-pyudev/Config.in"
> >      source "package/python-pyusb/Config.in"
> >-     source "package/python-pyxb/Config.in"
> >      source "package/python-pyyaml/Config.in"
> >      source "package/python-pyzmq/Config.in"
> >      source "package/python-qrcode/Config.in"
> >diff --git a/package/python-pygame/Config.in b/package/python-pygame/Config.in
> >deleted file mode 100644
> >index 57eb020742..0000000000
> >--- a/package/python-pygame/Config.in
> >+++ /dev/null
> >@@ -1,50 +0,0 @@
> >-config BR2_PACKAGE_PYTHON_PYGAME
> >-     bool "python-pygame"
> >-     select BR2_PACKAGE_SDL
> >-     help
> >-       Pygame is a cross-platfrom library designed to make it easy
> >-       to write multimedia software, such as games, in
> >-       Python. Pygame requires the Python language and SDL
> >-       multimedia library.
> >-       It can also make use of several other popular libraries.
> >-
> >-       http://www.pygame.org/
> >-
> >-if BR2_PACKAGE_PYTHON_PYGAME
> >-config BR2_PACKAGE_PYTHON_PYGAME_IMAGE
> >-     bool "pygame.image"
> >-     select BR2_PACKAGE_SDL_IMAGE
> >-     select BR2_PACKAGE_SDL_IMAGE_PNG
> >-     select BR2_PACKAGE_SDL_IMAGE_JPEG
> >-     help
> >-       pygame module for loading, saving and transfering images.
> >-       Will autoselect sdl_image with png and jpeg support.
> >-
> >-config BR2_PACKAGE_PYTHON_PYGAME_EXAMPLES
> >-     bool "pygame.examples"
> >-     help
> >-       Include examples.
> >-       Selecting this option adds about 1.5 MB to the target file
> >-       system.
> >-
> >-config BR2_PACKAGE_PYTHON_PYGAME_FONT
> >-     bool "pygame.font"
> >-     select BR2_PACKAGE_SDL_TTF
> >-     help
> >-       pygame module for loading and rendering fonts.
> >-       Will autoselect sdl_ttf.
> >-
> >-config BR2_PACKAGE_PYTHON_PYGAME_MIXER
> >-     bool "pygame.mixer"
> >-     select BR2_PACKAGE_SDL_MIXER
> >-     help
> >-       pygame module for loading and playing sounds.
> >-       Will autoselect sdl_mixer.
> >-
> >-config BR2_PACKAGE_PYTHON_PYGAME_SCRAP
> >-     bool "pygame.scrap"
> >-     depends on BR2_PACKAGE_SDL_X11
> >-     help
> >-       pygame module for clipboard support (X11 needed)
> >-
> >-endif
> >diff --git a/package/python-pygame/python-pygame.hash b/package/python-pygame/python-pygame.hash
> >deleted file mode 100644
> >index c0496515e2..0000000000
> >--- a/package/python-pygame/python-pygame.hash
> >+++ /dev/null
> >@@ -1,3 +0,0 @@
> >-# Locally computed
> >-sha256  f95a7dd68ea294d415e36e068d2f533c5a01c67773452d14a535c5c7455681fe  pygame-d61ea8eabd56.tar.gz
> >-sha256  a190dc9c8043755d90f8b0a75fa66b9e42d4af4c980bf5ddc633f0124db3cee7  LGPL
> >diff --git a/package/python-pygame/python-pygame.mk b/package/python-pygame/python-pygame.mk
> >deleted file mode 100644
> >index 600dd9e743..0000000000
> >--- a/package/python-pygame/python-pygame.mk
> >+++ /dev/null
> >@@ -1,111 +0,0 @@
> >-################################################################################
> >-#
> >-# python-pygame
> >-#
> >-################################################################################
> >-
> >-# stable 1.9.1 release requires V4L which has been wiped out of recent Linux
> >-# kernels, so use latest mercurial revision until next stable release is out.
> >-PYTHON_PYGAME_VERSION = d61ea8eabd56
> >-PYTHON_PYGAME_SOURCE = pygame-$(PYTHON_PYGAME_VERSION).tar.gz
> >-PYTHON_PYGAME_SITE = https://bitbucket.org/pygame/pygame
> >-PYTHON_PYGAME_SITE_METHOD = hg
> >-PYTHON_PYGAME_SETUP_TYPE = distutils
> >-PYTHON_PYGAME_LICENSE = LGPL-2.1+
> >-PYTHON_PYGAME_LICENSE_FILES = LGPL
> >-
> >-ifeq ($(BR2_PACKAGE_PYTHON_PYGAME_IMAGE),y)
> >-PYTHON_PYGAME_OPT_DEPENDS += sdl_image
> >-endif
> >-
> >-ifeq ($(BR2_PACKAGE_PYTHON_PYGAME_FONT),y)
> >-PYTHON_PYGAME_OPT_DEPENDS += sdl_ttf
> >-endif
> >-
> >-ifeq ($(BR2_PACKAGE_PYTHON_PYGAME_MIXER),y)
> >-PYTHON_PYGAME_OPT_DEPENDS += sdl_mixer
> >-endif
> >-
> >-PYTHON_PYGAME_DEPENDENCIES = sdl $(PYTHON_PYGAME_OPT_DEPENDS)
> >-
> >-ifneq ($(BR2_PACKAGE_PYTHON_PYGAME_IMAGE),y)
> >-define PYTHON_PYGAME_UNCONFIGURE_IMAGE
> >-     $(SED) 's/^imageext/#imageext/' $(@D)/Setup
> >-endef
> >-endif
> >-
> >-ifneq ($(BR2_PACKAGE_PYTHON_PYGAME_FONT),y)
> >-define PYTHON_PYGAME_UNCONFIGURE_FONT
> >-     $(SED) 's/^font/#font/' $(@D)/Setup
> >-endef
> >-endif
> >-
> >-ifneq ($(BR2_PACKAGE_PYTHON_PYGAME_MIXER),y)
> >-define PYTHON_PYGAME_UNCONFIGURE_MIXER
> >-     $(SED) 's/^mixer/#mixer/g' $(@D)/Setup
> >-endef
> >-endif
> >-
> >-# Both require numpy or numeric python module
> >-define PYTHON_PYGAME_UNCONFIGURE_SNDARRAY
> >-     $(SED) 's/^_numericsndarray/#_numericsndarray/' $(@D)/Setup
> >-endef
> >-
> >-define PYTHON_PYGAME_UNCONFIGURE_SURFARRAY
> >-     $(SED) 's/^_numericsurfarray/#_numericsurfarray/' $(@D)/Setup
> >-endef
> >-
> >-# Requires smpeg
> >-define PYTHON_PYGAME_UNCONFIGURE_MOVIE
> >-     $(SED) 's/^movie/#movie/' $(@D)/Setup
> >-endef
> >-
> >-ifneq ($(BR2_PACKAGE_PYTHON_PYGAME_SCRAP),y)
> >-define PYTHON_PYGAME_UNCONFIGURE_SCRAP
> >-     $(SED) 's/^scrap/#scrap/' $(@D)/Setup
> >-endef
> >-endif
> >-
> >-define PYTHON_PYGAME_UNCONFIGURE_FREETYPE
> >-     $(SED) 's/^_freetype/#_freetype/' $(@D)/Setup
> >-endef
> >-
> >-PYTHON_PYGAME_SDL_FLAGS = `$(STAGING_DIR)/usr/bin/sdl-config --cflags`
> >-PYTHON_PYGAME_SDL_FLAGS += `$(STAGING_DIR)/usr/bin/sdl-config --libs`
> >-
> >-# Pygame needs a Setup file where options should be commented out if
> >-# dependencies are not available
> >-define PYTHON_PYGAME_CONFIGURE_CMDS
> >-     cp -f $(@D)/Setup.in $(@D)/Setup
> >-     $(SED) "s~^SDL = ~SDL = $(PYTHON_PYGAME_SDL_FLAGS) \n#~" $(@D)/Setup
> >-     $(SED) 's/^pypm/#pypm/' $(@D)/Setup
> >-     $(PYTHON_PYGAME_UNCONFIGURE_IMAGE)
> >-     $(PYTHON_PYGAME_UNCONFIGURE_FONT)
> >-     $(PYTHON_PYGAME_UNCONFIGURE_MIXER)
> >-     $(PYTHON_PYGAME_UNCONFIGURE_SNDARRAY)
> >-     $(PYTHON_PYGAME_UNCONFIGURE_SURFARRAY)
> >-     $(PYTHON_PYGAME_UNCONFIGURE_MOVIE)
> >-     $(PYTHON_PYGAME_UNCONFIGURE_SCRAP)
> >-     $(PYTHON_PYGAME_UNCONFIGURE_FREETYPE)
> >-endef
> >-
> >-define PYTHON_PYGAME_REMOVE_DOC
> >-     rm -rf $(TARGET_DIR)/usr/lib/python*/site-packages/pygame/docs
> >-endef
> >-
> >-PYTHON_PYGAME_POST_INSTALL_TARGET_HOOKS += PYTHON_PYGAME_REMOVE_DOC
> >-
> >-define PYTHON_PYGAME_REMOVE_TESTS
> >-     rm -rf $(TARGET_DIR)/usr/lib/python*/site-packages/pygame/tests
> >-endef
> >-
> >-PYTHON_PYGAME_POST_INSTALL_TARGET_HOOKS += PYTHON_PYGAME_REMOVE_TESTS
> >-
> >-ifneq ($(BR2_PACKAGE_PYTHON_PYGAME_EXAMPLES),y)
> >-define PYTHON_PYGAME_REMOVE_EXAMPLES
> >-     rm -rf $(TARGET_DIR)/usr/lib/python$(PYTHON3_VERSION_MAJOR)/site-packages/pygame/examples
> >-endef
> >-PYTHON_PYGAME_POST_INSTALL_TARGET_HOOKS += PYTHON_PYGAME_REMOVE_EXAMPLES
> >-endif
> >-
> >-$(eval $(python-package))
> ________________________________
> 
> othermo GmbH | Sitz der Gesellschaft: Alzenau | Amtsgericht Aschaffenburg: HRB 14783 | USt-IdNr.: DE319977978 | Geschäftsführung: Dr. Dennis Metz.
> _______________________________________________
> buildroot mailing list
> buildroot@buildroot.org
> https://lists.buildroot.org/mailman/listinfo/buildroot

-- 
.-----------------.--------------------.------------------.--------------------.
|  Yann E. MORIN  | Real-Time Embedded | /"\ ASCII RIBBON | Erics' conspiracy: |
| +33 662 376 056 | Software  Designer | \ / CAMPAIGN     |  ___               |
| +33 561 099 427 `------------.-------:  X  AGAINST      |  \e/  There is no  |
| http://ymorin.is-a-geek.org/ | _/*\_ | / \ HTML MAIL    |   v   conspiracy.  |
'------------------------------^-------^------------------^--------------------'
_______________________________________________
buildroot mailing list
buildroot@buildroot.org
https://lists.buildroot.org/mailman/listinfo/buildroot

^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: [Buildroot] [PATCH 28/30] package/python-pygame: drop package
  2023-11-09 21:55     ` Yann E. MORIN
@ 2023-11-10  7:17       ` Yann E. MORIN
  0 siblings, 0 replies; 54+ messages in thread
From: Yann E. MORIN @ 2023-11-10  7:17 UTC (permalink / raw)
  To: Marcus Hoffmann; +Cc: buildroot

Marcus, All,

On 2023-11-09 22:55 +0100, Yann E. MORIN spake thusly:
> On 2023-10-26 16:13 +0200, Marcus Hoffmann via buildroot spake thusly:
> > On 26.10.23 11:26, Adam Duskett wrote:
> > >As python 3.12.0 no longer supports distutils, drop the package as the package
> > >is clearly unmaintained.
> > I'd like to update the package to a recent version of pygame instead.
> > Updating the package should be fairly straightforward (famous last
> > words...) but I've struggled to find a platform (real hardware or
> > virtualized) where I can actually test this with actual graphical
> > output. :-/
> You just got an extension deadline to update this package, as I
> committed a patch from James to switch to setuptools:
>     e759e927b344  package/python-pygame: switch from distutils to setuptools

Adam reports that even with the conversion to setuptools, python-pygame
still does not build.

So, if this package is really helpfull to you, please see if you can
update it before it gets ripped away, as I've marked the patch back to
"new" in patchwork, i.e. pending (not rejected).

Regards,
Yann E. MORIN.

-- 
.-----------------.--------------------.------------------.--------------------.
|  Yann E. MORIN  | Real-Time Embedded | /"\ ASCII RIBBON | Erics' conspiracy: |
| +33 662 376 056 | Software  Designer | \ / CAMPAIGN     |  ___               |
| +33 561 099 427 `------------.-------:  X  AGAINST      |  \e/  There is no  |
| http://ymorin.is-a-geek.org/ | _/*\_ | / \ HTML MAIL    |   v   conspiracy.  |
'------------------------------^-------^------------------^--------------------'
_______________________________________________
buildroot mailing list
buildroot@buildroot.org
https://lists.buildroot.org/mailman/listinfo/buildroot

^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: [Buildroot] [PATCH 28/30] package/python-pygame: drop package
  2023-10-26 14:13   ` Marcus Hoffmann via buildroot
  2023-11-09 21:55     ` Yann E. MORIN
@ 2023-11-10 14:11     ` Peter Korsgaard
  1 sibling, 0 replies; 54+ messages in thread
From: Peter Korsgaard @ 2023-11-10 14:11 UTC (permalink / raw)
  To: Marcus Hoffmann via buildroot; +Cc: Marcus Hoffmann

>>>>> "Marcus" == Marcus Hoffmann via buildroot <buildroot@buildroot.org> writes:

 > Hi,
 > On 26.10.23 11:26, Adam Duskett wrote:
 >> The python-pygame package has not recieved any update since
 >> Sun May 1 22:15:17 2016 (commit: a9ec96e545102ae5ccd4280323d35360b0a5072d)
 >> 
 >> As python 3.12.0 no longer supports distutils, drop the package as the package
 >> is clearly unmaintained.

 > I'd like to update the package to a recent version of pygame instead.
 > Updating the package should be fairly straightforward (famous last
 > words...) but I've struggled to find a platform (real hardware or
 > virtualized) where I can actually test this with actual graphical
 > output. :-/

Great, I also had a quick look at it recently as it is one of the few
packages with a hg site, but ran out of time.

Given that pygame supports SDL2, I guess you could just test in one of
our QEMU defconfigs?

-- 
Bye, Peter Korsgaard
_______________________________________________
buildroot mailing list
buildroot@buildroot.org
https://lists.buildroot.org/mailman/listinfo/buildroot

^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: [Buildroot] [PATCH 00/30] package/python3: bump version to 3.12.0
       [not found] <20231026092701.12345-1-adam.duskett__11152.3364832223$1698312455$gmane$org@amarulasolutions.com>
  2023-10-26 16:51 ` Bernd Kuhls
@ 2023-10-27  5:09 ` Bernd Kuhls
  1 sibling, 0 replies; 54+ messages in thread
From: Bernd Kuhls @ 2023-10-27  5:09 UTC (permalink / raw)
  To: buildroot

Am Thu, 26 Oct 2023 11:26:31 +0200 schrieb Adam Duskett:

> Drop the following packages:
>   python-pygame python-pyxb python-crossbar

Hi Adam,

there are entries in DEVELOPERS which need to be removed, get-developers 
prints these warnings:

WARNING: 'package/python-pygame/' doesn't match any file, line 1687
WARNING: 'package/python-crossbar/' doesn't match any file, line 2089

Regards, Bernd

_______________________________________________
buildroot mailing list
buildroot@buildroot.org
https://lists.buildroot.org/mailman/listinfo/buildroot

^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: [Buildroot] [PATCH 00/30] package/python3: bump version to 3.12.0
       [not found] <20231026092701.12345-1-adam.duskett__11152.3364832223$1698312455$gmane$org@amarulasolutions.com>
@ 2023-10-26 16:51 ` Bernd Kuhls
  2023-10-27  5:09 ` Bernd Kuhls
  1 sibling, 0 replies; 54+ messages in thread
From: Bernd Kuhls @ 2023-10-26 16:51 UTC (permalink / raw)
  To: buildroot

Am Thu, 26 Oct 2023 11:26:31 +0200 schrieb Adam Duskett:

>   package/python3: bump version to 3.12.0

Hi,

building this defconfig is broken with python 3.12:

BR2_PACKAGE_PYTHON3=y
BR2_PACKAGE_KMOD=y

libkmod/python/kmod/kmod.c:6022:34: error: 'PyThreadState' {aka 'struct 
_ts'} has no member named 'curexc_traceback'

libkmod/python/kmod/kmod.c:6782:53: error: 'PyLongObject' {aka 'struct 
_longobject'} has no member named 'ob_digit'

Regards, Bernd

_______________________________________________
buildroot mailing list
buildroot@buildroot.org
https://lists.buildroot.org/mailman/listinfo/buildroot

^ permalink raw reply	[flat|nested] 54+ messages in thread

end of thread, other threads:[~2023-11-10 14:11 UTC | newest]

Thread overview: 54+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2023-10-26  9:26 [Buildroot] [PATCH 00/30] package/python3: bump version to 3.12.0 Adam Duskett
2023-10-26  9:26 ` [Buildroot] [PATCH 01/30] package/python3: use upstream build system to disable berkeleydb module Adam Duskett
2023-10-26  9:26 ` [Buildroot] [PATCH 02/30] package/python3: use upstream build system to disable uuid module Adam Duskett
2023-10-26  9:26 ` [Buildroot] [PATCH 03/30] package/python3: use upstream build system to disable bzip2/zlib/xz modules Adam Duskett
2023-10-26  9:26 ` [Buildroot] [PATCH 04/30] package/python3: use upstream build system to disable curses/readline modules Adam Duskett
2023-10-26  9:26 ` [Buildroot] [PATCH 05/30] package/python3: use upstream build system to disable ssl module Adam Duskett
2023-10-26  9:26 ` [Buildroot] [PATCH 06/30] package/python3: use upstream build system to disable ossaudiodev module Adam Duskett
2023-10-26  9:26 ` [Buildroot] [PATCH 07/30] package/python3: use upstream build system to disable unicodedata module Adam Duskett
2023-10-26  9:26 ` [Buildroot] [PATCH 08/30] package/python3: use upstream build system to disable nis module Adam Duskett
2023-10-26  9:26 ` [Buildroot] [PATCH 09/30] package/python3: use upstream build system to disable decimal module Adam Duskett
2023-10-26  9:26 ` [Buildroot] [PATCH 10/30] package/python3: use upstream build system to disable CJK codecs Adam Duskett
2023-10-26  9:26 ` [Buildroot] [PATCH 11/30] package/python3: use upstream build system to disable pyexpat module Adam Duskett
2023-10-26  9:26 ` [Buildroot] [PATCH 12/30] package/python3: use upstream build system to disable sqlite3 module Adam Duskett
2023-10-26  9:26 ` [Buildroot] [PATCH 13/30] package/python3: update patch and partly use upstream build system to disable tk module Adam Duskett
2023-11-04 17:47   ` Arnout Vandecappelle via buildroot
2023-10-26  9:26 ` [Buildroot] [PATCH 14/30] package/python3: Remove infrastructure to disable the build of certain extensions Adam Duskett
2023-10-26  9:26 ` [Buildroot] [PATCH 15/30] package/scons: bump version to 4.5.2 Adam Duskett
2023-11-04 17:54   ` Arnout Vandecappelle via buildroot
2023-11-04 17:55     ` Arnout Vandecappelle via buildroot
2023-10-26  9:26 ` [Buildroot] [PATCH 16/30] package/python-wsaccel: bump version to 0.6.6 Adam Duskett
2023-11-04 17:56   ` Arnout Vandecappelle via buildroot
2023-10-26  9:26 ` [Buildroot] [PATCH 17/30] package/python-systemd: bump version to 235 Adam Duskett
2023-11-04 17:59   ` Arnout Vandecappelle via buildroot
2023-11-05 18:20     ` Adam Duskett
2023-10-26  9:26 ` [Buildroot] [PATCH 18/30] package/python-iptables: bump version to 1.0.1 Adam Duskett
2023-11-04 18:14   ` Arnout Vandecappelle via buildroot
2023-10-26  9:26 ` [Buildroot] [PATCH 19/30] package/libftdi: add patch to move from distutils to sysconfig Adam Duskett
2023-10-26 11:49   ` Yegor Yefremov via buildroot
2023-11-04 18:29   ` Arnout Vandecappelle via buildroot
2023-10-26  9:26 ` [Buildroot] [PATCH 20/30] package/python-constantly: update versioneer to 0.29 Adam Duskett
2023-11-04 21:20   ` Arnout Vandecappelle via buildroot
2023-10-26  9:26 ` [Buildroot] [PATCH 21/30] package/python-magic-wormhole: " Adam Duskett
2023-11-04 21:30   ` Arnout Vandecappelle via buildroot
2023-10-26  9:26 ` [Buildroot] [PATCH 22/30] package/python-magic-wormhole-mailbox-server: " Adam Duskett
2023-11-04 21:31   ` Arnout Vandecappelle via buildroot
2023-10-26  9:26 ` [Buildroot] [PATCH 23/30] package/python-magic-wormhole-transit-relay: " Adam Duskett
2023-11-04 21:31   ` Arnout Vandecappelle via buildroot
2023-10-26  9:26 ` [Buildroot] [PATCH 24/30] package/python-spake2: " Adam Duskett
2023-11-04 21:32   ` Arnout Vandecappelle via buildroot
2023-10-26  9:26 ` [Buildroot] [PATCH 25/30] package/python-iptables: use sysconfig.get_path instead of get_python_lib Adam Duskett
2023-11-04 21:36   ` Arnout Vandecappelle via buildroot
2023-10-26  9:26 ` [Buildroot] [PATCH 26/30] package/python-pathtools: add 0001-replace-imp.patch Adam Duskett
2023-11-04 21:36   ` Arnout Vandecappelle via buildroot
2023-10-26  9:26 ` [Buildroot] [PATCH 27/30] package/python-pyxb: Drop package Adam Duskett
2023-10-26  9:26 ` [Buildroot] [PATCH 28/30] package/python-pygame: drop package Adam Duskett
2023-10-26 14:13   ` Marcus Hoffmann via buildroot
2023-11-09 21:55     ` Yann E. MORIN
2023-11-10  7:17       ` Yann E. MORIN
2023-11-10 14:11     ` Peter Korsgaard
2023-10-26  9:27 ` [Buildroot] [PATCH 29/30] package/python-crossbar: " Adam Duskett
2023-10-26  9:27 ` [Buildroot] [PATCH 30/30] package/python3: bump version to 3.12.0 Adam Duskett
2023-11-04 17:41 ` [Buildroot] [PATCH 00/30] " Arnout Vandecappelle via buildroot
     [not found] <20231026092701.12345-1-adam.duskett__11152.3364832223$1698312455$gmane$org@amarulasolutions.com>
2023-10-26 16:51 ` Bernd Kuhls
2023-10-27  5:09 ` Bernd Kuhls

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.