From b8b699012216260a141e276c30a3286eb9f88a45 Mon Sep 17 00:00:00 2001 From: Jesse Myrberg Date: Tue, 15 Nov 2022 00:08:06 +0200 Subject: [PATCH] Fix installation (#36) * Fix setuptools<60.0 * Add setup.py for editable installs * Quiet NPY_NO_DEPRECATED_API * Add wheel building through cibuildwheel --- .flake8 | 2 +- .github/workflows/pr.yml | 10 +- .github/workflows/push.yml | 11 +- .github/workflows/release.yml | 24 ----- .github/workflows/wheels.yml | 102 ++++++++++++++++++ README.md | 12 +-- mknapsack/__init__.py | 15 +-- {tests => mknapsack/tests}/__init__.py | 0 .../tests}/test__bin_packing.py | 2 +- {tests => mknapsack/tests}/test__bounded.py | 2 +- .../tests}/test__bounded_change_making.py | 2 +- .../tests}/test__change_making.py | 2 +- .../tests}/test__generalized_assignment.py | 2 +- {tests => mknapsack/tests}/test__multiple.py | 2 +- {tests => mknapsack/tests}/test__single.py | 2 +- .../tests}/test__subset_sum.py | 2 +- {tests => mknapsack/tests}/test__unbounded.py | 2 +- {tests => mknapsack/tests}/utils.py | 0 pyproject.toml | 19 ++-- pytest.ini | 2 +- setup.cfg | 2 + setup.py | 1 + 22 files changed, 146 insertions(+), 72 deletions(-) delete mode 100644 .github/workflows/release.yml create mode 100644 .github/workflows/wheels.yml rename {tests => mknapsack/tests}/__init__.py (100%) rename {tests => mknapsack/tests}/test__bin_packing.py (98%) rename {tests => mknapsack/tests}/test__bounded.py (99%) rename {tests => mknapsack/tests}/test__bounded_change_making.py (99%) rename {tests => mknapsack/tests}/test__change_making.py (99%) rename {tests => mknapsack/tests}/test__generalized_assignment.py (99%) rename {tests => mknapsack/tests}/test__multiple.py (99%) rename {tests => mknapsack/tests}/test__single.py (99%) rename {tests => mknapsack/tests}/test__subset_sum.py (99%) rename {tests => mknapsack/tests}/test__unbounded.py (99%) rename {tests => mknapsack/tests}/utils.py (100%) create mode 100644 setup.cfg diff --git a/.flake8 b/.flake8 index 91bbb17..c50df08 100644 --- a/.flake8 +++ b/.flake8 @@ -2,4 +2,4 @@ max-line-length = 79 exclude = .svn,CVS,.bzr,.hg,.git,__pycache__,.eggs,*.egg,*/_vendor/*,node_modules per-file-ignores = - tests/test__generalized_assignment.py: E127, E128, E131, E201 \ No newline at end of file + mknapsack/tests/test__generalized_assignment.py: E127, E128, E131, E201 \ No newline at end of file diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index b04f8dd..eb9c0bb 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -14,8 +14,7 @@ jobs: - uses: actions/setup-python@v3 - name: Install dependencies run: | - python -m pip install --upgrade pip - pip install flake8 + python -m pip install --upgrade pip flake8 - name: Lint with flake8 run: | flake8 . @@ -46,10 +45,13 @@ jobs: with: python-version: ${{ matrix.python-version }} - name: Install Python dependencies + env: + # Latest setuptools enables supports pyproject.toml editable install + # ...but fails to build the extensions + SETUPTOOLS_ENABLE_FEATURES: legacy-editable run: | python -m pip install --upgrade pip - pip install -e . - pip install -r requirements-dev.txt + pip install -e .[test] - name: Test with pytest run: | pytest -vv diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index 6d1e951..c14d8f6 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -44,7 +44,7 @@ jobs: pytest -vv draft-release: - name: Draft release and upload distribution to Test PyPi + name: Draft release runs-on: ubuntu-latest permissions: contents: write @@ -57,12 +57,3 @@ jobs: - uses: release-drafter/release-drafter@v5 env: GITHUB_TOKEN: ${{ secrets.RELEASE_DRAFTER_PAT }} - - name: Build and upload distribution to Test PyPi - env: - TWINE_USERNAME: __token__ - TWINE_PASSWORD: ${{ secrets.TEST_PYPI_API_TOKEN }} - run: | - python -m pip install --upgrade pip - pip install build twine - python -m build --sdist --outdir dist/ . - twine upload -r testpypi dist/* diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index f820751..0000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,24 +0,0 @@ -name: Upload distribution to PyPi - -on: - release: - types: [published] - -jobs: - build-and-release: - name: Build and release - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - uses: actions/setup-python@v3 - - name: Build and upload distribution to PyPi - env: - TWINE_USERNAME: __token__ - TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }} - run: | - python -m pip install --upgrade pip - pip install build twine - python -m build --sdist --outdir dist/ . - twine upload dist/* diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml new file mode 100644 index 0000000..dd30625 --- /dev/null +++ b/.github/workflows/wheels.yml @@ -0,0 +1,102 @@ +name: Build + +on: + push: + branches: [master] + release: + types: [published] + +env: + IS_PUSH: ${{ github.event_name == 'push' }} + +jobs: + build-sdist: + name: Build source distribution + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + - name: Install Fortran compiler on Linux + if: contains(matrix.os, 'ubuntu') + run: | + sudo apt-get install -y gfortran + - name: Install dependencies + run: | + python -m pip install --upgrade pip + python -m pip install build + - name: Build source distribution + run: | + python -m build --sdist --outdir dist/ . + - uses: actions/upload-artifact@v3 + with: + path: ./dist/*.tar.gz + + build-wheels: + name: Build wheels on ${{ matrix.buildplat[0] }}-${{ matrix.buildplat[1] }} + runs-on: ${{ matrix.buildplat[0] }} + strategy: + fail-fast: false + matrix: + buildplat: + - [ubuntu-latest, manylinux_x86_64] + - [macos-latest, macosx_x86_64] + - [windows-latest, win_amd64] + # TODO: Add when GHA supports + # - [macos-latest, macosx_arm64] + # - [windows-latest, win32] + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + - name: Install Fortran compiler on MacOS + if: contains(matrix.buildplat[0], 'macos') + run: | + brew unlink gcc && brew link gcc + - name: Build wheels + uses: pypa/cibuildwheel@v2.11.2 + env: + CIBW_BUILD: "*${{ matrix.buildplat[1] }}" + - uses: actions/upload-artifact@v3 + with: + path: ./wheelhouse/*.whl + + upload-test-pypi: + name: Upload to Test PyPi + if: github.event_name == 'push' + runs-on: ubuntu-latest + needs: [build-sdist, build-wheels] + steps: + - uses: actions/download-artifact@v3 + with: + name: artifact + path: dist + - uses: actions/setup-python@v3 + - name: Upload artifacts to Test PyPi + env: + TWINE_USERNAME: __token__ + TWINE_PASSWORD: ${{ secrets.TEST_PYPI_API_TOKEN }} + run: | + python -m pip install --upgrade pip + python -m pip install twine + twine upload -r testpypi dist/* + + upload-pypi: + name: Upload to PyPi + if: github.event_name == 'release' + runs-on: ubuntu-latest + needs: [build-sdist, build-wheels] + steps: + - uses: actions/download-artifact@v3 + with: + name: artifact + path: dist + - uses: actions/setup-python@v3 + - name: Upload artifacts to PyPi + env: + TWINE_USERNAME: __token__ + TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }} + run: | + python -m pip install --upgrade pip + python -m pip install twine + twine upload dist/* diff --git a/README.md b/README.md index 2c382a4..ba80d3a 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # mknapsack [![CICD](https://github.com/jmyrberg/mknapsack/actions/workflows/push.yml/badge.svg)](https://github.com/jmyrberg/mknapsack/actions/workflows/push.yml) +[![Build](https://github.com/jmyrberg/mknapsack/actions/workflows/wheels.yml/badge.svg)](https://github.com/jmyrberg/mknapsack/actions/workflows/wheels.yml) [![Documentation](https://readthedocs.org/projects/mknapsack/badge/?version=latest)](https://mknapsack.readthedocs.io/en/latest/?badge=latest) ![mknapsack cover](https://github.com/jmyrberg/mknapsack/blob/master/docs/cover.png?raw=true) @@ -22,16 +23,7 @@ Documentation is available [here](https://mknapsack.readthedocs.io). ## Installation -1. Install Fortran compiler, if you don't already have it - * MacOS / Linux: - `brew install gcc` - * Linux / Windows Subsystem for Linux: - `sudo apt-get install gfortran` - * Windows (experimental): - * `conda install -c conda-forge m2w64-toolchain_win-64`, or - * [Install MSYS2](https://www.msys2.org) and `pacman -S --needed base-devel mingw-w64-x86_64-toolchain` - -2. `pip install mknapsack` +`pip install mknapsack` ## Example usage diff --git a/mknapsack/__init__.py b/mknapsack/__init__.py index 1228b8d..bef17cf 100644 --- a/mknapsack/__init__.py +++ b/mknapsack/__init__.py @@ -17,15 +17,16 @@ ] import os -import sys - # .libs -folder must be added to dll for Windows and Python >=3.8 -# https://github.com/numpy/numpy/issues/14923 -extra_dll_dir = os.path.join(os.path.dirname(__file__), '.libs') -if sys.platform == 'win32' and os.path.isdir(extra_dll_dir): - os.add_dll_directory(extra_dll_dir) - +# https://github.com/isuruf/scipy-feedstock/blob/c36d45b104fc7310f1f4ca45aefd321b2f9b6c59/recipe/_distributor_init.py +basedir = os.path.dirname(__file__) +extra_dll_dir = os.path.abspath(os.path.join(basedir, '.libs')) +if os.name == 'nt' and os.path.isdir(extra_dll_dir): + import glob + from ctypes import WinDLL + for filename in glob.glob(os.path.join(extra_dll_dir, '*dll')): + WinDLL(os.path.abspath(filename)) from mknapsack._exceptions import FortranInputCheckError, NoSolutionError, \ ProblemSizeError # noqa: E402 diff --git a/tests/__init__.py b/mknapsack/tests/__init__.py similarity index 100% rename from tests/__init__.py rename to mknapsack/tests/__init__.py diff --git a/tests/test__bin_packing.py b/mknapsack/tests/test__bin_packing.py similarity index 98% rename from tests/test__bin_packing.py rename to mknapsack/tests/test__bin_packing.py index 207ad42..0b0ddde 100644 --- a/tests/test__bin_packing.py +++ b/mknapsack/tests/test__bin_packing.py @@ -7,7 +7,7 @@ from mknapsack._bin_packing import solve_bin_packing from mknapsack._exceptions import FortranInputCheckError -from tests.utils import get_id +from mknapsack.tests.utils import get_id bin_packing_case_small = { diff --git a/tests/test__bounded.py b/mknapsack/tests/test__bounded.py similarity index 99% rename from tests/test__bounded.py rename to mknapsack/tests/test__bounded.py index 3503376..4a71e41 100644 --- a/tests/test__bounded.py +++ b/mknapsack/tests/test__bounded.py @@ -7,7 +7,7 @@ from mknapsack._bounded import solve_bounded_knapsack from mknapsack._exceptions import FortranInputCheckError -from tests.utils import get_id +from mknapsack.tests.utils import get_id bounded_knapsack_case_small = { diff --git a/tests/test__bounded_change_making.py b/mknapsack/tests/test__bounded_change_making.py similarity index 99% rename from tests/test__bounded_change_making.py rename to mknapsack/tests/test__bounded_change_making.py index b491e94..9a0fca7 100644 --- a/tests/test__bounded_change_making.py +++ b/mknapsack/tests/test__bounded_change_making.py @@ -7,7 +7,7 @@ from mknapsack._bounded_change_making import solve_bounded_change_making from mknapsack._exceptions import FortranInputCheckError, NoSolutionError -from tests.utils import get_id +from mknapsack.tests.utils import get_id bounded_change_making_case_small = { diff --git a/tests/test__change_making.py b/mknapsack/tests/test__change_making.py similarity index 99% rename from tests/test__change_making.py rename to mknapsack/tests/test__change_making.py index e02f7de..1e59533 100644 --- a/tests/test__change_making.py +++ b/mknapsack/tests/test__change_making.py @@ -7,7 +7,7 @@ from mknapsack._change_making import solve_change_making from mknapsack._exceptions import FortranInputCheckError, NoSolutionError -from tests.utils import get_id +from mknapsack.tests.utils import get_id change_making_case_small = { diff --git a/tests/test__generalized_assignment.py b/mknapsack/tests/test__generalized_assignment.py similarity index 99% rename from tests/test__generalized_assignment.py rename to mknapsack/tests/test__generalized_assignment.py index 4d4210e..1295137 100644 --- a/tests/test__generalized_assignment.py +++ b/mknapsack/tests/test__generalized_assignment.py @@ -8,7 +8,7 @@ from mknapsack._exceptions import FortranInputCheckError, NoSolutionError, \ ProblemSizeError -from tests.utils import get_id +from mknapsack.tests.utils import get_id generalized_assignment_case_small = { diff --git a/tests/test__multiple.py b/mknapsack/tests/test__multiple.py similarity index 99% rename from tests/test__multiple.py rename to mknapsack/tests/test__multiple.py index 3522ee8..045b28b 100644 --- a/tests/test__multiple.py +++ b/mknapsack/tests/test__multiple.py @@ -7,7 +7,7 @@ from mknapsack._multiple import solve_multiple_knapsack from mknapsack._exceptions import FortranInputCheckError -from tests.utils import get_id +from mknapsack.tests.utils import get_id multiple_knapsack_case_small = { diff --git a/tests/test__single.py b/mknapsack/tests/test__single.py similarity index 99% rename from tests/test__single.py rename to mknapsack/tests/test__single.py index 0de87b4..9afb09a 100644 --- a/tests/test__single.py +++ b/mknapsack/tests/test__single.py @@ -7,7 +7,7 @@ from mknapsack._single import solve_single_knapsack from mknapsack._exceptions import FortranInputCheckError -from tests.utils import get_id +from mknapsack.tests.utils import get_id single_knapsack_case_small = { diff --git a/tests/test__subset_sum.py b/mknapsack/tests/test__subset_sum.py similarity index 99% rename from tests/test__subset_sum.py rename to mknapsack/tests/test__subset_sum.py index 1fe599b..da61515 100644 --- a/tests/test__subset_sum.py +++ b/mknapsack/tests/test__subset_sum.py @@ -7,7 +7,7 @@ from mknapsack._subset_sum import solve_subset_sum from mknapsack._exceptions import FortranInputCheckError -from tests.utils import get_id +from mknapsack.tests.utils import get_id subset_sum_case_small = { diff --git a/tests/test__unbounded.py b/mknapsack/tests/test__unbounded.py similarity index 99% rename from tests/test__unbounded.py rename to mknapsack/tests/test__unbounded.py index ee76eb3..e6acb49 100644 --- a/tests/test__unbounded.py +++ b/mknapsack/tests/test__unbounded.py @@ -7,7 +7,7 @@ from mknapsack._unbounded import solve_unbounded_knapsack from mknapsack._exceptions import FortranInputCheckError -from tests.utils import get_id +from mknapsack.tests.utils import get_id unbounded_knapsack_case_small = { diff --git a/tests/utils.py b/mknapsack/tests/utils.py similarity index 100% rename from tests/utils.py rename to mknapsack/tests/utils.py diff --git a/pyproject.toml b/pyproject.toml index d33f196..06f6cd1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,6 +35,9 @@ docs = [ "piccolo-theme", "sphinx" ] +test = [ + "pytest" +] [project.urls] "Homepage" = "https://github.com/jmyrberg/mknapsack" @@ -42,19 +45,23 @@ docs = [ [build-system] requires = [ - "setuptools", + "setuptools>=51.0.0", "setuptools-scm", - "numpy", - "wheel" + "numpy" ] build-backend = "setuptools.build_meta" [tool.setuptools.packages.find] include = [ - "mknapsack*", - "tests*" + "mknapsack*" ] [tool.setuptools_scm] write_to = "mknapsack/_version.py" -local_scheme = "no-local-version" \ No newline at end of file +local_scheme = "no-local-version" + +[tool.cibuildwheel] +skip = 'pp*' +build-verbosity = "3" +test-extras = ["test"] +test-command = "pytest -vv --import-mode=importlib {package}" diff --git a/pytest.ini b/pytest.ini index c857d08..4c01b87 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,2 +1,2 @@ [pytest] -junit_family=xunit1 \ No newline at end of file +junit_family = xunit1 \ No newline at end of file diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..a9b1bd0 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,2 @@ +[metadata] +name = mknapsack \ No newline at end of file diff --git a/setup.py b/setup.py index c0d8150..bc203a0 100644 --- a/setup.py +++ b/setup.py @@ -7,6 +7,7 @@ sources=['mknapsack/_algos.f'], extra_f77_compile_args=['-std=legacy'], f2py_options=['--quiet'], + define_macros=[('NPY_NO_DEPRECATED_API', 'NPY_1_7_API_VERSION')] ) ]