diff --git a/.github/workflows/release-pypi.yml b/.github/workflows/release-pypi.yml index 3f15cd791..7b6d72510 100644 --- a/.github/workflows/release-pypi.yml +++ b/.github/workflows/release-pypi.yml @@ -1,20 +1,36 @@ --- -name: Release to PyPI +name: Publish to PyPI +# manually trigger this workflow on: - release: - types: [created] - + workflow_dispatch: + inputs: + tag: + description: 'The Git tag to create or test a release for. Official releases should always be made from an existing tag.' + required: true + type: string + official: + description: 'If true, publish to official PyPI and create GitHub release. Otherwise publish only to test PyPI.' + default: false + type: boolean jobs: build_sdist: name: Build source package runs-on: 'ubuntu-latest' steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 + with: + ref: ${{ inputs.tag }} + + - name: Set up Python + uses: actions/setup-python@v4 + with: + # HPy requires at least Python 3.8; we mostly work with >=3.10 + python-version: '>=3.10' - name: Install/Upgrade Python dependencies - run: python -m pip install --upgrade pip wheel + run: python -m pip install --upgrade pip wheel 'setuptools>=60.2' - name: Build and install Python source package run: | @@ -23,12 +39,14 @@ jobs: - name: Run tests run: | - pip install pytest pytest-xdist + make + pip install pytest pytest-xdist filelock pytest -n auto test/ - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v3 with: path: dist/*.tar.gz + retention-days: 5 build_bdist: @@ -37,46 +55,87 @@ jobs: strategy: matrix: # Windows tests fail when built as a binary wheel for some reason - os: [ubuntu-latest, macos-latest] # windows-latest + # 'macos-12' doesn't pass the tests + os: [ubuntu-latest, macos-11] # windows-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 + with: + ref: ${{ inputs.tag }} # setup Python for cibuildwheel - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: '3.x' # for other architectures, see: https://cibuildwheel.readthedocs.io/en/stable/faq/#emulation - name: Build wheels for CPython - uses: pypa/cibuildwheel@v2.1.1 + uses: pypa/cibuildwheel@v2.12.3 env: - CIBW_BUILD: 'cp*' # no PyPI builds - CIBW_SKIP: 'cp310-*' # 3.10 builds are broken - CIBW_ARCHS: 'auto64' # only 64-bit - CIBW_TEST_REQUIRES: pytest pytest-xdist - CIBW_TEST_COMMAND: pytest -n auto {project}/test/ - - - uses: actions/upload-artifact@v2 + # cibuildwheel automatically reads 'python_requires' from 'setup.py' + CIBW_BUILD: 'cp*' # no PyPy builds + CIBW_ARCHS_LINUX: "x86_64" # only Intel 64-bit + CIBW_ARCHS_MACOS: "x86_64" # only Intel 64-bit + CIBW_TEST_REQUIRES: pytest pytest-xdist filelock setuptools>=60.2 + # only copy test dir to current working dir + CIBW_TEST_COMMAND: cp -R {project}/test ./ && pytest --basetemp=.tmpdir --ignore=test/hpy_devel -n auto ./test/ + + - uses: actions/upload-artifact@v3 with: path: ./wheelhouse/*.whl - + retention-days: 5 upload_pypi: - name: Publish packages to PyPI - # only publish packages once everything is successful - needs: [build_bdist, build_sdist] + name: Publish packages to (Test) PyPI + needs: [build_sdist, build_bdist] + runs-on: ubuntu-latest + # don't do this action on forks by default + if: github.event_name == 'push' && github.repository == 'hpyproject/hpy' + steps: + - uses: actions/download-artifact@v3 + with: + name: artifact + path: dist + + - name: Upload to Test PyPI + uses: pypa/gh-action-pypi-publish@v1.8.5 + if: ${{ !inputs.official }} + with: + verbose: true + user: __token__ + password: ${{ secrets.TEST_PYPI_API_TOKEN }} + repository-url: https://test.pypi.org/legacy/ + + - name: Upload to PyPI + uses: pypa/gh-action-pypi-publish@v1.8.5 + if: ${{ inputs.official }} + with: + verbose: true + user: __token__ + password: ${{ secrets.PYPI_API_TOKEN }} + + create_gh_release: + needs: [upload_pypi] runs-on: ubuntu-latest + # don't do this action on forks by default + if: github.event_name == 'push' && github.repository == 'hpyproject/hpy' steps: - - uses: actions/download-artifact@v2 - with: - name: artifact - path: dist - - - uses: pypa/gh-action-pypi-publish@v1.4.2 - with: - user: __token__ - password: ${{ secrets.pypi_api_token }} - # uncomment the following to upload to test.pypi.org - # repository_url: https://test.pypi.org/legacy/ + - uses: actions/download-artifact@v3 + with: + name: artifact + path: dist + + - name: Checkout + uses: actions/checkout@v3 + with: + ref: ${{ inputs.tag }} + + - name: Release + uses: softprops/action-gh-release@v1 + with: + files: dist/* + # consider tags in form '*.*.*rc*' to indicate a pre-release + prerelease: ${{ contains(github.ref, 'rc') }} + draft: ${{ !inputs.official }} + tag_name: ${{ inputs.tag }} diff --git a/docs/api-reference/hpy-dict.rst b/docs/api-reference/hpy-dict.rst new file mode 100644 index 000000000..fb9400f4c --- /dev/null +++ b/docs/api-reference/hpy-dict.rst @@ -0,0 +1,5 @@ +HPy Dict +======== + +.. autocmodule:: autogen/public_api.h + :members: HPyDict_Check, HPyDict_New, HPyDict_Keys, HPyDict_Copy diff --git a/docs/api-reference/index.rst b/docs/api-reference/index.rst index 1d7727e20..10aa9babf 100644 --- a/docs/api-reference/index.rst +++ b/docs/api-reference/index.rst @@ -27,6 +27,7 @@ between the modes. hpy-call hpy-field hpy-global + hpy-dict hpy-gil hpy-err builder diff --git a/docs/changelog.rst b/docs/changelog.rst index bd02135f7..e2915b98d 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,6 +1,159 @@ Changelog ========= +Version 0.9 (April 25th, 2023) +------------------------------ + +This release adds numerous major features and indicates the end of HPy's *alhpa* +phase. We've migrated several key packages to HPy (for a list, see our website +https://hpyproject.org) and we are now confident that HPy is mature enough for +being used as serious extension API. We also plan that the next major release +will be ``1.0``. + +Major new features +~~~~~~~~~~~~~~~~~~ + +Support subclasses of built-in types + It is now possible to create pure HPy types that inherit from built-in types + like ``type`` or ``float``. This was already possible before but in a very + limited way, i.e., by setting :c:member:`HPyType_Spec.basicsize` to ``0``. In + this case, the type implicitly inherited the basic size of the supertype but + that also means that you cannot have a custom C struct. It is now possible + inherit from a built-in type **AND** have a custom C struct. For further + reference, see :c:member:`HPyType_Spec.builtin_shape` and + :c:enum:`HPyType_BuiltinShape`. + +Support for metaclasses + HPy now supports creating types with metaclasses. This can be done by passing + type specification parameter with kind + :c:enumerator:`HPyType_SpecParam_Metaclass` when calling + :c:func:`HPyType_FromSpec`. + +:term:`HPy Hybrid ABI` + In addition to :term:`CPython ABI` and :term:`HPy Universal ABI`, we now + introduced the Hybrid ABI. The major difference is that whenever you use a + legacy API like :c:func:`HPy_AsPyObject` or :c:func:`HPy_FromPyObject`, the + prdouced binary will then be specific to one interpreter. This was necessary + to ensure that universal binaries are really portable and can be used on any + HPy-capable interpreter. + +:doc:`trace-mode` + Similar to the :doc:`debug-mode`, HPy now provides the Trace Mode that can be + enabled at runtime and helps analyzing API usage and identifying performance + issues. + +:ref:`porting-guide:multi-phase module initialization` + HPy now support multi-phase module initialization which is an important + feature in particular needed for two important use cases: (1) module state + support (which is planned to be introduced in the next major release), and (2) + subinterpreters. We decided to drop support for single-phase module + initialization since this makes the API cleaner and easier to use. + +HPy :ref:`porting-guide:calling protocol` + This was a big missing piece and is now eventually available. It enables slot + ``HPy_tp_call``, which can now be used in the HPy type specification. We + decided to use a calling convention similar to CPython's vectorcall calling + convention. This is: the arguments are passed in a C array and the keyword + argument names are provided as a Python tuple. Before this release, the only + way to create a callable type was to set the special method ``__call__``. + However, this has several disadvantages. In particlar, poor performance on + CPython (and maybe other implementations) and it was not possible to have + specialized call function implementations per object (see + :c:func:`HPy_SetCallFunction`) + +Added APIs +~~~~~~~~~~ + +Deleting attributes and items + :c:func:`HPy_DelAttr`, :c:func:`HPy_DelAttr_s`, :c:func:`HPy_DelItem`, :c:func:`HPy_DelItem_i`, :c:func:`HPy_DelItem_s` + +Capsule API + :c:func:`HPyCapsule_New`, :c:func:`HPyCapsule_IsValid`, :c:func:`HPyCapsule_Get`, :c:func:`HPyCapsule_Set` + +Eval API + :c:func:`HPy_Compile_s` and :c:func:`HPy_EvalCode` + +Formatting helpers + :c:func:`HPyUnicode_FromFormat` and :c:func:`HPyErr_Format` + +Contextvar API + :c:func:`HPyContextVar_New`, :c:func:`HPyContextVar_Get`, :c:func:`HPyContextVar_Set` + +Unicode API + :c:func:`HPyUnicode_FromEncodedObject` and :c:func:`HPyUnicode_Substring` + +Dict API + :c:func:`HPyDict_Keys` and :c:func:`HPyDict_Copy` + +Type API + :c:func:`HPyType_GetName` and :c:func:`HPyType_IsSubtype` + +Slice API + :c:func:`HPySlice_Unpack` and :c:func:`HPySlice_AdjustIndices` + +Structseq API + :c:func:`HPyStructSequence_NewType`, :c:func:`HPyStructSequence_New` + +Call API + :c:func:`HPy_Call`, :c:func:`HPy_CallMethod`, :c:func:`HPy_CallMethodTupleDict`, :c:func:`HPy_CallMethodTupleDict_s` + +HPy call protocol + :c:func:`HPy_SetCallFunction` + +Debug mode +~~~~~~~~~~ + +* Detect closing and returning (without dup) of context handles +* Detect invalid usage of stored ``HPyContext *`` pointer +* Detect invalid usage of tuple and list builders +* Added Windows support for checking invalid use of raw data pointers (e.g + ``HPyUnicode_AsUTF8AndSize``) after handle was closed. +* Added support for backtrace on MacOS + +Documentation +~~~~~~~~~~~~~ + +* Added incremental :doc:`porting-example/index` +* Added :doc:`quickstart` guide +* Extended :doc:`api-reference/index` +* Added :doc:`api-reference/function-index` +* Added possiblity to generate examples from tests with argument ``--dump-dir`` + (see :ref:`api:hpy unit tests`) +* Added initial :doc:`contributing/index` docs + +Incompatible changes to version 0.0.4 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +* Simplified ``HPyDef_*`` macros +* Changed macro :c:macro:`HPy_MODINIT` because of multi-phase module init + support. +* Replace environment variable ``HPY_DEBUG`` by ``HPY`` (see :doc:`debug-mode` + or :doc:`trace-mode`). +* Changed signature of ``HPyFunc_VARARGS`` and ``HPyFunc_ KEYWORDS`` to align + with HPy's call protocol calling convention. + +Supported Python versions +~~~~~~~~~~~~~~~~~~~~~~~~~ + +* Added Python 3.11 support +* Preliminary Python 3.12 support +* Dropped Python 3.6 support (since EOL) +* Dropped Python 3.7 support (since EOL by June 2023) + +Misc +~~~~ + +* Ensure deterministic auto-generation +* Ensure ABI backwards compatibility + + * Explicitly define slot within HPyContext of function pointers and handles + * Compile HPy ABI version into binary and verify at load time +* Added proper support for object members ``HPyMember_OBJECT`` +* Changed :c:func:`HPyBytes_AsString` and :c:func:`HPyBytes_AS_STRING` to return ``const char *`` +* Use fixed-width integers in context functions + + + Version 0.0.4 (May 25th, 2022) ------------------------------ diff --git a/docs/conf.py b/docs/conf.py index 44c506a57..45a907761 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -26,7 +26,7 @@ author = "HPy Collective" # The full version, including alpha/beta/rc tags -release = "0.0.4" +release = "0.9" # -- General configuration --------------------------------------------------- diff --git a/hpy/devel/__init__.py b/hpy/devel/__init__.py index b2e6b62cb..e74141612 100644 --- a/hpy/devel/__init__.py +++ b/hpy/devel/__init__.py @@ -177,6 +177,12 @@ def handle_hpy_ext_modules(dist, attr, hpy_ext_modules): """ assert attr == 'hpy_ext_modules' + # It can happen that this hook will be called multiple times depending on + # which command was used. So, skip patching if we already patched the + # distribution. + if getattr(dist, 'hpydevel', None): + return + # add a global option --hpy-abi to setup.py dist.__class__.hpy_abi = DEFAULT_HPY_ABI dist.__class__.hpy_use_static_libs = False diff --git a/hpy/tools/autogen/public_api.h b/hpy/tools/autogen/public_api.h index a25062eda..52e3b58c7 100644 --- a/hpy/tools/autogen/public_api.h +++ b/hpy/tools/autogen/public_api.h @@ -671,8 +671,32 @@ HPy_ID(200) int HPyList_Append(HPyContext *ctx, HPy h_list, HPy h_item); /* dictobject.h */ + +/** + * Tests if an object is an instance of a Python dict. + * + * :param ctx: + * The execution context. + * :param h: + * A handle to an arbitrary object (must not be ``HPy_NULL``). + * + * :returns: + * Non-zero if object ``h`` is an instance of type ``dict`` or an instance + * of a subtype of ``dict``, and ``0`` otherwise. + */ HPy_ID(201) int HPyDict_Check(HPyContext *ctx, HPy h); + +/** + * Creates a new empty Python dictionary. + * + * :param ctx: + * The execution context. + * + * :returns: + * A handle to the new and empty Python dictionary or ``HPy_NULL`` in case + * of an error. + */ HPy_ID(202) HPy HPyDict_New(HPyContext *ctx); diff --git a/setup.py b/setup.py index 32a3ac69d..8dd6962dd 100644 --- a/setup.py +++ b/setup.py @@ -161,14 +161,15 @@ def get_library_names(self): def build_libraries(self, libraries): # we just inherit the 'inplace' option from 'build_ext' - inplace = self.get_finalized_command('build_ext').inplace + build_ext = self.get_finalized_command('build_ext') + inplace = build_ext.inplace if inplace: # the inplace option requires to find the package directory # using the build_py command for that build_py = self.get_finalized_command('build_py') lib_dir = os.path.abspath(build_py.get_package_dir('hpy.devel')) else: - lib_dir = self.build_clib + lib_dir = os.path.join(build_ext.build_lib, 'hpy', 'devel') import pathlib for lib in libraries: