From 9bee15caa39a72b7fef86b9ee2ce4cecd89c687b Mon Sep 17 00:00:00 2001 From: messense Date: Fri, 3 Jun 2022 22:57:39 +0800 Subject: [PATCH] Switch from setuptools-rust to maturin (#158) * Make the project compatible with maturin * Build armv7l wheels with maturin-action * Update to `actions/checkout@v3` * Update to `actions/cache@v3` * Build abi3 wheels * Switch from setuptools-rust to maturin * Automate updating package version on tagged release * update set_version.py Co-authored-by: Samuel Colvin --- .github/set_version.py | 39 ++++++++++ .github/workflows/ci.yml | 140 +++++++++++++----------------------- Cargo.lock | 2 +- Cargo.toml | 7 +- MANIFEST.in | 4 -- Makefile | 8 +-- pyproject.toml | 48 ++++++++++++- setup.py | 71 ------------------ src/lib.rs | 1 + watchfiles/_rust_notify.pyi | 2 + watchfiles/version.py | 5 +- 11 files changed, 150 insertions(+), 177 deletions(-) create mode 100755 .github/set_version.py delete mode 100644 MANIFEST.in delete mode 100644 setup.py diff --git a/.github/set_version.py b/.github/set_version.py new file mode 100755 index 00000000..5ad5cfd2 --- /dev/null +++ b/.github/set_version.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python3 +import os +import re +import sys +from pathlib import Path + + +def main(cargo_path_env_var='CARGO_PATH', version_env_vars=('VERSION', 'GITHUB_REF')) -> int: + cargo_path = os.getenv(cargo_path_env_var, 'Cargo.toml') + cargo_path = Path(cargo_path) + if not cargo_path.is_file(): + print(f'✖ path "{cargo_path}" does not exist') + return 1 + + version = None + for var in version_env_vars: + version_ref = os.getenv(var) + if version_ref: + version = re.sub('^refs/tags/v*', '', version_ref.lower()) + break + if not version: + print(f'✖ "{version_env_vars}" env variables not found') + return 1 + + print(f'writing version "{version}", to {cargo_path}') + + version_regex = re.compile('^version ?= ?".*"', re.M) + cargo_content = cargo_path.read_text() + if not version_regex.search(cargo_content): + print(f'✖ {version_regex!r} not found in {cargo_path}') + return 1 + + new_content = version_regex.sub(f'version = "{version}"', cargo_content) + cargo_path.write_text(new_content) + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index df661f3e..d11be71d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -39,7 +39,7 @@ jobs: - id: cache-py name: cache python - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: ${{ env.pythonLocation }} key: > @@ -84,7 +84,18 @@ jobs: with: python-version: '3.10' - - uses: actions/cache@v2 + - name: install rust + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + components: rustfmt, clippy + + - name: cache rust + uses: Swatinem/rust-cache@v1 + + - uses: actions/cache@v3 id: cache-py name: cache python with: @@ -98,29 +109,18 @@ jobs: - run: pip install -r tests/requirements-linting.txt if: steps.cache-py.outputs.cache-hit != 'true' - - run: SKIP_RUST_EXTENSION=1 pip install . + - run: pip install . if: steps.cache-py.outputs.cache-hit != 'true' - run: pip freeze - - name: install rust - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: stable - override: true - components: rustfmt, clippy - - - name: cache rust - uses: Swatinem/rust-cache@v1 - - run: make lint - run: make mypy docs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: set up python uses: actions/setup-python@v3 @@ -141,108 +141,64 @@ jobs: build: name: > - build py3.${{ matrix.python-version }} on ${{ matrix.platform || matrix.os }} - (${{ matrix.alt_arch_name || matrix.arch }}) + build on ${{ matrix.platform || matrix.os }} (${{ matrix.target }} - ${{ matrix.manylinux || 'auto' }}) needs: [test, lint] strategy: fail-fast: false matrix: os: [ubuntu, macos, windows] - python-version: ['7', '8', '9', '10'] - arch: [main, alt] + target: [x86_64, aarch64] + manylinux: [auto] include: - os: ubuntu platform: linux - os: windows ls: dir - - os: ubuntu - arch: alt - alt_arch_name: aarch64 - - os: macos - arch: alt - alt_arch_name: arm64 - exclude: - os: windows - arch: alt - - os: macos - python-version: '7' - arch: alt + ls: dir + target: i686 + python-architecture: x86 + - os: ubuntu + platform: linux + target: i686 + - os: ubuntu + platform: linux + target: armv7 + - os: ubuntu + platform: linux + target: x86_64 + manylinux: musllinux_1_1 + - os: ubuntu + platform: linux + target: aarch64 + manylinux: musllinux_1_1 runs-on: ${{ matrix.os }}-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: set up python uses: actions/setup-python@v3 with: - python-version: '3.9' - - - name: set up rust - if: matrix.os != 'ubuntu' - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: stable - override: true - - - name: cache rust - id: cache-rust - uses: Swatinem/rust-cache@v1 - # caching here seems to break macos, hence only using cache with windows, macos is fairly quick anyway - if: matrix.os == 'windows' + python-version: '3.10' + architecture: ${{ matrix.python-architecture || 'x64' }} - - run: curl -Lso set_version.py https://git.io/JT3rm + - name: set package version + run: python .github/set_version.py if: "startsWith(github.ref, 'refs/tags/')" - - run: python set_version.py + - name: Sync Cargo.lock + run: cargo update -p watchfiles_rust_notify if: "startsWith(github.ref, 'refs/tags/')" - env: - VERSION_PATH: watchfiles/version.py - - - run: rustup target add aarch64-apple-darwin - if: matrix.os == 'macos' - - - run: rustup toolchain install stable-i686-pc-windows-msvc - if: matrix.os == 'windows' && steps.cache-rust.outputs.cache-hit != 'true' - - run: pip install -U setuptools wheel twine cibuildwheel - - - name: build sdist - if: matrix.os == 'ubuntu' && matrix.python-version == '9' - run: python setup.py sdist - env: - SKIP_RUST_EXTENSION: 1 + - run: pip install -U twine - - name: Set up QEMU - if: matrix.os == 'ubuntu' - uses: docker/setup-qemu-action@v2 + - name: build wheels + uses: messense/maturin-action@v1 with: - platforms: all - - - name: build ${{ matrix.platform || matrix.os }} binaries - run: cibuildwheel --output-dir dist - env: - CIBW_BUILD: 'cp3${{ matrix.python-version }}-*' - # rust doesn't seem to be available for musl linux on i686 - CIBW_SKIP: '*-musllinux_i686' - # we build for "alt_arch_name" if it exists, else 'auto' - CIBW_ARCHS: ${{ matrix.alt_arch_name || 'auto' }} - # see https://cibuildwheel.readthedocs.io/en/stable/faq/#universal2, tests can run on cross-compiled binaries - CIBW_TEST_SKIP: '*-macosx_arm64' - CIBW_TEST_REQUIRES: pytest pytest-mock pytest-timeout dirty-equals - CIBW_TEST_COMMAND: 'pytest {project}/tests -s' - CIBW_ENVIRONMENT: 'PATH="$HOME/.cargo/bin:$PATH"' - CIBW_ENVIRONMENT_WINDOWS: 'PATH="$UserProfile\.cargo\bin;$PATH"' - CIBW_MANYLINUX_I686_IMAGE: manylinux2014 - CIBW_MANYLINUX_X86_64_IMAGE: manylinux2014 - CIBW_MUSLLINUX_X86_64_IMAGE: musllinux_1_1 - CIBW_MANYLINUX_AARCH64_IMAGE: manylinux2014 - CIBW_MUSLLINUX_AARCH64_IMAGE: musllinux_1_1 - CIBW_BEFORE_BUILD: rustup show - CIBW_ENVIRONMENT_LINUX: 'PATH="$HOME/.cargo/bin:$PATH" CARGO_NET_GIT_FETCH_WITH_CLI="true"' - CIBW_BEFORE_BUILD_LINUX: > - curl https://sh.rustup.rs -sSf | sh -s -- --profile=minimal -y && - rustup show + target: ${{ matrix.target }} + manylinux: ${{ matrix.manylinux || 'auto' }} + args: --release --out dist - run: ${{ matrix.ls || 'ls -lh' }} dist/ diff --git a/Cargo.lock b/Cargo.lock index 44e023da..0196373d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -375,7 +375,7 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "watchfiles_rust_notify" -version = "1.0.0" +version = "0.0.0" dependencies = [ "crossbeam-channel", "notify", diff --git a/Cargo.toml b/Cargo.toml index afb4d7b7..40219075 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,13 +1,16 @@ [package] name = "watchfiles_rust_notify" -version = "1.0.0" +version = "0.0.0" edition = "2021" [dependencies] crossbeam-channel = "0.5.4" notify = "=5.0.0-pre.15" -pyo3 = {version = "0.16.4", features = ["extension-module"]} +pyo3 = {version = "0.16.4", features = ["extension-module", "abi3-py37"]} [lib] name = "_rust_notify" crate-type = ["cdylib"] + +[package.metadata.maturin] +name = "watchfiles._rust_notify" diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index 824b5c1a..00000000 --- a/MANIFEST.in +++ /dev/null @@ -1,4 +0,0 @@ -include LICENSE -include README.md -include Cargo.toml -recursive-include src * diff --git a/Makefile b/Makefile index 6a172f4d..5212edd3 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ .DEFAULT_GOAL := all -isort = isort watchfiles tests setup.py -black = black watchfiles tests setup.py +isort = isort watchfiles tests +black = black watchfiles tests .PHONY: install install: @@ -31,7 +31,7 @@ format: .PHONY: lint-python lint-python: - flake8 --max-complexity 10 --max-line-length 120 --ignore E203,W503 watchfiles tests setup.py + flake8 --max-complexity 10 --max-line-length 120 --ignore E203,W503 watchfiles tests $(isort) --check-only --df $(black) --check --diff @@ -70,6 +70,7 @@ all: lint mypy testcov docs clean: rm -rf `find . -name __pycache__` rm -f `find . -type f -name '*.py[co]' ` + rm -f `find . -type f -name '*.so' ` rm -f `find . -type f -name '*~' ` rm -f `find . -type f -name '.*~' ` rm -f tests/__init__.py @@ -81,4 +82,3 @@ clean: rm -f .coverage rm -f .coverage.* rm -rf build - python setup.py clean diff --git a/pyproject.toml b/pyproject.toml index 098e1bd4..a8a269e0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,51 @@ [build-system] -requires = ['setuptools', 'setuptools-rust'] +requires = ['maturin>=0.12,<0.13'] +build-backend = 'maturin' + +[project] +name = 'watchfiles' +requires-python = '>=3.7' +description = 'Simple, modern and high performance file watching and code reload in python.' +authors = [ + {name ='Samuel Colvin', email = 's@muelcolvin.com'}, +] +dependencies = [ + 'anyio>=3.0.0,<4', +] +classifiers = [ + 'Development Status :: 5 - Production/Stable', + 'Environment :: Console', + 'Programming Language :: Python', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3 :: Only', + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', + 'Intended Audience :: Developers', + 'Intended Audience :: Information Technology', + 'Intended Audience :: System Administrators', + 'License :: OSI Approved :: MIT License', + 'Operating System :: POSIX :: Linux', + 'Operating System :: Microsoft :: Windows', + 'Operating System :: MacOS', + 'Environment :: MacOS X', + 'Topic :: Software Development :: Libraries :: Python Modules', + 'Topic :: System :: Filesystems', + 'Framework :: AnyIO', +] +dynamic = [ + "license", + "readme", + "version" +] +[project.scripts] +watchfiles = 'watchfiles.cli:cli' +[project.urls] +Documentation = 'https://watchfiles.helpmanual.io' +Funding = 'https://github.com/sponsors/samuelcolvin' +Source = 'https://github.com/samuelcolvin/watchfiles' +Changelog = 'https://github.com/samuelcolvin/watchfiles/releases' [tool.pytest.ini_options] testpaths = 'tests' diff --git a/setup.py b/setup.py deleted file mode 100644 index 17cd3411..00000000 --- a/setup.py +++ /dev/null @@ -1,71 +0,0 @@ -import os -from importlib.machinery import SourceFileLoader -from pathlib import Path - -from setuptools import setup - -description = 'Simple, modern and high performance file watching and code reload in python.' -THIS_DIR = Path(__file__).resolve().parent -try: - long_description = (THIS_DIR / 'README.md').read_text() -except FileNotFoundError: - long_description = description - -# avoid loading the package before requirements are installed: -version = SourceFileLoader('version', 'watchfiles/version.py').load_module() - -extra = {} -if not os.getenv('SKIP_RUST_EXTENSION'): - from setuptools_rust import Binding, RustExtension - - extra['rust_extensions'] = [RustExtension('watchfiles._rust_notify', binding=Binding.PyO3)] - -setup( - name='watchfiles', - version=str(version.VERSION), - description=description, - long_description=long_description, - long_description_content_type='text/markdown', - classifiers=[ - 'Development Status :: 5 - Production/Stable', - 'Environment :: Console', - 'Programming Language :: Python', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3 :: Only', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.8', - 'Programming Language :: Python :: 3.9', - 'Programming Language :: Python :: 3.10', - 'Intended Audience :: Developers', - 'Intended Audience :: Information Technology', - 'Intended Audience :: System Administrators', - 'License :: OSI Approved :: MIT License', - 'Operating System :: POSIX :: Linux', - 'Operating System :: Microsoft :: Windows', - 'Operating System :: MacOS', - 'Environment :: MacOS X', - 'Topic :: Software Development :: Libraries :: Python Modules', - 'Topic :: System :: Filesystems', - 'Framework :: AnyIO', - ], - author='Samuel Colvin', - author_email='s@muelcolvin.com', - url='https://github.com/samuelcolvin/watchfiles', - entry_points=""" - [console_scripts] - watchfiles=watchfiles.cli:cli - """, - license='MIT', - packages=['watchfiles'], - package_data={'watchfiles': ['py.typed', '*.pyi']}, - install_requires=['anyio>=3.0.0,<4'], - python_requires='>=3.7', - zip_safe=False, - project_urls={ - 'Documentation': 'https://watchfiles.helpmanual.io', - 'Funding': 'https://github.com/sponsors/samuelcolvin', - 'Source': 'https://github.com/samuelcolvin/watchfiles', - 'Changelog': 'https://github.com/samuelcolvin/watchfiles/releases', - }, - **extra, -) diff --git a/src/lib.rs b/src/lib.rs index fac8343c..b777ccd2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -239,6 +239,7 @@ impl RustNotify { #[pymodule] fn _rust_notify(py: Python, m: &PyModule) -> PyResult<()> { + m.add("__version__", env!("CARGO_PKG_VERSION"))?; m.add( "WatchfilesRustInternalError", py.get_type::(), diff --git a/watchfiles/_rust_notify.pyi b/watchfiles/_rust_notify.pyi index 82c3b67a..911f3d05 100644 --- a/watchfiles/_rust_notify.pyi +++ b/watchfiles/_rust_notify.pyi @@ -2,6 +2,8 @@ from typing import List, Literal, Optional, Protocol, Set, Tuple, Union __all__ = 'RustNotify', 'WatchfilesRustInternalError' +__version__: str + class AbstractEvent(Protocol): def is_set(self) -> bool: ... diff --git a/watchfiles/version.py b/watchfiles/version.py index d5918058..f55721f1 100644 --- a/watchfiles/version.py +++ b/watchfiles/version.py @@ -1,4 +1,5 @@ +from ._rust_notify import __version__ + __all__ = ('VERSION',) -# version is automatically set in the "build" CI job before release -VERSION = '0.0.dev0' +VERSION = __version__