diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 694ed92..ab178a3 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -7,6 +7,11 @@ on: tags: - '*' +concurrency: + # Cancel previous runs of this workflow for the same branch + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: build: runs-on: ${{ matrix.os }} @@ -14,27 +19,107 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest] - python-version: ['3.7', '3.10', '3.11'] - pari-version: ['pari-2.9.5', 'pari-2.11.4', 'pari-2.13.0', 'pari-2.15.1'] + python-version: ['3.9', '3.10', '3.11', '3.12'] + pari-version: ['pari-2.11.4', 'pari-2.13.0', 'pari-2.15.4'] env: LC_ALL: C PARI_VERSION: ${{ matrix.pari-version }} steps: - name: Set up the repository - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - name: Install pari run: | - ccache -M 256M && ccache -s - export PATH="/usr/lib/ccache:$PATH" bash -x .install-pari.sh - name: Local build run: | - pip install sphinx cython cysignals - make build make install make check + pip install sphinx (cd docs && make html) + + dist: + runs-on: ubuntu-latest + steps: + - name: Check out ${{ env.SPKG }} + uses: actions/checkout@v4 + with: + path: build/pkgs/${{ env.SPKG }}/src + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + - name: Install prerequisites + run: | + sudo DEBIAN_FRONTEND=noninteractive apt-get update + sudo DEBIAN_FRONTEND=noninteractive apt-get install $DIST_PREREQ + python3 -m pip install build + - name: Run make dist, prepare upstream artifact + run: | + (cd build/pkgs/${{ env.SPKG }}/src && python3 -m build --sdist) \ + && mkdir -p upstream && cp build/pkgs/${{ env.SPKG }}/src/dist/*.tar.gz upstream/${{ env.SPKG }}-git.tar.gz \ + && echo "sage-package create ${{ env.SPKG }} --version git --tarball ${{ env.SPKG }}-git.tar.gz --type=standard" > upstream/update-pkgs.sh \ + && if [ -n "${{ env.REMOVE_PATCHES }}" ]; then echo "(cd ../build/pkgs/${{ env.SPKG }}/patches && rm -f ${{ env.REMOVE_PATCHES }}; :)" >> upstream/update-pkgs.sh; fi \ + && ls -l upstream/ + - uses: actions/upload-artifact@v3 + with: + path: upstream + name: upstream + + linux-sage: + uses: sagemath/sage/.github/workflows/docker.yml@develop + with: + targets: SAGE_CHECK=no SAGE_CHECK_PACKAGES="cypari" cypari + targets_optional: build/make/Makefile + sage_repo: sagemath/sage + sage_ref: develop + upstream_artifact: upstream + # We prefix the image name with the SPKG name ("cypari2-") to avoid the error + # 'Package "sage-docker-..." is already associated with another repository.' + docker_push_repository: ghcr.io/${{ github.repository }}/cypari2- + needs: [dist] + + linux-sage-incremental: + uses: sagemath/sage/.github/workflows/docker.yml@develop + with: + # Build incrementally from published Docker image + incremental: true + free_disk_space: true + from_docker_repository: ghcr.io/sagemath/sage/ + from_docker_target: "with-targets" + from_docker_tag: "dev" + docker_targets: "with-targets" + targets_pre: build/make/Makefile + targets: "cypari-uninstall build doc-html ptest" + targets_optional: build/make/Makefile + sage_repo: sagemath/sage + sage_ref: develop + upstream_artifact: upstream + # We prefix the image name with the SPKG name ("cypari2-") to avoid the error + # 'Package "sage-docker-..." is already associated with another repository.' + docker_push_repository: ghcr.io/${{ github.repository }}/cypari2- + needs: [linux-sage] + + macos-sage: + uses: sagemath/sage/.github/workflows/macos.yml@develop + with: + osversion_xcodeversion_toxenv_tuples: >- + [["latest", "", "homebrew-macos-usrlocal-minimal"], + ["latest", "", "homebrew-macos-usrlocal-standard"], + ["13", "xcode_15.0", "homebrew-macos-usrlocal-standard"], + ["latest", "", "conda-forge-macos-standard"]] + targets: SAGE_CHECK=no SAGE_CHECK_PACKAGES="cypari" cypari + # Standard setting: Test the current beta release of Sage + sage_repo: sagemath/sage + sage_ref: develop + upstream_artifact: upstream + needs: [dist] + +env: + # Ubuntu packages to install so that the project's "setup.py sdist" can succeed + DIST_PREREQ: libpari-dev pari-doc + # Name of this project in the Sage distribution + SPKG: cypari + # Remove all downstream patches + REMOVE_PATCHES: "*" diff --git a/MANIFEST.in b/MANIFEST.in index c836145..4d02d76 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,4 +1,5 @@ include LICENSE README.rst VERSION +include Makefile global-include *.py *.pyx *.pxd *.pxi recursive-exclude cypari2 auto_* diff --git a/Makefile b/Makefile index 8b5c9a4..a753997 100644 --- a/Makefile +++ b/Makefile @@ -6,11 +6,8 @@ PYTHON = python PIP = $(PYTHON) -m pip -v -build: - $(PYTHON) setup.py build - install: - $(PIP) install --no-index --upgrade . + $(PIP) install --upgrade . check: ulimit -s 8192; $(PYTHON) -u tests/rundoctest.py @@ -19,7 +16,8 @@ check: dist: chmod go+rX-w -R . - umask 0022 && $(PYTHON) setup.py sdist --formats=gztar + $(PIP) install build + umask 0022 && $(PYTHON) -m build --sdist -.PHONY: build install check dist +.PHONY: install check dist diff --git a/NEWS b/NEWS index 2432c83..d088674 100644 --- a/NEWS +++ b/NEWS @@ -2,6 +2,13 @@ cypari2 change log ================== +v2.1.4 +------ + +- compatibility with Python 3.12 [`https://github.com/sagemath/cypari2/pull/138`] +- compatibility with Cython 3 [`https://github.com/sagemath/cypari2/pull/139`] +- declare build dependencies in `pyproject.toml` [`https://github.com/sagemath/cypari2/pull/139`] + v2.1.3 ------ diff --git a/README.rst b/README.rst index 3cbb910..d429ea4 100644 --- a/README.rst +++ b/README.rst @@ -10,22 +10,23 @@ A Python interface to the number theory library `PARI/GP = 2.9.4 (header files and library) +- PARI/GP >= 2.9.4 (header files and library); see + https://doc.sagemath.org/html/en/reference/spkg/pari#spkg-pari + for availability in distributions (GNU/Linux, conda-forge, Homebrew, FreeBSD), + or install from source. - Python >= 3.6 - pip -- `cysignals `_ >= 1.7 -- Cython >= 0.29 Install cypari2 via the Python Package Index (PyPI) via @@ -34,10 +35,13 @@ Install cypari2 via the Python Package Index (PyPI) via $ pip install cypari2 [--user] (the optional option *--user* allows to install cypari2 for a single user -and avoids using pip with administrator rights). Depending on your operating -system the pip command might also be called pip3. +and avoids using pip with administrator rights). -If you want to try the development version use +`pip` builds the package using build isolation. All Python build dependencies +of the package, declared in pyproject.toml, are automatically installed in +a temporary virtual environment. + +If you want to try the development version, use :: @@ -51,12 +55,6 @@ already installed, try to reinstall cysignals and cypari2 $ pip install cysignals --upgrade [--user] $ pip install cypari2 --upgrade [--user] -Other -^^^^^ - -Any other way to install cypari2 is not supported. In particular, ``python -setup.py install`` will produce an error. - Usage ----- diff --git a/VERSION b/VERSION index ac2cdeb..7d2ed7c 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.1.3 +2.1.4 diff --git a/_custom_build_meta.py b/_custom_build_meta.py new file mode 100644 index 0000000..9573341 --- /dev/null +++ b/_custom_build_meta.py @@ -0,0 +1,2 @@ +# See https://setuptools.pypa.io/en/latest/build_meta.html#dynamic-build-dependencies-and-other-build-meta-tweaks +from setuptools.build_meta import * diff --git a/autogen/paths.py b/autogen/paths.py index 3e8194f..e879464 100644 --- a/autogen/paths.py +++ b/autogen/paths.py @@ -15,12 +15,13 @@ from __future__ import absolute_import, unicode_literals import os +import shutil + from glob import glob -from distutils.spawn import find_executable -# find_executable() returns None if nothing was found -gppath = find_executable("gp") +gppath = shutil.which("gp") + if gppath is None: # This almost certainly won't work, but we need to put something here prefix = "." diff --git a/cypari2/Py_SET_SIZE.h b/cypari2/Py_SET_SIZE.h deleted file mode 100644 index 5f18ab0..0000000 --- a/cypari2/Py_SET_SIZE.h +++ /dev/null @@ -1,8 +0,0 @@ -#include "Python.h" - -#if (PY_MAJOR_VERSION == 3) && (PY_MINOR_VERSION < 9) -// The function Py_SET_SIZE is defined starting with python 3.9. -void Py_SET_SIZE(PyVarObject *o, Py_ssize_t size){ - Py_SIZE(o) = size; -} -#endif diff --git a/cypari2/convert.pyx b/cypari2/convert.pyx index 7c2ed24..8d39c3c 100644 --- a/cypari2/convert.pyx +++ b/cypari2/convert.pyx @@ -54,14 +54,8 @@ from libc.math cimport INFINITY from .paridecl cimport * from .stack cimport new_gen, reset_avma from .string_utils cimport to_string, to_bytes - -cdef extern from *: - ctypedef struct PyLongObject: - digit* ob_digit - -cdef extern from "Py_SET_SIZE.h": - void Py_SET_SIZE(py_long o, Py_ssize_t size) - +from .pycore_long cimport (ob_digit, _PyLong_IsZero, _PyLong_IsNegative, + _PyLong_IsPositive, _PyLong_DigitCount, _PyLong_SetSignAndDigitCount) ######################################################################## # Conversion PARI -> Python @@ -424,7 +418,7 @@ cdef PyLong_FromINT(GEN g): cdef Py_ssize_t sizedigits_final = 0 cdef py_long x = _PyLong_New(sizedigits) - cdef digit* D = x.ob_digit + cdef digit* D = ob_digit(x) cdef digit d cdef ulong w @@ -452,10 +446,7 @@ cdef PyLong_FromINT(GEN g): sizedigits_final = i+1 # Set correct size - if signe(g) > 0: - Py_SET_SIZE(x, sizedigits_final) - else: - Py_SET_SIZE(x, -sizedigits_final) + _PyLong_SetSignAndDigitCount(x, signe(g), sizedigits_final) return x @@ -465,18 +456,18 @@ cdef PyLong_FromINT(GEN g): ######################################################################## cdef GEN PyLong_AS_GEN(py_long x): - cdef const digit* D = x.ob_digit + cdef const digit* D = ob_digit(x) # Size of the input cdef size_t sizedigits cdef long sgn - if Py_SIZE(x) == 0: + if _PyLong_IsZero(x): return gen_0 - elif Py_SIZE(x) > 0: - sizedigits = Py_SIZE(x) + elif _PyLong_IsPositive(x): + sizedigits = _PyLong_DigitCount(x) sgn = evalsigne(1) else: - sizedigits = -Py_SIZE(x) + sizedigits = _PyLong_DigitCount(x) sgn = evalsigne(-1) # Size of the output, in bits and in words diff --git a/cypari2/gen.pyx b/cypari2/gen.pyx index ce834fc..cee2687 100644 --- a/cypari2/gen.pyx +++ b/cypari2/gen.pyx @@ -328,7 +328,7 @@ cdef class Gen(Gen_base): >>> pari = Pari() >>> L = pari("vector(10,i,i^2)") >>> L.__iter__() - + <...generator object at ...> >>> [x for x in L] [1, 4, 9, 16, 25, 36, 49, 64, 81, 100] >>> list(L) diff --git a/cypari2/pycore_long.h b/cypari2/pycore_long.h new file mode 100644 index 0000000..ff1a73d --- /dev/null +++ b/cypari2/pycore_long.h @@ -0,0 +1,98 @@ +#include "Python.h" +#include + +#if PY_VERSION_HEX >= 0x030C00A5 +#define ob_digit(o) (((PyLongObject*)o)->long_value.ob_digit) +#else +#define ob_digit(o) (((PyLongObject*)o)->ob_digit) +#endif + +#if PY_VERSION_HEX >= 0x030C00A7 +// taken from cpython:Include/internal/pycore_long.h @ 3.12 + +/* Long value tag bits: + * 0-1: Sign bits value = (1-sign), ie. negative=2, positive=0, zero=1. + * 2: Reserved for immortality bit + * 3+ Unsigned digit count + */ +#define SIGN_MASK 3 +#define SIGN_ZERO 1 +#define SIGN_NEGATIVE 2 +#define NON_SIZE_BITS 3 + +static inline bool +_PyLong_IsZero(const PyLongObject *op) +{ + return (op->long_value.lv_tag & SIGN_MASK) == SIGN_ZERO; +} + +static inline bool +_PyLong_IsNegative(const PyLongObject *op) +{ + return (op->long_value.lv_tag & SIGN_MASK) == SIGN_NEGATIVE; +} + +static inline bool +_PyLong_IsPositive(const PyLongObject *op) +{ + return (op->long_value.lv_tag & SIGN_MASK) == 0; +} + +static inline Py_ssize_t +_PyLong_DigitCount(const PyLongObject *op) +{ + assert(PyLong_Check(op)); + return op->long_value.lv_tag >> NON_SIZE_BITS; +} + +#define TAG_FROM_SIGN_AND_SIZE(sign, size) ((1 - (sign)) | ((size) << NON_SIZE_BITS)) + +static inline void +_PyLong_SetSignAndDigitCount(PyLongObject *op, int sign, Py_ssize_t size) +{ + assert(size >= 0); + assert(-1 <= sign && sign <= 1); + assert(sign != 0 || size == 0); + op->long_value.lv_tag = TAG_FROM_SIGN_AND_SIZE(sign, (size_t)size); +} + +#else +// fallback for < 3.12 + +static inline bool +_PyLong_IsZero(const PyLongObject *op) +{ + return Py_SIZE(op) == 0; +} + +static inline bool +_PyLong_IsNegative(const PyLongObject *op) +{ + return Py_SIZE(op) < 0; +} + +static inline bool +_PyLong_IsPositive(const PyLongObject *op) +{ + return Py_SIZE(op) > 0; +} + +static inline Py_ssize_t +_PyLong_DigitCount(const PyLongObject *op) +{ + Py_ssize_t size = Py_SIZE(op); + return size < 0 ? -size : size; +} + +static inline void +_PyLong_SetSignAndDigitCount(PyLongObject *op, int sign, Py_ssize_t size) +{ +#if (PY_MAJOR_VERSION == 3) && (PY_MINOR_VERSION < 9) +// The function Py_SET_SIZE is defined starting with python 3.9. + Py_SIZE(o) = size; +#else + Py_SET_SIZE(op, sign < 0 ? -size : size); +#endif +} + +#endif diff --git a/cypari2/pycore_long.pxd b/cypari2/pycore_long.pxd new file mode 100644 index 0000000..41de637 --- /dev/null +++ b/cypari2/pycore_long.pxd @@ -0,0 +1,9 @@ +from cpython.longintrepr cimport py_long, digit + +cdef extern from "pycore_long.h": + digit* ob_digit(py_long o) + bint _PyLong_IsZero(py_long o) + bint _PyLong_IsNegative(py_long o) + bint _PyLong_IsPositive(py_long o) + Py_ssize_t _PyLong_DigitCount(py_long o) + void _PyLong_SetSignAndDigitCount(py_long o, int sign, Py_ssize_t size) diff --git a/docs/source/conf.py b/docs/source/conf.py index e11d56d..96d327d 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -50,9 +50,9 @@ master_doc = 'index' # General information about the project. -project = u'CyPari2' -copyright = u'2017, Many people' -author = u'Many people' +project = 'CyPari2' +copyright = '2017, Many people' +author = 'Many people' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -68,7 +68,7 @@ # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. -language = 'en' +#language = None # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. @@ -160,13 +160,14 @@ # Links to external resources (copied from Sage) extlinks = { - 'trac': ('https://trac.sagemath.org/%s', 'Sage ticket #'), - 'wikipedia': ('https://en.wikipedia.org/wiki/%s', 'Wikipedia article '), - 'arxiv': ('http://arxiv.org/abs/%s', 'Arxiv '), - 'oeis': ('https://oeis.org/%s', 'OEIS sequence '), - 'doi': ('https://dx.doi.org/%s', 'doi:'), - 'pari': ('http://pari.math.u-bordeaux.fr/dochtml/help/%s', 'pari:'), - 'mathscinet': ('http://www.ams.org/mathscinet-getitem?mr=%s', 'MathSciNet ') + 'trac': ('https://github.com/sagemath/sage/issues/%s', 'github issue #%s'), # support :trac: for backward compatibility + 'issue': ('https://github.com/sagemath/sage/issues/%s', 'github issue #%s'), + 'wikipedia': ('https://en.wikipedia.org/wiki/%s', 'Wikipedia article %s'), + 'arxiv': ('https://arxiv.org/abs/%s', 'arXiv %s'), + 'oeis': ('https://oeis.org/%s', 'OEIS sequence %s'), + 'doi': ('https://dx.doi.org/%s', 'doi:%s'), + 'pari': ('https://pari.math.u-bordeaux.fr/dochtml/help/%s', 'pari:%s'), + 'mathscinet': ('https://www.ams.org/mathscinet-getitem?mr=%s', 'MathSciNet %s') } diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..1689d81 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,8 @@ +[build-system] +requires = ["setuptools", + "Cython>=0.29", + "cysignals>=1.7"] +# We need access to the autogen package at build time. +# Hence we declare a custom build backend. +build-backend = "_custom_build_meta" # just re-exports setuptools.build_meta definitions +backend-path = ["."] diff --git a/setup.py b/setup.py index 2188711..9ded2f5 100755 --- a/setup.py +++ b/setup.py @@ -36,6 +36,8 @@ def finalize_options(self): "binding": True, "cdivision": True, "language_level": 2, + "legacy_implicit_noexcept": True, + "c_api_binop_methods": True, } _build_ext.finalize_options(self) @@ -66,7 +68,6 @@ def run(self): setup( name='cypari2', version=VERSION, - setup_requires=['Cython>=0.29'], install_requires=['cysignals>=1.7'], description="A Python interface to the number theory library PARI/GP", long_description=README,