diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 987b0005..030e5abc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -208,18 +208,18 @@ jobs: working-directory: ${{runner.workspace}}/build run: make test - pybind11: + pybind11-pip: needs: [build-ubuntu, build-mac] strategy: fail-fast: false matrix: platform: [macos-latest, ubuntu-latest] #windows-latest, - # python-version: [3.5, 3.6, 3.7, 3.8] - python-version: [3.6] + python-version: [3.6, 3.7, 3.8, 3.9] runs-on: ${{ matrix.platform }} steps: - name: Checkout uses: actions/checkout@v2 + - run: git fetch --prune --unshallow - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v2 with: @@ -235,12 +235,46 @@ jobs: - name: Setup run: | python -m pip install --upgrade pip - pip install pytest "pybind11[global]" - pip install -r requirements.txt + python -m pip install build - name: Build - run: pip install . + run: python -m pip install -v .[testing] - name: Test - run: pytest + run: python -m pytest + + pybind11-cmake: + needs: [build-ubuntu, build-mac] + # strategy: + # fail-fast: false + # matrix: + # python-version: [3.9] + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + - run: git fetch --prune --unshallow + # - name: Set up Python ${{ matrix.python-version }} + # uses: actions/setup-python@v2 + # with: + # python-version: ${{ matrix.python-version }} + - name: Setup apt + run: | + sudo apt update + sudo apt install -y libeigen3-dev pybind11-dev python3-pytest python3-numpy + mkdir ${{runner.workspace}}/build + - name: Configure + working-directory: ${{runner.workspace}}/build + run: cmake $GITHUB_WORKSPACE -DBUILD_EXAMPLES=OFF -DBUILD_TESTING=ON -DBUILD_PYTHON_BINDINGS=ON -DCMAKE_BUILD_TYPE=Release + - name: Build + working-directory: ${{runner.workspace}}/build + run: make -j2 + - name: Test + working-directory: ${{runner.workspace}}/build + run: make test + - name: Install + working-directory: ${{runner.workspace}}/build + run: sudo make install + - name: Test Import + run: python3 -c 'import manifpy' # arm64: # needs: [build-ubuntu] diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..2aa64154 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,72 @@ +name: release +on: + push: + tags: + - '*' + pull_request: + branches: + - devel # master only when ready + - master + workflow_dispatch: + +jobs: + + build-sdist: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + - run: git fetch --prune --unshallow + + - name: Install Python + uses: actions/setup-python@v2 + with: + python-version: 3.6 + + - name: Setup apt + run: | + sudo apt update + sudo apt install -y libeigen3-dev + + - name: Setup + run: | + python3 -m pip install --upgrade pip + pip3 install build + + - name: Build sdist + run: python3 -m build --sdist -o dist/ + + - name: Build wheel + run: python3 -m build --wheel -o dist/ + + - name: Upload artifacts + uses: actions/upload-artifact@v2 + with: + name: dist + path: dist/* + # path: | + # path/*.whl + # path/*.tar.gz + + upload_pypi: + needs: build-sdist + runs-on: ubuntu-latest + steps: + + - uses: actions/download-artifact@v2 + with: + name: dist + path: dist + + - name: Inspect dist folder + run: ls -lah dist/ + + # @todo: see https://github.com/diegoferigo/manif/pull/1#discussion_r668531581 + # - uses: pypa/gh-action-pypi-publish@master + # if: | + # github.repository == 'artivis/manif' && + # ((github.event_name == 'release' && github.event.action == 'published') || + # (github.event_name == 'push' && github.ref == 'refs/heads/main')) + # with: + # user: __token__ + # password: ${{ secrets.PYPI_TOKEN }} diff --git a/.gitignore b/.gitignore index 15efcd57..feb5f881 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,4 @@ docs/m.css *.so *__pycache__ .pytest_cache +dist diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 18eeb4ba..e85f37e3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -42,8 +42,7 @@ Let's install all dependencies for development and testing, ```terminal apt install libeigen3-dev -python3 -m pip install "pybind11[global]" pytest -python3 -m pip install -r requirements +python3 -m pip install "pybind11[global]" pytest numpy ``` We can now build **manif**, its Python wrappers and all tests, diff --git a/docs/pages/python/Quick-start.md b/docs/pages/python/Quick-start.md index d51d11e0..480a36a1 100644 --- a/docs/pages/python/Quick-start.md +++ b/docs/pages/python/Quick-start.md @@ -1,14 +1,29 @@ # Quick start - [Quick start](#quick-start) - - [Getting Pybind11](#getting-pybind11) - - [Installation](#installation) - - [Dependencies](#dependencies) + - [Installing manifpy](#installing-manifpy) + - [From conda](#from-conda) - [From source](#from-source) + - [Getting Pybind11](#getting-pybind11) + - [Getting the dependencies](#getting-the-dependencies) + - [Building](#building) + - [Testing](#testing) - [Use manifpy in your project](#use-manifpy-in-your-project) - [Tutorials and application demos](#tutorials-and-application-demos) -## Getting Pybind11 +## Installing manifpy + +### From conda + +`manifpy` can be installed from the [conda-forge][conda-manifpy], + +```bash +conda install -c conda-forge manifpy +``` + +### From source + +#### Getting Pybind11 The Python wrappers are generated using [pybind11][pybind11-rtd]. So first we need to install it, but we want it available directly in our environment root so that `CMake` can find it. @@ -30,9 +45,9 @@ cmake .. make install ``` -## Installation + -### Dependencies +#### Getting the dependencies - Eigen 3 : - Linux ( Ubuntu and similar ) @@ -49,13 +64,7 @@ make install - [lt::optional][optional-repo] : included in the `external` folder -Python bindings also depends on `numpy`. - -```bash -python3 -m pip install -r requirements -``` - -### From source +#### Building To generate `manif` Python bindings run, @@ -65,6 +74,20 @@ cd manif python3 -m pip install . ``` +#### Testing + +To run the tests you will also need `numpy`, + +```bash +python3 -m pip install numpy +``` + +To run the tests, simply hits: + +```bash +python3 -m pytest +``` + ## Use manifpy in your project ```python @@ -101,3 +124,4 @@ python3 se2_localization.py [pybind11-rtd]: https://pybind11.readthedocs.io/en/stable/index.html [optional-repo]: https://github.com/TartanLlama/optional +[conda-manifpy]: https://anaconda.org/conda-forge/manifpy diff --git a/docs/pages/python/index.rst b/docs/pages/python/index.rst index f1341957..96af4f4c 100644 --- a/docs/pages/python/index.rst +++ b/docs/pages/python/index.rst @@ -8,30 +8,6 @@ Quick start .. contents:: Table of Contents :depth: 3 - -Getting Pybind11 ----------------- - -The Python wrappers are generated using `pybind11 `_. So first we need to install it, -but we want it available directly in our environment root so that ``CMake`` can find it. -To do so we can use, - -.. code-block:: bash - - python3 -m pip install "pybind11[global]" - -Note that this is not recommended when using one's system Python, -as it will add files to ``/usr/local/include/pybind11`` and ``/usr/local/share/cmake/pybind11``. - -Another way is to use ``CMake`` to install it, - -.. code-block:: bash - - git clone https://github.com/pybind/pybind11.git - cd pybind11 && mkdir build && cd build - cmake .. - make install - Installation ------------ @@ -60,11 +36,6 @@ Dependencies * `lt::optional `_ : included in the ``external`` folder -Python bindings also depends on ``numpy``. - -.. code-block:: bash - - python3 -m pip install -r requirements From source ^^^^^^^^^^^ diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..0b3ea7d5 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,14 @@ +[build-system] +requires = [ + "wheel", + "setuptools>=45", + "setuptools_scm[toml]>=6.0", + "ninja", + "cmake>=3.18.2", + "cmake-build-extension", + "pybind11", +] +build-backend = "setuptools.build_meta" + +[tool.setuptools_scm] +local_scheme = "dirty-tag" diff --git a/pytest.ini b/pytest.ini deleted file mode 100644 index b5473fe8..00000000 --- a/pytest.ini +++ /dev/null @@ -1,2 +0,0 @@ -[pytest] -testpaths = test/python \ No newline at end of file diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index 6138dc84..7a3ee826 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -1,5 +1,5 @@ set(PYBIND11_CPP_STANDARD -std=c++11) -pybind11_add_module(manifpy +pybind11_add_module(manifpy MODULE bindings_rn.cpp bindings_so2.cpp bindings_so3.cpp @@ -17,3 +17,51 @@ target_compile_definitions(manifpy PRIVATE EIGEN_DEFAULT_TO_ROW_MAJOR) set_property(TARGET manifpy PROPERTY CXX_STANDARD 11) set_property(TARGET manifpy PROPERTY CXX_STANDARD_REQUIRED ON) set_property(TARGET manifpy PROPERTY CXX_EXTENSIONS OFF) + +if (CALL_FROM_SETUP_PY) + # cmake-build-extension sets the full absolute path as CMAKE_INSTALL_PREFIX. + set(MANIFPY_INSTDIR "${CMAKE_INSTALL_PREFIX}") +else() + # 'distutils.sysconfig.get_python_lib' returns the absolute path of Python + # by default a global location managed by the distro e.g. /usr/lib/python. + # + # pybind11 and FindPython3 set respectively PYTHON_SITE_PACKAGES/Python3_SITELIB + # from 'distutils.sysconfig.get_python_lib' + # + # Those are especially annoying on Ubuntu since it has + # some hardcoded paths in python3.x/site.py + # + # `sysconfig.get_path` may return paths that does not even exists. + # + # So below we retrieve the first site-package path from 'site.getsitepackages()'. + + execute_process( + COMMAND + ${PYTHON_EXECUTABLE} -c "import site; print(site.getsitepackages()[0])" + OUTPUT_VARIABLE _PYTHON_SITE_PACKAGE OUTPUT_STRIP_TRAILING_WHITESPACE + ) + set(MANIFPY_INSTDIR "${_PYTHON_SITE_PACKAGE}/manifpy") +endif() + +message(STATUS "Installing manifpy in ${MANIFPY_INSTDIR}") + +# Setup installation path +install(TARGETS manifpy COMPONENT python DESTINATION "${MANIFPY_INSTDIR}") + +# Create the Python package in the build tree for testing purposes +set(MANIFPY_BUILDDIR "${CMAKE_BINARY_DIR}/manifpy") +set_target_properties( + manifpy PROPERTIES + OUTPUT_NAME _bindings + LIBRARY_OUTPUT_DIRECTORY "${MANIFPY_BUILDDIR}") + +# Create the __init__.py file +file( + GENERATE + OUTPUT "${MANIFPY_BUILDDIR}/__init__.py" + CONTENT "from manifpy._bindings import *\n") + +# Install the __init__.py file +install( + FILES "${MANIFPY_BUILDDIR}/__init__.py" + DESTINATION ${MANIFPY_INSTDIR}) diff --git a/python/bindings_manif.cpp b/python/bindings_manif.cpp index 96757b38..d1b16d56 100644 --- a/python/bindings_manif.cpp +++ b/python/bindings_manif.cpp @@ -10,7 +10,7 @@ void wrap_SE3(pybind11::module &m); void wrap_SE_2_3(pybind11::module &m); -PYBIND11_MODULE(manifpy, m) { +PYBIND11_MODULE(_bindings, m) { m.doc() = "Python bindings for the manif library, " "a small library for Lie theory."; diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 296d6545..00000000 --- a/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -numpy \ No newline at end of file diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 00000000..efa225bb --- /dev/null +++ b/setup.cfg @@ -0,0 +1,46 @@ +[metadata] +name = manifpy +description = A small library for Lie theory. +long_description = file: README.md; charset=UTF-8 +long_description_content_type = text/markdown +author = Jeremie Deray +author_email = deray.jeremie@gmail.com +license = MIT +platforms = any +url = https://github.com/artivis/manif +project_urls = + Source = https://github.com/artivis/manif + Tracker = https://github.com/artivis/manif/issues +keywords = geometry lie-theory state-estimation slam robotics computer-vision +classifiers = + Development Status :: 5 - Production/Stable + Operating System :: OS Independent + Operating System :: POSIX :: Linux + Operating System :: MacOS + Operating System :: Microsoft :: Windows + Framework :: Robot Framework + Intended Audience :: Science/Research + Intended Audience :: Developers + Intended Audience :: Education + Programming Language :: C++ + Programming Language :: Python :: 3 :: Only + Programming Language :: Python :: 3 + Programming Language :: Python :: 3.6 + Programming Language :: Python :: 3.7 + Programming Language :: Python :: 3.8 + Programming Language :: Python :: 3.9 + License :: OSI Approved :: MIT License + +[options] +zip_safe = False +python_requires = >=3.6 + +[options.extras_require] +testing = + pytest + numpy +all = + %(testing)s + +[tool:pytest] +testpaths = test/python diff --git a/setup.py b/setup.py index 456a5cb4..23d2bd70 100644 --- a/setup.py +++ b/setup.py @@ -1,107 +1,20 @@ -import os -import platform -import subprocess import sys -import xml.etree.ElementTree as ET - -import setuptools -from setuptools import Extension, setup -from setuptools.command.build_ext import build_ext - - -""" -Modified from https://www.benjack.io/2017/06/12/python-cpp-tests.html -""" - - -class CMakeExtension(Extension): - - def __init__(self, name, sourcedir=''): - Extension.__init__(self, name, sources=[]) - self.sourcedir = os.path.abspath(sourcedir) - - -class CMakeBuild(build_ext): - - def run(self): - try: - # out = - subprocess.check_output(['cmake', '--version']) - except OSError: - raise RuntimeError( - 'CMake must be installed to build the following extensions: ' + - ', '.join(e.name for e in self.extensions)) - - # if platform.system() == "Windows": - # cmake_version = LooseVersion(re.search(r'version\s*([\d.]+)', - # out.decode()).group(1)) - # if cmake_version < '3.1.0': - # raise RuntimeError("CMake >= 3.1.0 is required on Windows") - - for ext in self.extensions: - self.build_extension(ext) - - def build_extension(self, ext): - extdir = os.path.abspath( - os.path.dirname(self.get_ext_fullpath(ext.name))) - - cmake_args = ['-DCMAKE_LIBRARY_OUTPUT_DIRECTORY=' + extdir, - '-DPYTHON_EXECUTABLE=' + sys.executable] - - cfg = 'Debug' if self.debug else 'Release' - build_args = ['--config', cfg] - - if platform.system() == 'Windows': - cmake_args += ['-DCMAKE_LIBRARY_OUTPUT_DIRECTORY_{}={}'.format( - cfg.upper(), - extdir)] - if sys.maxsize > 2**32: - cmake_args += ['-A', 'x64'] - build_args += ['--', '/m'] - else: - cmake_args += ['-DCMAKE_BUILD_TYPE=' + cfg] - cmake_args += ['-DBUILD_PYTHON_BINDINGS=ON'] - build_args += ['--', '-j6'] - - env = os.environ.copy() - env['CXXFLAGS'] = '{} -DVERSION_INFO=\\"{}\\"'.format( - env.get('CXXFLAGS', ''), - self.distribution.get_version()) - if not os.path.exists(self.build_temp): - os.makedirs(self.build_temp) - subprocess.check_call(['cmake', ext.sourcedir] + cmake_args, - cwd=self.build_temp, env=env) - subprocess.check_call(['cmake', '--build', '.'] + build_args, - cwd=self.build_temp) - - -def get_package_xml_version(): - tree = ET.parse('package.xml') - return tree.find('version').text - - -with open('README.md', 'r') as f: - long_description = f.read() +from cmake_build_extension import BuildExtension, CMakeExtension +from setuptools import setup setup( - name='manifpy', - version=get_package_xml_version(), - author='Jeremie Deray', - author_email='deray.jeremie@gmail.com', - description='A small library for Lie theory.', - long_description=long_description, - long_description_content_type='text/markdown', - url='https://github.com/artivis/manif', - license='MIT', - packages=setuptools.find_packages(), - classifiers=[ - 'Programming Language :: Python :: 3', - 'Operating System :: POSIX :: Linux' + ext_modules=[ + CMakeExtension( + name="CMakeProject", + install_prefix="manifpy", + cmake_depends_on=["pybind11"], + disable_editable=True, + cmake_configure_options=[ + "-DCALL_FROM_SETUP_PY:BOOL=ON", + "-DBUILD_PYTHON_BINDINGS:BOOL=ON", + ], + ) ], - ext_modules=[CMakeExtension('manifpy')], - python_requires='>=3.6', - cmdclass=dict(build_ext=CMakeBuild), - zip_safe=False, - install_requires=['numpy'] + cmdclass=dict(build_ext=BuildExtension), ) diff --git a/test/python/CMakeLists.txt b/test/python/CMakeLists.txt index c74efbfb..0e3d9eb0 100644 --- a/test/python/CMakeLists.txt +++ b/test/python/CMakeLists.txt @@ -2,11 +2,11 @@ function(manif_add_pytest target) add_test( NAME ${target} - COMMAND ${PYTHON_EXECUTABLE} -m pytest ${target}.py + COMMAND pytest-3 ${target}.py WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} ) set_tests_properties(${target} - PROPERTIES ENVIRONMENT "PYTHONPATH=${CMAKE_BINARY_DIR}/python:$ENV{PYTHONPATH}" + PROPERTIES ENVIRONMENT "PYTHONPATH=${CMAKE_BINARY_DIR}:$ENV{PYTHONPATH}" ) endfunction()