diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml new file mode 100644 index 00000000..4a646e20 --- /dev/null +++ b/.github/workflows/wheels.yml @@ -0,0 +1,140 @@ +name: Wheels + +on: + push: + branches: + - master + paths: + - ".github/workflows/wheels.yml" + - "python/**" + - "source/**" + pull_request: + branches: + - master + paths: + - ".github/workflows/wheels.yml" + - "python/**" + - "source/**" + release: + types: [published] + workflow_dispatch: + +concurrency: + group: wheels-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + build-wheels: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: + - ubuntu-latest + archs: + - "x86_64" + - "i686" + impl: + - "manylinux" + - "musllinux" + include: + - os: windows-latest + archs: "AMD64" + impl: win + - os: windows-latest + archs: "x86" + impl: win + - os: macos-13 + archs: "universal2" + impl: macosx + fail-fast: false + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup QEMU for Linux cross-compilation + if: runner.os == 'Linux' + uses: docker/setup-qemu-action@v3 + with: + platforms: all + + - name: Clone LLVM project + shell: bash + run: | + git clone -b release/6.x https://github.com/llvm/llvm-project.git + working-directory: python + + - name: Setup LLVM project + shell: bash + run: | + echo 'add_subdirectory(binder)' > python/llvm-project/clang-tools-extra/CMakeLists.txt + cp -r source python/llvm-project/clang-tools-extra/binder + + - name: Build wheels + uses: pypa/cibuildwheel@v2.23.3 + env: + CIBW_BUILD: cp313-${{ matrix.impl }}* + CIBW_ARCHS: ${{ matrix.archs }} + with: + package-dir: python + + - name: Upload wheels to artifacts + uses: actions/upload-artifact@v4 + with: + name: cppbinder-wheels-${{ matrix.os }}-${{ matrix.archs }}-${{ matrix.impl }}-${{ github.event_name == 'release' && github.ref_name || github.sha }} + path: ./wheelhouse/*.whl + + build-sdist: + runs-on: ubuntu-22.04 + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Clone LLVM project + shell: bash + run: | + git clone -b release/6.x https://github.com/llvm/llvm-project.git + working-directory: python + + - name: Setup LLVM project + shell: bash + run: | + echo 'add_subdirectory(binder)' > python/llvm-project/clang-tools-extra/CMakeLists.txt + cp -r source python/llvm-project/clang-tools-extra/binder + + - name: Setup build and build source distribution + run: | + pipx install build + pipx run build --sdist --outdir dist + working-directory: python + + - name: Upload source distribution to artifacts + uses: actions/upload-artifact@v4 + with: + name: cppbinder-sdist-${{ github.event_name == 'release' && github.ref_name || github.sha }} + path: ./python/dist/*.tar.gz + + publish: + needs: [build-wheels, build-sdist] + if: github.event_name == 'release' + runs-on: ubuntu-22.04 + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Download wheels and source distribution + uses: actions/download-artifact@v4 + with: + pattern: cppbinder-* + path: dist + merge-multiple: true + + - name: Publish package to PyPI + uses: pypa/gh-action-pypi-publish@v1.8.14 + with: + user: __token__ + password: ${{ secrets.PYPI_API_TOKEN }} + packages-dir: dist diff --git a/documentation/install.rst b/documentation/install.rst index e30168dd..465dc894 100644 --- a/documentation/install.rst +++ b/documentation/install.rst @@ -1,5 +1,18 @@ -Installation -============ +Install from PyPI +================= + +The easiest way to install Binder is to use pip to install pre-built wheels from PyPI: + +.. code-block:: bash + + pip install cppbinder + +This will install the latest version of Binder and its dependencies. + +To build Binder from source, follow the instructions below. + +Build and install from source +============================= **Binder** is written in C++11 and must be built before use. This page describes the steps for the build process. Please note that installation require up to ~2.6+ Gb of free disk space. diff --git a/python/.gitignore b/python/.gitignore new file mode 100644 index 00000000..9a0dfc3d --- /dev/null +++ b/python/.gitignore @@ -0,0 +1,143 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# IDE config files +.vs/ +.vscode/ +.VSCodeCounter/ +.idea/ + +# CMake build/debug directory +cmake-build-*/ + +# Binder build files +binder/_version.py +binder/bin diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt new file mode 100644 index 00000000..2d3d1f1b --- /dev/null +++ b/python/CMakeLists.txt @@ -0,0 +1,4 @@ +cmake_minimum_required(VERSION 3.15) +project(binder LANGUAGES CXX C) + +add_subdirectory(llvm-project/llvm) diff --git a/python/LICENSE b/python/LICENSE new file mode 120000 index 00000000..ea5b6064 --- /dev/null +++ b/python/LICENSE @@ -0,0 +1 @@ +../LICENSE \ No newline at end of file diff --git a/python/README.md b/python/README.md new file mode 120000 index 00000000..32d46ee8 --- /dev/null +++ b/python/README.md @@ -0,0 +1 @@ +../README.md \ No newline at end of file diff --git a/python/binder/__init__.py b/python/binder/__init__.py new file mode 100644 index 00000000..845e0c64 --- /dev/null +++ b/python/binder/__init__.py @@ -0,0 +1,8 @@ +import os +import subprocess +import sys + + +def binder(): + BINDER_BIN_DIR = os.path.join(os.path.dirname(__file__), "bin") + raise SystemExit(subprocess.call([os.path.join(BINDER_BIN_DIR, "binder"), *sys.argv[1:]], close_fds=False)) diff --git a/python/binder/__main__.py b/python/binder/__main__.py new file mode 100644 index 00000000..03d32031 --- /dev/null +++ b/python/binder/__main__.py @@ -0,0 +1,4 @@ +from binder import binder + +if __name__ == "__main__": + binder() diff --git a/python/pyproject.toml b/python/pyproject.toml new file mode 100644 index 00000000..0892bb46 --- /dev/null +++ b/python/pyproject.toml @@ -0,0 +1,76 @@ +[build-system] +requires = ["scikit-build-core>=0.10.0", "setuptools", "setuptools-scm"] +build-backend = "scikit_build_core.build" + +[project] +name = "cppbinder" +description = "Binder, tool for automatic generation of Python bindings" +readme = "README.md" +dynamic = ["version"] +authors = [ + {name = "Rosetta Commons"}, +] +classifiers = [ + "Programming Language :: Python", + "Programming Language :: Python :: 2", + "Programming Language :: Python :: 3", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", +] +requires-python = ">=2.7" + +[project.urls] +Homepage = "https://github.com/RosettaCommons/binder/" +Documentation = "https://cppbinder.readthedocs.io/" +"Bug Tracker" = "https://github.com/RosettaCommons/binder/issues/" + +[project.scripts] +binder = "binder:binder" + +[tool.scikit-build] +minimum-version = "build-system.requires" +build-dir = "build/{wheel_tag}" + +# Print verbose output +build.verbose = true +logging.level = "INFO" + +# Dynamic versioning +metadata.version.provider = "scikit_build_core.metadata.setuptools_scm" + +# Components to install and targets to build +install.components = ["binder"] +build.targets = ["binder"] + +# Cmake version +cmake.version = ">=3.15,<4" + +# Cmake defines +cmake.define.LLVM_ENABLE_EH = "ON" +cmake.define.LLVM_ENABLE_RTTI = "ON" +cmake.define.LLVM_ENABLE_PROJECTS = "clang" +cmake.define.CMAKE_POLICY_VERSION_MINIMUM = "3.15" + +# Wheel configuration +wheel.packages = ["binder"] +wheel.install-dir = "binder" +wheel.py-api = "py2.py3" +wheel.expand-macos-universal-tags = true + +[tool.cibuildwheel] +build = "cp313-*" +build-verbosity = 1 +test-requires = ["pytest"] +test-command = "pytest {package}/tests" +musllinux-x86_64-image = "quay.io/pypa/musllinux_1_1_x86_64:latest" +musllinux-i686-image = "quay.io/pypa/musllinux_1_1_i686:latest" +musllinux-aarch64-image = "quay.io/pypa/musllinux_1_1_aarch64:latest" +musllinux-ppc64le-image = "quay.io/pypa/musllinux_1_1_ppc64le:latest" +musllinux-s390x-image = "quay.io/pypa/musllinux_1_1_s390x:latest" + +[tool.cibuildwheel.macos] +archs = ["universal2"] + +[tool.setuptools_scm] +root = ".." +git_describe_command = ["git", "describe", "--long", "--tags", "--match", "*[0-9]*"] diff --git a/python/tests/test_binder.py b/python/tests/test_binder.py new file mode 100644 index 00000000..cc97dc76 --- /dev/null +++ b/python/tests/test_binder.py @@ -0,0 +1,12 @@ +import re +import subprocess + + +def test_check_binder_llvm_version(): + # Check the output of the `binder -version` command to obtain the versions of Binder and LLVM + output = subprocess.check_output(["binder", "-version"]).decode() + pattern = r"binder ([\d\.]+)\nLLVM ([\d\.]+)" + binder_version, llvm_version = re.match(pattern, output).groups() + + # Check the versions of LLVM + assert llvm_version == "6.0.1", f"LLVM version mismatch: {llvm_version} != 6.0.1" diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index 06a0353a..5523d3b1 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -123,6 +123,6 @@ else() clangFrontend ) endif() -install(FILES ${CMAKE_CURRENT_BINARY_DIR}/binder +install(TARGETS binder PERMISSIONS WORLD_EXECUTE WORLD_READ OWNER_WRITE OWNER_READ OWNER_EXECUTE -DESTINATION ${CMAKE_INSTALL_BINDIR}) +DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT binder)