diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 528f981..f8c9808 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -24,10 +24,14 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-latest] + os: [ubuntu-latest, macos-latest] steps: + - name: Install X11 dependencies (macOS) + if: runner.os == 'macOS' + run: brew install libx11 libxpm pkg-config + - name: install X11 dependencies if: ${{ runner.os == 'Linux' }} run: | @@ -90,7 +94,8 @@ jobs: - name: Download build artifacts from this run uses: actions/download-artifact@v4 with: - name: vmol-wheels-${{ runner.os }}-${{ github.ref_name }}-${{ github.run_number }} + pattern: vmol-wheels-*-${{ github.ref_name }}-${{ github.run_number }} + merge-multiple: true path: dist - name: Publish to TestPyPI diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index b13b176..32fc366 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -26,8 +26,11 @@ on: jobs: build_wheels: - name: Build wheels - runs-on: ubuntu-latest + name: Build wheels on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, macos-latest] steps: @@ -56,6 +59,12 @@ jobs: env: CIBW_BUILD: ${{ inputs.cibw-build }} CIBW_SKIP: ${{ inputs.cibw-skip }} + CIBW_BEFORE_ALL_MACOS: | + brew install libx11 libxpm pkg-config + CIBW_ENVIRONMENT_MACOS: MACOSX_DEPLOYMENT_TARGET=15.0 + CIBW_REPAIR_WHEEL_COMMAND_MACOS: > + DYLD_LIBRARY_PATH=$(brew --prefix)/lib + delocate-wheel --require-archs {delocate_archs} -w {dest_dir} -v {wheel} CIBW_BEFORE_ALL_LINUX: | if [ -f /etc/alpine-release ]; then # musllinux (Alpine) apk add --no-cache libx11-dev libxpm-dev pkgconf diff --git a/.gitignore b/.gitignore index 9c6ac0f..b5f9e8a 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ vmol.egg-info/ python/build python/dist python/src +wheelhouse/ diff --git a/BUILD.md b/BUILD.md new file mode 100644 index 0000000..211d498 --- /dev/null +++ b/BUILD.md @@ -0,0 +1,27 @@ +# Build instructions + +## GNU/Linux + +### Requirements: + +* `gcc >= 4.7` +* X11 dev pagages: + * `libx11-dev libxpm-dev x11proto-dev` (Ubuntu / Debian / most of deb-based distros) + * `libX11-devel libXpm-devel xproto-devel` (most of rpm-based distros) +* `make` + +### Compilation + +``` +make +``` + +## macOS + +thanks to [@aligfellow](https://github.com/aligfellow) + +``` +brew install --cask xquartz # runtime display +brew install libx11 libxpm pkg-config +make +``` diff --git a/README.md b/README.md index 525a1fd..6ffe184 100644 --- a/README.md +++ b/README.md @@ -28,18 +28,13 @@ chmod +x ./v ``` ## Build [↑](#contents) -``` -make v -``` + +See build [instructions](BUILD.md). ### Requirements: -To use: -* `GNU/Linux` or `Cygwin` -* `X11` -To build: -* `gcc >= 4.7` -* `libX11-devel libXpm-devel xproto-devel` (`libx11-dev libxpm-dev x11proto-dev` on Ubuntu) -* `make` + +* `GNU/Linux` / `Cygwin` / `macOS` +* `X11` / `XQuartz` ## Usage [↑](#contents) ``` diff --git a/python/README.md b/python/README.md index acc3b7e..1e57073 100644 --- a/python/README.md +++ b/python/README.md @@ -23,6 +23,10 @@ pip install vmol # base version pip install --no-deps vmol # vmol script and "capture the output" will work, # but "pass a structure" feature won't work without numpy ``` +For macOS, an X11 server like XQuartz is required: +``` +brew install --cask xquartz +``` ## Usage diff --git a/python/setup.py b/python/setup.py index 6da5c76..ade1d46 100644 --- a/python/setup.py +++ b/python/setup.py @@ -1,6 +1,7 @@ from setuptools import setup, Extension import subprocess import os +import sys import tempfile import shutil from pathlib import Path @@ -29,6 +30,32 @@ def rel_posix(path): return os.path.relpath(path, start=setup_dir).replace(os.sep, "/") +def get_x11_config(): + # On Linux, X11 headers/libs are in standard system paths — nothing extra needed. + # On macOS, Homebrew installs them to non-default locations, so we probe for them. + if sys.platform != 'darwin': + return [], [] + try: + inc = subprocess.run( + ['pkg-config', '--cflags-only-I', 'x11', 'xpm'], + check=True, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, text=True, + ).stdout.split() + lib = subprocess.run( + ['pkg-config', '--libs-only-L', 'x11', 'xpm'], + check=True, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, text=True, + ).stdout.split() + return [f[2:] for f in inc if f.startswith('-I')], \ + [f[2:] for f in lib if f.startswith('-L')] + except Exception: + pass + for prefix in ['/opt/homebrew', '/usr/local']: + if Path(f'{prefix}/include/X11/Xlib.h').exists(): + return [f'{prefix}/include'], [f'{prefix}/lib'] + raise RuntimeError( + 'X11 headers not found. Install with: brew install libx11 libxpm pkg-config' + ) + + setup_dir = Path(__file__).parent src_dir = setup_dir.parent / "src" @@ -45,12 +72,15 @@ def rel_posix(path): f'-DBUILD_USER="{os.getenv("USER")}@{os.getenv("HOSTNAME")}"', f'-DBUILD_DIRECTORY="{os.getcwd()}"'] +x11_inc, x11_lib = get_x11_config() + setup( version=get_git_version_hash(), include_package_data=True, ext_modules=[Extension('vmol.v', sources=c_files, - include_dirs=include_dirs, + include_dirs=include_dirs + x11_inc, + library_dirs=x11_lib, libraries = ['X11', 'Xpm'], extra_compile_args=['-std=gnu11', '-O2', ] + VERSION_FLAGS,