From dc9b159fd80925b756fd04b3bbe56c32ada24b0b Mon Sep 17 00:00:00 2001 From: Will Date: Sun, 14 Jul 2024 11:29:55 -0400 Subject: [PATCH] watcher-py: with meson and cibuildwheel --- .github/workflows/watcher-py-wheels.yml | 31 +++++++++++++ .gitignore | 1 + libcwatcher/cross-compile.sh | 60 ++++++++++++++++--------- libcwatcher/meson.build | 35 ++++----------- meson.build | 8 ++++ pyproject.toml | 20 +++++++++ watcher-py/build.py | 29 ------------ watcher-py/meson.build | 2 + watcher-py/pyproject.toml | 21 --------- watcher-py/watcher/watcher.py | 22 +++++---- 10 files changed, 123 insertions(+), 106 deletions(-) create mode 100644 .github/workflows/watcher-py-wheels.yml create mode 100644 meson.build create mode 100644 pyproject.toml delete mode 100644 watcher-py/build.py create mode 100644 watcher-py/meson.build delete mode 100644 watcher-py/pyproject.toml diff --git a/.github/workflows/watcher-py-wheels.yml b/.github/workflows/watcher-py-wheels.yml new file mode 100644 index 00000000..54c3a9b8 --- /dev/null +++ b/.github/workflows/watcher-py-wheels.yml @@ -0,0 +1,31 @@ +name: Watcher Py Wheels + +on: + push: + branches: [ release, next ] + pull_request: + branches: [ release, next ] + + +jobs: + watcher_py_wheels: + # Ref: https://cibuildwheel.pypa.io/en/stable/setup + strategy: + matrix: + # macos-13 is an intel runner, macos-14 is apple silicon + os: [ ubuntu-latest, windows-latest, macos-13, macos-14 ] + name: Build wheels on ${{matrix.os}} + runs-on: ${{matrix.os}} + steps: + - uses: actions/checkout@v4 + - uses: pypa/cibuildwheel@v2.19.2 + env: + MACOSX_DEPLOYMENT_TARGET: "10.14" + with: + package-dir: . + output-dir: wheelhouse + config-file: pyproject.toml + - uses: actions/upload-artifact@v4 + with: + name: cibw-wheels-${{matrix.os}}-${{strategy.job-index}} + path: wheelhouse/*.whl diff --git a/.gitignore b/.gitignore index 3dc0c39e..33b3337b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ .cache compile_commands.json tmp_test_watcher +virtualenv /out /result /watcher diff --git a/libcwatcher/cross-compile.sh b/libcwatcher/cross-compile.sh index b968b73d..218be652 100755 --- a/libcwatcher/cross-compile.sh +++ b/libcwatcher/cross-compile.sh @@ -1,24 +1,42 @@ #! /usr/bin/env bash set -e -[ -d "$(dirname "$0")/out" ] || mkdir "$(dirname "$0")/out" -cd "$(dirname "$0")" -linux-cross-compilation-containers/build-containers.sh -[ -f out/meson.build ] || cp meson.build out -[ -d out/src ] || cp -r src out -[ -d out/include ] || cp -r include out -( - cd out - SRC=$PWD - docker run --platform linux/amd64 --rm -v "$SRC:/src" meson-builder-x86_64-unknown-linux-gnu:latest - docker run --platform linux/arm64 --rm -v "$SRC:/src" meson-builder-aarch64-unknown-linux-gnu:latest - docker run --platform linux/arm/v7 --rm -v "$SRC:/src" meson-builder-armv7-unknown-linux-gnueabihf:latest -) -[ "$(uname)" = Darwin ] && { - [ -d out/x86_64-apple-darwin ] || meson setup --cross-file cross-files/x86_64-apple-darwin.txt out/x86_64-apple-darwin - [ -d out/aarch64-apple-darwin ] || meson setup --cross-file cross-files/aarch64-apple-darwin.txt out/aarch64-apple-darwin - meson compile -C out/x86_64-apple-darwin - meson compile -C out/aarch64-apple-darwin +[ "${1:-}" = --clean ] && { + rm -rf "$(dirname "$0")/out" + exit +} +[ "${1:-}" = --show-artifacts ] && { + for pattern in '*.a' '*.so' '*.dylib' + do find "$(dirname "$0")/out" -type f -name "$pattern" + done + exit +} +[ "${1:---build}" = --build ] && { + [ -d "$(dirname "$0")/out" ] || mkdir "$(dirname "$0")/out" + cd "$(dirname "$0")" + linux-cross-compilation-containers/build-containers.sh + [ -f out/meson.build ] || cp meson.build out + [ -d out/src ] || cp -r src out + [ -d out/include ] || cp -r include out + ( + cd out + SRC=$PWD + docker run --platform linux/amd64 --rm -v "$SRC:/src" meson-builder-x86_64-unknown-linux-gnu:latest + docker run --platform linux/arm64 --rm -v "$SRC:/src" meson-builder-aarch64-unknown-linux-gnu:latest + docker run --platform linux/arm/v7 --rm -v "$SRC:/src" meson-builder-armv7-unknown-linux-gnueabihf:latest + ) + [ "$(uname)" = Darwin ] && { + [ -d out/x86_64-apple-darwin ] || meson setup --cross-file cross-files/x86_64-apple-darwin.txt out/x86_64-apple-darwin + [ -d out/aarch64-apple-darwin ] || meson setup --cross-file cross-files/aarch64-apple-darwin.txt out/aarch64-apple-darwin + meson compile -C out/x86_64-apple-darwin + meson compile -C out/aarch64-apple-darwin + } + echo '-------- Artifacts --------' + for pattern in '*.a' '*.so' '*.dylib' + do find out -type f -name "$pattern" -exec file {} \; + done + echo '~~~~~~~~~~~~~~~~~~~~~~~~~~~' + for pattern in '*.a' '*.so' '*.dylib' + do find out -type f -name "$pattern" + done + echo '---------------------------' } -for pattern in '*.a' '*.so' '*.dylib' -do find out -type f -name "$pattern" -exec file {} \; -done diff --git a/libcwatcher/meson.build b/libcwatcher/meson.build index 9eac5604..a720001a 100644 --- a/libcwatcher/meson.build +++ b/libcwatcher/meson.build @@ -1,45 +1,28 @@ -project( - 'libcwatcher', - ['cpp', 'c'], - version : '0.12.0', # hook: tool/release - default_options : ['c_std=c99', 'cpp_std=c++20'], -) - -# I know it's not the convential way to version a shared library... +# I know it's not the convential way to version a library... # But I'm not sure how to do it the "right" way in Meson. -libname = 'cwatcher-' + meson.project_version() +libcwatcher_name = 'cwatcher-' + meson.project_version() if target_machine.system() == 'darwin' - deps = dependency(['CoreServices', 'CoreFoundation']) + libcwatcher_deps = [dependency('CoreServices'), dependency('CoreFoundation')] else - deps = dependency('threads') + libcwatcher_deps = [dependency('threads')] endif -cwatcher_shared_lib = shared_library( - libname, - ['src/watcher-cabi.cpp'], - include_directories : ['include'], - dependencies : deps, - build_rpath : '/usr/local/lib', - install_rpath : '/usr/local/lib', - install : true, -) - -cwatcher_static_lib = static_library( - libname, +libcwatcher = library( + libcwatcher_name, ['src/watcher-cabi.cpp'], include_directories : ['include'], - dependencies : deps, + dependencies : libcwatcher_deps, build_rpath : '/usr/local/lib', install_rpath : '/usr/local/lib', install : true, ) -executable( +test_libcwatcher = executable( 'test-watcher-cabi', ['src/test-watcher-cabi.c'], include_directories : ['include'], - link_with : [cwatcher_shared_lib], + link_with : [libcwatcher], build_rpath : '/usr/local/lib', install_rpath : '/usr/local/lib', install : false, diff --git a/meson.build b/meson.build new file mode 100644 index 00000000..4eed1d85 --- /dev/null +++ b/meson.build @@ -0,0 +1,8 @@ +project( + 'watcher', + ['cpp', 'c'], + version : '0.12.0', # hook: tool/release + default_options : ['c_std=c99', 'cpp_std=c++20'], +) +subdir('libcwatcher') +subdir('watcher-py') diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..12a2542e --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,20 @@ +[build-system] +build-backend = "mesonpy" +requires = ["meson >= 1.0.0", "meson-python >= 0.14.0"] + +[project] +name = "watcher" +version = "0.12.0" +authors = [{name = "Will"}] +homepage = "https://github.com/e-dant/watcher" +description = "Filesystem watcher. Works anywhere. Simple, efficient, and friendly." +license.text = "MIT" +keywords = [ "watcher", "filesystem", "events", "async" ] +packages = [{include = "watcher"}] + +[tool.cibuildwheel] +free-threaded-support = true +manylinux-x86_64-image = "manylinux2014" +manylinux-i686-image = "manylinux2014" +manylinux-pypy_x86_64-image = "manylinux2014" +manylinux-pypy_i686-image = "manylinux2014" diff --git a/watcher-py/build.py b/watcher-py/build.py deleted file mode 100644 index cda7b2f3..00000000 --- a/watcher-py/build.py +++ /dev/null @@ -1,29 +0,0 @@ -import os -import shutil -import glob -from os.path import dirname, realpath -from subprocess import check_call - -HERE_DIR = realpath(dirname(__file__)) -LIBCWATCHER_SRC_DIR = realpath(os.path.join(HERE_DIR, "..", "libcwatcher")) -BUILD_DIR = os.path.join(HERE_DIR, "build") -INSTALL_DIR = os.path.join(HERE_DIR, "watcher", "lib") - - -def build(): - def is_shared_lib(lib): - return lib.endswith(".so") or lib.endswith(".dylib") or lib.endswith(".dll") - - print(f"{HERE_DIR=}") - print(f"{LIBCWATCHER_SRC_DIR=}") - print(f"{BUILD_DIR=}") - print(f"{INSTALL_DIR=}") - if not os.path.exists(BUILD_DIR): - check_call(["meson", "setup", BUILD_DIR, LIBCWATCHER_SRC_DIR]) - check_call(["meson", "compile", "-C", BUILD_DIR]) - # Because Meson does not want to install the library in the right place, - # regardless of the destdir, and I don't want hardcodes paths in the build file. - os.makedirs(INSTALL_DIR, exist_ok=True) - for lib in filter(is_shared_lib, glob.glob(os.path.join(BUILD_DIR, "lib*"))): - print(f"Copying {lib} to {INSTALL_DIR}") - shutil.copy(lib, INSTALL_DIR) diff --git a/watcher-py/meson.build b/watcher-py/meson.build new file mode 100644 index 00000000..f825eaf6 --- /dev/null +++ b/watcher-py/meson.build @@ -0,0 +1,2 @@ +py = import('python').find_installation() +py.install_sources(['watcher/__init__.py', 'watcher/watcher.py']) diff --git a/watcher-py/pyproject.toml b/watcher-py/pyproject.toml deleted file mode 100644 index cf219612..00000000 --- a/watcher-py/pyproject.toml +++ /dev/null @@ -1,21 +0,0 @@ -[build-system] -requires = ["poetry-core>=1.0.0"] -build-backend = "poetry.core.masonry.api" - -[tool.poetry] -name = "watcher" -version = "0.12.0" -authors = ["Will"] -homepage = "https://github.com/e-dant/watcher" -description = "Filesystem watcher. Works anywhere. Simple, efficient, and friendly." -license = "MIT" -keywords = ["watcher", "filesystem", "events", "async"] -packages = [{include = "watcher"}] - -[tool.poetry.dependencies] -python = "^3.12" -meson = "^0.59" - -[tool.poetry.scripts] -build-libcwatcher = "build:build" - diff --git a/watcher-py/watcher/watcher.py b/watcher-py/watcher/watcher.py index edae2582..cf26101f 100644 --- a/watcher-py/watcher/watcher.py +++ b/watcher-py/watcher/watcher.py @@ -24,7 +24,7 @@ class _CEvent(ctypes.Structure): def _lazy_static_solib_handle() -> ctypes.CDLL: - def so_file_ending(): + def native_solib_file_ending(): match os.uname().sysname: case "Darwin": return "dylib" @@ -33,15 +33,19 @@ def so_file_ending(): case _: return "so" - def libcwatcher_path(): + def libcwatcher_lib_path(): version = "0.12.0" # hook: tool/release - libname = f"libcwatcher-{version}.{so_file_ending()}" heredir = os.path.dirname(os.path.abspath(__file__)) - return os.path.join(heredir, "lib", libname) + dir_path = os.path.join(heredir, ".watcher.mesonpy.libs") + lib_name = f"libcwatcher-{version}.{native_solib_file_ending()}" + lib_path = os.path.join(dir_path, lib_name) + if not os.path.exists(lib_path): + raise RuntimeError(f"Could not find '{lib_path}', did the install dir change?") + return lib_path global _LIB if _LIB is None: - _LIB = ctypes.CDLL(libcwatcher_path()) + _LIB = ctypes.CDLL(libcwatcher_lib_path()) _LIB.wtr_watcher_open.argtypes = [ctypes.c_char_p, _CCallback, ctypes.c_void_p] _LIB.wtr_watcher_open.restype = ctypes.c_void_p _LIB.wtr_watcher_close.argtypes = [ctypes.c_void_p] @@ -98,8 +102,9 @@ class Event: class Watch: def __init__(self, path: str, callback: Callable[[Event], None]): - def callback_bridge(event, _): - callback(_c_event_to_event(event)) + def callback_bridge(c_event: _CEvent, _) -> None: + py_event = _c_event_to_event(c_event) + callback(py_event) self._lib = _lazy_static_solib_handle() self._path = path.encode("utf-8") @@ -113,8 +118,7 @@ def callback_bridge(event, _): def close(self): if self._watcher: - if not self._lib.wtr_watcher_close(self._watcher): - raise RuntimeError("Internal error while closing a watcher") + self._lib.wtr_watcher_close(self._watcher) self._watcher = None def __del__(self):