diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..bcc96b7 --- /dev/null +++ b/.clang-format @@ -0,0 +1,88 @@ +# https://raw.githubusercontent.com/cubao/cmake/master/_clang-format +Language: Cpp +# BasedOnStyle: LLVM +AccessModifierOffset: -2 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlinesLeft: false +AlignOperands: true +AlignTrailingComments: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: true +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: false +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterFunction: true + AfterClass: true + AfterControlStatement: false + AfterEnum: true + AfterNamespace: true + AfterObjCDeclaration: false + AfterStruct: true + AfterUnion: true + BeforeCatch: false + BeforeElse: false + IndentBraces: false +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Custom +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +ColumnLimit: 80 +CommentPragmas: '^ IWYU pragma:' +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DerivePointerAlignment: false +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ] +IncludeCategories: + - Regex: '^"(llvm|llvm-c|clang|clang-c)/' + Priority: 2 + - Regex: '^(<|"(gtest|isl|json)/)' + Priority: 3 + - Regex: '.*' + Priority: 1 +IndentCaseLabels: false +IndentWidth: 4 +IndentWrappedFunctionNames: false +KeepEmptyLinesAtTheStartOfBlocks: true +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBlockIndentWidth: 2 +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 60 +PointerAlignment: Right +ReflowComments: true +SortIncludes: false +SpaceAfterCStyleCast: false +SpaceBeforeAssignmentOperators: true +SpaceBeforeParens: ControlStatements +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Cpp11 +TabWidth: 4 +UseTab: Never diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..8ec2be9 --- /dev/null +++ b/.flake8 @@ -0,0 +1,4 @@ +[flake8] +# these error codes interfere with Black +ignore = E203, E231, E501, W503, B950 +select = C,E,F,W,B,B9 diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml new file mode 100644 index 0000000..c792242 --- /dev/null +++ b/.github/workflows/format.yml @@ -0,0 +1,22 @@ +# This is a format job. Pre-commit has a first-party GitHub action, so we use +# that: https://github.com/pre-commit/action + +name: Format + +on: + workflow_dispatch: + pull_request: + push: + branches: + - master + +jobs: + pre-commit: + name: Format + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 + with: + python-version: "3.x" + - uses: pre-commit/action@v3.0.0 diff --git a/.github/workflows/pip.yml b/.github/workflows/pip.yml new file mode 100644 index 0000000..f53ed3c --- /dev/null +++ b/.github/workflows/pip.yml @@ -0,0 +1,36 @@ +name: Pip + +on: + workflow_dispatch: + pull_request: + push: + branches: + - master + +jobs: + build: + strategy: + fail-fast: false + matrix: + platform: [ubuntu-20.04, windows-2019, macos-11] + python-version: ["3.8", "3.9", "3.10"] + + runs-on: ${{ matrix.platform }} + + steps: + - uses: actions/checkout@v3 + with: + submodules: true + + - uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + + - name: Add requirements + run: python -m pip install --upgrade wheel setuptools + + - name: Build and install + run: pip install --verbose .[test] + + - name: Test + run: python -m pytest diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml new file mode 100644 index 0000000..8a09551 --- /dev/null +++ b/.github/workflows/wheels.yml @@ -0,0 +1,91 @@ +name: Wheels + +on: + workflow_dispatch: + pull_request: + push: + branches: + - master + release: + types: + - published + +jobs: + build_sdist: + name: Build SDist + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + submodules: true + + - name: Build SDist + run: pipx run build --sdist + + - name: Check metadata + run: pipx run twine check dist/* + + - uses: actions/upload-artifact@v3 + with: + path: dist/*.tar.gz + + + build_wheels: + name: Wheels on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + + steps: + - uses: actions/checkout@v3 + with: + submodules: true + + - name: Set up QEMU + if: runner.os == 'Linux' + uses: docker/setup-qemu-action@v2 + with: + platforms: all + + - uses: pypa/cibuildwheel@v2.12.0 + env: + # CIBW_ARCHS: auto64 + CIBW_ARCHS_LINUX: x86_64 aarch64 + CIBW_ARCHS_WINDOWS: AMD64 # ARM64 + CIBW_ARCHS_MACOS: x86_64 arm64 + CIBW_BEFORE_BUILD: pip install numpy fire --prefer-binary + # https://cibuildwheel.readthedocs.io/en/stable/options/#build-skip + CIBW_SKIP: pp* *i686 *musllinux* + CIBW_TEST_SKIP: "*macosx* *-win_arm64" + + - name: Verify clean directory + run: git diff --exit-code + shell: bash + + - name: Upload wheels + uses: actions/upload-artifact@v3 + with: + path: wheelhouse/*.whl + + + upload_all: + name: Upload if release + needs: [build_wheels, build_sdist] + runs-on: ubuntu-latest + if: github.event_name == 'release' && github.event.action == 'published' + + steps: + - uses: actions/setup-python@v4 + with: + python-version: "3.x" + + - uses: actions/download-artifact@v3 + with: + name: artifact + path: dist + + - uses: pypa/gh-action-pypi-publish@v1.6.4 + with: + password: ${{ secrets.PYPI_API_TOKEN }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9542c50 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +build/ +dist/ +_build/ +_generate/ +*.so +*.py[cod] +*.egg-info +*env* +wheelhouse +site diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..ced1d11 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,4 @@ +[submodule "pybind11"] + path = pybind11 + url = https://github.com/pybind/pybind11.git + branch = master diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..e4b8a5e --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,78 @@ +# To use: +# +# pre-commit run -a +# +# Or: +# +# pre-commit install # (runs every time you commit in git) +# +# To update this file: +# +# pre-commit autoupdate +# +# See https://github.com/pre-commit/pre-commit + +ci: + autoupdate_commit_msg: "chore: update pre-commit hooks" + autofix_commit_msg: "style: pre-commit fixes" + +repos: +# Standard hooks +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.4.0 + hooks: + - id: check-added-large-files + - id: check-case-conflict + - id: check-merge-conflict + - id: check-symlinks + - id: check-yaml + exclude: ^conda\.recipe/meta\.yaml$ + - id: debug-statements + - id: end-of-file-fixer + - id: mixed-line-ending + - id: requirements-txt-fixer + - id: trailing-whitespace + +# Black, the code formatter, natively supports pre-commit +- repo: https://github.com/psf/black + rev: 23.1.0 + hooks: + - id: black + exclude: ^(docs) + +- repo: https://github.com/charliermarsh/ruff-pre-commit + rev: "v0.0.254" + hooks: + - id: ruff + args: ["--fix", "--show-fixes"] + +# Checking static types +- repo: https://github.com/pre-commit/mirrors-mypy + rev: "v1.0.1" + hooks: + - id: mypy + files: "setup.py" + args: [] + additional_dependencies: [types-setuptools] + +# Changes tabs to spaces +- repo: https://github.com/Lucas-C/pre-commit-hooks + rev: v1.4.2 + hooks: + - id: remove-tabs + exclude: ^(docs|Makefile) + +# CMake formatting +- repo: https://github.com/cheshirekow/cmake-format-precommit + rev: v0.6.13 + hooks: + - id: cmake-format + additional_dependencies: [pyyaml] + types: [file] + files: (\.cmake|CMakeLists.txt)(.in)?$ + +# Suggested hook if you add a .clang-format file +- repo: https://github.com/pre-commit/mirrors-clang-format + rev: v13.0.0 + hooks: + - id: clang-format diff --git a/.readthedocs.yml b/.readthedocs.yml new file mode 100644 index 0000000..a4f6d73 --- /dev/null +++ b/.readthedocs.yml @@ -0,0 +1,7 @@ +version: 2 +formats: all +mkdocs: + fail_on_warning: false +python: + install: + - requirements: docs/requirements.txt diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..b7804d3 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,10 @@ +cmake_minimum_required(VERSION 3.4...3.18) +project(cubao_cmake_example) + +add_subdirectory(pybind11) +pybind11_add_module(pybind11_cubao_cmake_example src/main.cpp) + +# EXAMPLE_VERSION_INFO is defined by setup.py and passed into the C++ code as a +# define (VERSION_INFO) here. +target_compile_definitions(pybind11_cubao_cmake_example + PRIVATE VERSION_INFO=${EXAMPLE_VERSION_INFO}) diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..99f6068 --- /dev/null +++ b/LICENSE @@ -0,0 +1,36 @@ +Copyright (c) 2016 The Pybind Development Team, All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +You are under no obligation whatsoever to provide any bug fixes, patches, or +upgrades to the features, functionality or performance of the source code +("Enhancements") to anyone; however, if you choose to make your Enhancements +available either publicly, or directly to the author of this software, without +imposing a separate written license agreement for such Enhancements, then you +hereby grant the following license: a non-exclusive, royalty-free perpetual +license to install, use, modify, prepare derivative works, incorporate into +other computer software, distribute, and sublicense such enhancements or +derivative works thereof, in binary and source code form. diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..71dbebb --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,5 @@ +include README.md LICENSE pybind11/LICENSE +graft pybind11/include +graft pybind11/tools +graft src +global-include CMakeLists.txt *.cmake diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..7ca3ff3 --- /dev/null +++ b/Makefile @@ -0,0 +1,109 @@ +PROJECT_SOURCE_DIR ?= $(abspath ./) +PROJECT_NAME ?= $(shell basename $(PROJECT_SOURCE_DIR)) + +all: + @echo nothing special + +clean: + rm -rf build dist site wheelhouse *.egg-info +force_clean: + docker run --rm -v `pwd`:`pwd` -w `pwd` -it alpine/make make clean +.PHONY: clean force_clean + +lint: + pre-commit run -a +lint_install: + pre-commit install + +build: + mkdir -p build && cd build && \ + cmake .. && make +.PHONY: build + +docs_build: + python3 -m pip install -r docs/requirements.txt + mkdocs build +docs_serve: + python3 -m pip install -r docs/requirements.txt + mkdocs serve -a 0.0.0.0:8088 + +DOCKER_TAG_WINDOWS ?= ghcr.io/cubao/build-env-windows-x64:v0.0.1 +DOCKER_TAG_LINUX ?= ghcr.io/cubao/build-env-manylinux2014-x64:v0.0.4 +DOCKER_TAG_MACOS ?= ghcr.io/cubao/build-env-macos-arm64:v0.0.1 + +test_in_win: + docker run --rm -w `pwd` -v `pwd`:`pwd` -v `pwd`/build/win:`pwd`/build -it $(DOCKER_TAG_WINDOWS) bash +test_in_mac: + docker run --rm -w `pwd` -v `pwd`:`pwd` -v `pwd`/build/mac:`pwd`/build -it $(DOCKER_TAG_MACOS) bash +test_in_linux: + docker run --rm -w `pwd` -v `pwd`:`pwd` -v `pwd`/build/linux:`pwd`/build -it $(DOCKER_TAG_LINUX) bash + +DEV_CONTAINER_NAME ?= $(USER)_$(subst /,_,$(PROJECT_NAME)____$(PROJECT_SOURCE_DIR)) +DEV_CONTAINER_IMAG ?= $(DOCKER_TAG_LINUX) +test_in_dev_container: + docker ps | grep $(DEV_CONTAINER_NAME) \ + && docker exec -it $(DEV_CONTAINER_NAME) bash \ + || docker run --rm --name $(DEV_CONTAINER_NAME) \ + --network host --security-opt seccomp=unconfined \ + -v `pwd`:`pwd` -w `pwd` -it $(DEV_CONTAINER_IMAG) bash + +PYTHON ?= python3 +python_install: + $(PYTHON) setup.py install +python_build: + $(PYTHON) setup.py bdist_wheel +python_sdist: + $(PYTHON) setup.py sdist +python_test: + $(PYTHON) -c 'from pybind11_rdp import rdp; print(rdp([[1, 1], [2, 2], [3, 3], [4, 4]]))' + $(PYTHON) -c 'import cubao_cmake_example; print(cubao_cmake_example.add(1, 2))' + $(PYTHON) -m cubao_cmake_example add 1 2 + $(PYTHON) -m cubao_cmake_example subtract 9 4 + $(PYTHON) -m cubao_cmake_example pure_python_func --arg1=43234 + python3 -m pip install pytest + pytest tests + +# conda create -y -n py36 python=3.6 +# conda create -y -n py37 python=3.7 +# conda create -y -n py38 python=3.8 +# conda create -y -n py39 python=3.9 +# conda create -y -n py310 python=3.10 +# conda create -y -n py311 python=3.11 +# conda env list +python_build_py36: + PYTHON=python conda run --no-capture-output -n py36 make python_build +python_build_py37: + PYTHON=python conda run --no-capture-output -n py37 make python_build +python_build_py38: + PYTHON=python conda run --no-capture-output -n py38 make python_build +python_build_py39: + PYTHON=python conda run --no-capture-output -n py39 make python_build +python_build_py310: + PYTHON=python conda run --no-capture-output -n py310 make python_build +python_build_py311: + PYTHON=python conda run --no-capture-output -n py311 make python_build +python_build_all: python_build_py36 python_build_py37 python_build_py38 python_build_py39 python_build_py310 python_build_py311 +python_build_all_in_linux: + docker run --rm -w `pwd` -v `pwd`:`pwd` -v `pwd`/build/linux:`pwd`/build -it $(DOCKER_TAG_LINUX) make python_build_all repair_wheels +python_build_all_in_macos: python_build_py38 python_build_py39 python_build_py310 python_build_py311 +python_build_all_in_windows: python_build_all + +repair_wheels: + python -m pip install auditwheel # sudo apt install patchelf + ls dist/*-linux_x86_64.whl | xargs -n1 auditwheel repair --plat manylinux2014_x86_64 + rm -rf dist/*-linux_x86_64.whl && cp wheelhouse/*.whl dist && rm -rf wheelhouse + +pypi_remote ?= pypi +upload_wheels: + python -m pip install twine + twine upload dist/*.whl -r $(pypi_remote) + +tar.gz: + tar -cvz --exclude .git -f ../$(PROJECT_NAME).tar.gz . + ls -alh ../$(PROJECT_NAME).tar.gz + +# https://stackoverflow.com/a/25817631 +echo-% : ; @echo -n $($*) +Echo-% : ; @echo $($*) +ECHO-% : ; @echo $* = $($*) +echo-Tab: ; @echo -n ' ' diff --git a/README.md b/README.md new file mode 100644 index 0000000..8c189ec --- /dev/null +++ b/README.md @@ -0,0 +1,215 @@ +# cmake-example + +Online document: **[readthedocs](http://cmake-example.readthedocs.io/)** + + + +## Usage + +Install: + +```bash +python3 -m pip install cubao_cmake_example # install from pypi +python3 -c 'import cubao_cmake_example; print(cubao_cmake_example.add(1, 2))' +``` + +CLI interface: (created with [python-fire](https://github.com/google/python-fire)) + +```bash +python3 -m cubao_cmake_example add 1 2 +python3 -m cubao_cmake_example subtract 9 4 +``` + +Help: + +```bash +$ python3 -m cubao_cmake_example --help +INFO: Showing help with the command '__main__.py -- --help'. + +NAME + __main__.py + +SYNOPSIS + __main__.py GROUP | COMMAND + +GROUPS + GROUP is one of the following: + + fire + The Python Fire module. + +COMMANDS + COMMAND is one of the following: + + add + add(arg0: int, arg1: int) -> int + + subtract + subtract(arg0: int, arg1: int) -> int + + pure_python_func +``` + +```bash +$ python3 -m cubao_cmake_example pure_python_func --help +INFO: Showing help with the command '__main__.py pure_python_func -- --help'. + +NAME + __main__.py pure_python_func + +SYNOPSIS + __main__.py pure_python_func + +FLAGS + --arg1=ARG1 + Type: int + Default: 42 + --arg2=ARG2 + Type: float + Default: 3.14 + --arg3=ARG3 + Type: str + Default: 'you shall not pass' + +$ python3 -m cubao_cmake_example pure_python_func --arg1=43234 +int: 43234, float: 3.14, str: you shall not pass +``` + + + +--- + +# Make a release + +(We now use Github Workflow to release to pypi. Skip the rest if you don't want to manually compile wheels.) + +## On linux + +Install docker then + +``` +make python_build_all_in_linux +make upload_wheels +``` + +## On macOS + +Install c++ compiler and cmake. + +Install conda and envs: + +``` +# conda create -y -n py36 python=3.6 +# conda create -y -n py37 python=3.7 +conda create -y -n py38 python=3.8 +conda create -y -n py39 python=3.9 +conda create -y -n py310 python=3.10 +conda env list +``` + +Then + +``` +make python_build_all_in_macos +make upload_wheels +``` + +## On windows + +Install visual studio and cmake, (also git for windows, maybe). + +Install conda and envs same as on macOS, then: + +``` +make python_build_all_in_windows +make upload_wheels +``` + +--- + +# cmake_example for pybind11 + +[![Gitter][gitter-badge]][gitter-link] + +| CI | status | +|----------------------|--------| +| MSVC 2015 | [![AppVeyor][appveyor-badge]][appveyor-link] | +| conda.recipe | [![Conda Actions Status][actions-conda-badge]][actions-conda-link] | +| pip builds | [![Pip Actions Status][actions-pip-badge]][actions-pip-link] | +| [`cibuildwheel`][] | [![Wheels Actions Status][actions-wheels-badge]][actions-wheels-link] | + +[gitter-badge]: https://badges.gitter.im/pybind/Lobby.svg +[gitter-link]: https://gitter.im/pybind/Lobby +[actions-badge]: https://github.com/pybind/cmake_example/workflows/Tests/badge.svg +[actions-conda-link]: https://github.com/pybind/cmake_example/actions?query=workflow%3A%22Conda +[actions-conda-badge]: https://github.com/pybind/cmake_example/workflows/Conda/badge.svg +[actions-pip-link]: https://github.com/pybind/cmake_example/actions?query=workflow%3A%22Pip +[actions-pip-badge]: https://github.com/pybind/cmake_example/workflows/Pip/badge.svg +[actions-wheels-link]: https://github.com/pybind/cmake_example/actions?query=workflow%3AWheels +[actions-wheels-badge]: https://github.com/pybind/cmake_example/workflows/Wheels/badge.svg +[appveyor-link]: https://ci.appveyor.com/project/dean0x7d/cmake-example/branch/master +[appveyor-badge]: https://ci.appveyor.com/api/projects/status/57nnxfm4subeug43/branch/master?svg=true + +An example [pybind11](https://github.com/pybind/pybind11) module built with a +CMake-based build system. This is useful for C++ codebases that have an +existing CMake project structure. This is in many cases superseded by +[`scikit_build_example`](https://github.com/pybind/scikit_build_example), which uses +[scikit-build][], a tool from the makers of CMake designed to allow Python +packages to be driven from CMake. However, there are still cases where you +might want full control over the CMake run; and both of these approaches have +some trade-offs not present in a pure setuptools build (see +[`python_example`](https://github.com/pybind/python_example)). Python 3.6+ required; +see the commit history for older versions of Python. + +## Prerequisites + +* A compiler with C++11 support +* Pip 10+ or CMake >= 3.4 (or 3.8+ on Windows, which was the first version to support VS 2015) +* Ninja or Pip 10+ + + +## Installation + +Just clone this repository and pip install. Note the `--recursive` option which is +needed for the pybind11 submodule: + +```bash +git clone --recursive https://github.com/pybind/cmake_example.git +pip install ./cmake_example +``` + +With the `setup.py` file included in this example, the `pip install` command will +invoke CMake and build the pybind11 module as specified in `CMakeLists.txt`. + + + +## Building the documentation + +Documentation for the example project is generated using Sphinx. Sphinx has the +ability to automatically inspect the signatures and documentation strings in +the extension module to generate beautiful documentation in a variety formats. +The following command generates HTML-based reference documentation; for other +formats please refer to the Sphinx manual: + + - `cd cmake_example/docs` + - `make html` + + +## License + +Pybind11 is provided under a BSD-style license that can be found in the LICENSE +file. By using, distributing, or contributing to this project, you agree to the +terms and conditions of this license. + + +## Test call + +```python +import cubao_cmake_example +cubao_cmake_example.add(1, 2) +``` + +[`cibuildwheel`]: https://cibuildwheel.readthedocs.io +[FAQ]: http://pybind11.rtfd.io/en/latest/faq.html#working-with-ancient-visual-studio-2009-builds-on-windows +[vs2015_runtime]: https://www.microsoft.com/en-us/download/details.aspx?id=48145 +[scikit-build]: https://scikit-build.readthedocs.io/en/latest/ diff --git a/cubao_cmake_example/__init__.py b/cubao_cmake_example/__init__.py new file mode 100644 index 0000000..87bbf8a --- /dev/null +++ b/cubao_cmake_example/__init__.py @@ -0,0 +1,10 @@ +from pybind11_cubao_cmake_example import * # noqa: F403 +from pybind11_cubao_cmake_example import __version__ # noqa: F401 + + +def pure_python_func( + arg1: int = 42, + arg2: float = 3.14, + arg3: str = "you shall not pass", +) -> str: + return f"int: {arg1}, float: {arg2}, str: {arg3}" diff --git a/cubao_cmake_example/__main__.py b/cubao_cmake_example/__main__.py new file mode 100644 index 0000000..5f89a74 --- /dev/null +++ b/cubao_cmake_example/__main__.py @@ -0,0 +1,7 @@ +from cubao_cmake_example import * # noqa: F403 + +if __name__ == "__main__": + import fire + + fire.core.Display = lambda lines, out: print(*lines, file=out) # no pager for fire + fire.Fire() diff --git a/docs/about/license.md b/docs/about/license.md new file mode 100644 index 0000000..c22fa08 --- /dev/null +++ b/docs/about/license.md @@ -0,0 +1,14 @@ +# License + +The legal stuff. + +--- + +## Included projects + +* [pybind/pybind11](https://github.com/pybind/pybind11/blob/master/LICENSE) (BSD) +* [pybind/cmake_example](https://github.com/pybind/cmake_example/blob/master/LICENSE) (BSD) + +## License (BSD) + +See . diff --git a/docs/about/release-notes.md b/docs/about/release-notes.md new file mode 100644 index 0000000..e5bda31 --- /dev/null +++ b/docs/about/release-notes.md @@ -0,0 +1,30 @@ +# Release Notes + +--- + +## Upgrading + +To upgrade `cubao_cmake_example` to the latest version, use pip: + +```bash +pip install -U cubao_cmake_example +``` + +## Version 0.0.3 (2023-03-04) + +* Add macOS arm64 (for m1 chip) + +## Version 0.0.2 (2023-03-03) + +* Release to pypi on GitHub workflow + +## Version 0.0.1 (2022-10-14) + +* First release to pypi + +--- + +You can also checkout releases on: + +- GitHub: +- PyPi: diff --git a/docs/code.md b/docs/code.md new file mode 100644 index 0000000..c8a0bec --- /dev/null +++ b/docs/code.md @@ -0,0 +1,44 @@ +# code + +GitHub: [cmake-example](https://github.com/cubao/cmake_example) + +## CMakeLists.txt + +```cmake +{% + include-markdown "../CMakeLists.txt" + comments=false +%} +``` + +## src/main.cpp + +```cpp +{% + include-markdown "../src/main.cpp" + comments=false +%} +``` + +## setup.py + +
+ code + +```python +{% + include-markdown "../setup.py" + comments=false +%} +``` + +
+ +## tests/test_basic.py + +```cpp +{% + include-markdown "../tests/test_basic.py" + comments=false +%} +``` diff --git a/docs/css/extra.css b/docs/css/extra.css new file mode 100644 index 0000000..a5242a9 --- /dev/null +++ b/docs/css/extra.css @@ -0,0 +1 @@ +/* add your css here */ diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..fe2d7ce --- /dev/null +++ b/docs/index.md @@ -0,0 +1,25 @@ +# cmake_example + +Example pybind11 module built with a CMake-based build system. + +It's a [fork](https://github.com/cubao/cmake_example) of [pybind/cmake_example](https://github.com/pybind/cmake_example) +with some modifications: + +- integrate python code along side c++ pybind code +- release to pypi +- use markdown document + +{% + include-markdown "../README.md" + start="" + end="" +%} + + + +## Related + +See more projects at [cubao](https://cubao.readthedocs.io). diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 0000000..3d69119 --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,4 @@ +mkdocs==1.5.2 +mkdocs-include-markdown-plugin==4.0.3 +mkdocs-material==9.1.21 +mkdocs-material-extensions==1.1.1 diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 0000000..3e7e733 --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,19 @@ +site_name: cmake-example +site_url: https://cmake-example.readthedocs.io +site_description: Example pybind11 module built with a CMake-based build system. +site_author: district10 + +repo_url: https://github.com/cubao/cmake_example +edit_uri: blob/master/docs/ + +theme: material +nav: + - Home: index.md + - Code: code.md + - About: + - Release Notes: about/release-notes.md + - License: about/license.md +plugins: + - include-markdown + +copyright: Copyright © 2023 cubao team. diff --git a/pybind11 b/pybind11 new file mode 160000 index 0000000..8a099e4 --- /dev/null +++ b/pybind11 @@ -0,0 +1 @@ +Subproject commit 8a099e44b3d5f85b20f05828d919d2332a8de841 diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..428cde0 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,48 @@ +[build-system] +requires = [ + "setuptools>=42", + "wheel", + "ninja", + "cmake>=3.12", +] +build-backend = "setuptools.build_meta" + +[tool.mypy] +files = "setup.py" +python_version = "3.7" +strict = true +show_error_codes = true +enable_error_code = ["ignore-without-code", "redundant-expr", "truthy-bool"] +warn_unreachable = true + +[[tool.mypy.overrides]] +module = ["ninja"] +ignore_missing_imports = true + +[tool.pytest.ini_options] +minversion = "6.0" +addopts = ["-ra", "--showlocals", "--strict-markers", "--strict-config"] +xfail_strict = true +filterwarnings = ["error"] +testpaths = ["tests"] + +[tool.cibuildwheel] +test-command = "pytest {project}/tests" +test-extras = ["test"] +test-skip = ["*universal2:arm64"] +# Setuptools bug causes collision between pypy and cpython artifacts +before-build = "rm -rf {project}/build" + +[tool.ruff] +extend-select = [ + "B", # flake8-bugbear + "B904", + "I", # isort + "PGH", # pygrep-hooks + "RUF", # Ruff-specific + "UP", # pyupgrade +] +extend-ignore = [ + "E501", # Line too long +] +target-version = "py37" diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..68bce96 --- /dev/null +++ b/setup.py @@ -0,0 +1,144 @@ +import os +import re +import subprocess +import sys +from pathlib import Path + +from setuptools import Extension, find_packages, setup +from setuptools.command.build_ext import build_ext + +# Convert distutils Windows platform specifiers to CMake -A arguments +PLAT_TO_CMAKE = { + "win32": "Win32", + "win-amd64": "x64", + "win-arm32": "ARM", + "win-arm64": "ARM64", +} + + +# A CMakeExtension needs a sourcedir instead of a file list. +# The name must be the _single_ output extension from the CMake build. +# If you need multiple extensions, see scikit-build. +class CMakeExtension(Extension): + def __init__(self, name: str, sourcedir: str = "") -> None: + super().__init__(name, sources=[]) + self.sourcedir = os.fspath(Path(sourcedir).resolve()) + + +class CMakeBuild(build_ext): + def build_extension(self, ext: CMakeExtension) -> None: + # Must be in this form due to bug in .resolve() only fixed in Python 3.10+ + ext_fullpath = Path.cwd() / self.get_ext_fullpath(ext.name) + extdir = ext_fullpath.parent.resolve() + + # Using this requires trailing slash for auto-detection & inclusion of + # auxiliary "native" libs + + debug = int(os.environ.get("DEBUG", 0)) if self.debug is None else self.debug + cfg = "Debug" if debug else "Release" + + # CMake lets you override the generator - we need to check this. + # Can be set with Conda-Build, for example. + cmake_generator = os.environ.get("CMAKE_GENERATOR", "") + + # Set Python_EXECUTABLE instead if you use PYBIND11_FINDPYTHON + # EXAMPLE_VERSION_INFO shows you how to pass a value into the C++ code + # from Python. + cmake_args = [ + f"-DCMAKE_LIBRARY_OUTPUT_DIRECTORY={extdir}{os.sep}", + f"-DPYTHON_EXECUTABLE={sys.executable}", + f"-DCMAKE_BUILD_TYPE={cfg}", # not used on MSVC, but no harm + ] + build_args = [] + # Adding CMake arguments set as environment variable + # (needed e.g. to build for ARM OSx on conda-forge) + if "CMAKE_ARGS" in os.environ: + cmake_args += [item for item in os.environ["CMAKE_ARGS"].split(" ") if item] + + # In this example, we pass in the version to C++. You might not need to. + cmake_args += [f"-DEXAMPLE_VERSION_INFO={self.distribution.get_version()}"] + + if self.compiler.compiler_type != "msvc": + # Using Ninja-build since it a) is available as a wheel and b) + # multithreads automatically. MSVC would require all variables be + # exported for Ninja to pick it up, which is a little tricky to do. + # Users can override the generator with CMAKE_GENERATOR in CMake + # 3.15+. + if not cmake_generator or cmake_generator == "Ninja": + try: + import ninja + + ninja_executable_path = Path(ninja.BIN_DIR) / "ninja" + cmake_args += [ + "-GNinja", + f"-DCMAKE_MAKE_PROGRAM:FILEPATH={ninja_executable_path}", + ] + except ImportError: + pass + + else: + # Single config generators are handled "normally" + single_config = any(x in cmake_generator for x in {"NMake", "Ninja"}) + + # CMake allows an arch-in-generator style for backward compatibility + contains_arch = any(x in cmake_generator for x in {"ARM", "Win64"}) + + # Specify the arch if using MSVC generator, but only if it doesn't + # contain a backward-compatibility arch spec already in the + # generator name. + if not single_config and not contains_arch: + cmake_args += ["-A", PLAT_TO_CMAKE[self.plat_name]] + + # Multi-config generators have a different way to specify configs + if not single_config: + cmake_args += [ + f"-DCMAKE_LIBRARY_OUTPUT_DIRECTORY_{cfg.upper()}={extdir}" + ] + build_args += ["--config", cfg] + + if sys.platform.startswith("darwin"): + # Cross-compile support for macOS - respect ARCHFLAGS if set + archs = re.findall(r"-arch (\S+)", os.environ.get("ARCHFLAGS", "")) + if archs: + cmake_args += ["-DCMAKE_OSX_ARCHITECTURES={}".format(";".join(archs))] + + # Set CMAKE_BUILD_PARALLEL_LEVEL to control the parallel build level + # across all generators. + if "CMAKE_BUILD_PARALLEL_LEVEL" not in os.environ: + # self.parallel is a Python 3 only way to set parallel jobs by hand + # using -j in the build_ext call, not supported by pip or PyPA-build. + if hasattr(self, "parallel") and self.parallel: + # CMake 3.12+ only. + build_args += [f"-j{self.parallel}"] + + build_temp = Path(self.build_temp) / ext.name + if not build_temp.exists(): + build_temp.mkdir(parents=True) + + subprocess.run( + ["cmake", ext.sourcedir, *cmake_args], cwd=build_temp, check=True + ) + subprocess.run( + ["cmake", "--build", ".", *build_args], cwd=build_temp, check=True + ) + + +# The information here can also be placed in setup.cfg - better separation of +# logic and declaration, and simpler if you include description/version in a file. +setup( + name="cubao_cmake_example", + version="0.0.3", + author="tzx", + author_email="dvorak4tzx@gmail.com", + url="https://cmake-example.readthedocs.io", + description="A test project using pybind11 and CMake", + long_description=open("README.md", encoding="utf-8").read(), + long_description_content_type="text/markdown", + packages=find_packages(), + ext_modules=[CMakeExtension("cubao_cmake_example")], + cmdclass={"build_ext": CMakeBuild}, + zip_safe=False, + python_requires=">=3.7", + install_requires=["fire"], + extras_require={"test": ["pytest>=6.0"]}, +) diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..60309f3 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,43 @@ +#include + +#define STRINGIFY(x) #x +#define MACRO_STRINGIFY(x) STRINGIFY(x) + +int add(int i, int j) { return i + j; } + +namespace py = pybind11; + +PYBIND11_MODULE(pybind11_cubao_cmake_example, m) +{ + m.doc() = R"pbdoc( + Pybind11 example plugin + ----------------------- + + .. currentmodule:: cmake_example + + .. autosummary:: + :toctree: _generate + + add + subtract + )pbdoc"; + + m.def("add", &add, R"pbdoc( + Add two numbers + + Some other explanation about the add function. + )pbdoc"); + + m.def( + "subtract", [](int i, int j) { return i - j; }, R"pbdoc( + Subtract two numbers + + Some other explanation about the subtract function. + )pbdoc"); + +#ifdef VERSION_INFO + m.attr("__version__") = MACRO_STRINGIFY(VERSION_INFO); +#else + m.attr("__version__") = "dev"; +#endif +} diff --git a/tests/test_basic.py b/tests/test_basic.py new file mode 100644 index 0000000..c1202a5 --- /dev/null +++ b/tests/test_basic.py @@ -0,0 +1,7 @@ +import cubao_cmake_example as m + + +def test_main(): + assert m.__version__ == "0.0.3" + assert m.add(1, 2) == 3 + assert m.subtract(1, 2) == -1