├── .bumpversion.cfg ├── .gitattributes ├── .github ├── dependabot.yml └── workflows │ ├── ci.yml │ └── python-publish.yml ├── .gitignore ├── .readthedocs.yaml ├── CHANGELOG.md ├── LICENSE ├── MANIFEST.in ├── README.md ├── docs ├── Makefile ├── README.md ├── building_wheels.md ├── conf.py ├── index.md ├── make.bat ├── reference.rst ├── requirements.txt └── setuppy_tutorial.md ├── emscripten ├── .gitignore ├── .ruff.toml ├── _sysconfigdata__emscripten_wasm32-emscripten.py ├── emcc_wrapper.py ├── pyo3_config.ini └── runner.js ├── examples ├── hello-world-script │ ├── Cargo.lock │ ├── Cargo.toml │ ├── MANIFEST.in │ ├── noxfile.py │ ├── pyproject.toml │ ├── pytest.ini │ ├── python │ │ └── hello_world │ │ │ └── __init__.py │ └── rust │ │ └── main.rs ├── hello-world-setuppy │ ├── Cargo.lock │ ├── Cargo.toml │ ├── MANIFEST.in │ ├── noxfile.py │ ├── pyproject.toml │ ├── pytest.ini │ ├── python │ │ └── hello_world │ │ │ └── __init__.py │ ├── rust │ │ └── lib.rs │ └── setup.py ├── hello-world │ ├── Cargo.lock │ ├── Cargo.toml │ ├── MANIFEST.in │ ├── noxfile.py │ ├── pyproject.toml │ ├── python │ │ └── hello_world │ │ │ ├── __init__.py │ │ │ └── sum_cli.py │ ├── rust │ │ ├── lib.rs │ │ └── print_hello.rs │ └── tests │ │ └── test_hello_world.py ├── html-py-ever │ ├── .github │ │ └── workflows │ │ │ └── upload.yml │ ├── Cargo.lock │ ├── Cargo.toml │ ├── MANIFEST.in │ ├── README.md │ ├── build-wheels.sh │ ├── noxfile.py │ ├── pyproject.toml │ ├── pytest.ini │ ├── python │ │ └── html_py_ever │ │ │ └── __init__.py │ ├── requirements.txt │ ├── rust │ │ ├── lib.rs │ │ └── main.rs │ └── tests │ │ ├── conftest.py │ │ ├── empty.html │ │ ├── monty-python.html │ │ ├── python.html │ │ ├── run_all.py │ │ ├── rust.html │ │ ├── small.html │ │ ├── test_parsing.py │ │ └── test_selector.py ├── namespace_package │ ├── .cargo │ │ └── config.toml │ ├── Cargo.lock │ ├── Cargo.toml │ ├── Cross.toml │ ├── Dockerfile │ ├── MANIFEST.in │ ├── noxfile.py │ ├── pyproject.toml │ ├── python │ │ └── namespace_package │ │ │ └── python │ │ │ └── __init__.py │ ├── rust │ │ └── lib.rs │ └── tests │ │ └── test_namespace_package.py └── rust_with_cffi │ ├── Cargo.lock │ ├── Cargo.toml │ ├── MANIFEST.in │ ├── cffi_module.py │ ├── noxfile.py │ ├── pyproject.toml │ ├── pytest.ini │ ├── python │ └── rust_with_cffi │ │ └── __init__.py │ ├── rust │ └── lib.rs │ ├── setup.py │ └── tests │ └── test_rust_with_cffi.py ├── mypy.ini ├── noxfile.py ├── pyproject.toml ├── setuptools_rust ├── __init__.py ├── _utils.py ├── build.py ├── clean.py ├── command.py ├── extension.py ├── py.typed ├── rustc_info.py ├── setuptools_ext.py └── version.py └── tests ├── test_build.py └── test_extension.py /.bumpversion.cfg: -------------------------------------------------------------------------------- 1 | [bumpversion] 2 | commit = True 3 | tag = False 4 | current_version = 1.11.1 5 | message = release: {new_version} 6 | 7 | [bumpversion:file:pyproject.toml] 8 | 9 | [bumpversion:file:setuptools_rust/version.py] 10 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | html-py-ever/test/*.html linguist-vendored=true 2 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | 4 | - package-ecosystem: "cargo" 5 | directories: 6 | - "examples/*" 7 | schedule: 8 | interval: "monthly" 9 | groups: 10 | deps: 11 | patterns: 12 | - "*" 13 | 14 | - package-ecosystem: "pip" 15 | directory: "docs" 16 | schedule: 17 | interval: "monthly" 18 | groups: 19 | docs-dependencies: 20 | patterns: 21 | - "*" 22 | 23 | - package-ecosystem: "github-actions" 24 | directory: "/" 25 | schedule: 26 | interval: "monthly" 27 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | 8 | concurrency: 9 | group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.event.pull_request.number || github.sha }} 10 | cancel-in-progress: true 11 | 12 | # Setting a fixed target dir ensures that all builds of examples re-use the same common dependencies 13 | env: 14 | CARGO_TARGET_DIR: ${{ github.workspace }}/target 15 | 16 | jobs: 17 | ruff: 18 | runs-on: "ubuntu-latest" 19 | steps: 20 | - uses: actions/checkout@v4 21 | 22 | - name: Set up Python 23 | uses: actions/setup-python@v5 24 | with: 25 | python-version: "3.x" 26 | 27 | - run: pip install nox 28 | 29 | - run: nox -s ruff 30 | 31 | mypy: 32 | runs-on: "ubuntu-latest" 33 | steps: 34 | - uses: actions/checkout@v4 35 | 36 | - name: Set up Python 37 | uses: actions/setup-python@v5 38 | with: 39 | python-version: "3.x" 40 | 41 | - run: pip install nox 42 | 43 | - run: nox -s mypy 44 | 45 | pytest: 46 | runs-on: "ubuntu-latest" 47 | steps: 48 | - uses: actions/checkout@v4 49 | 50 | - name: Set up Python 51 | uses: actions/setup-python@v5 52 | with: 53 | python-version: "3.x" 54 | 55 | - name: Install Rust toolchain 56 | uses: dtolnay/rust-toolchain@stable 57 | 58 | - run: pip install nox 59 | 60 | - run: nox -s test 61 | 62 | build: 63 | name: ${{ matrix.python-version }} ${{ matrix.platform.os }}-${{ matrix.platform.python-architecture }} 64 | runs-on: ${{ matrix.platform.os }} 65 | strategy: 66 | # If one platform fails, allow the rest to keep testing if `CI-no-fail-fast` label is present 67 | fail-fast: ${{ !contains(github.event.pull_request.labels.*.name, 'CI-no-fail-fast') }} 68 | matrix: 69 | python-version: 70 | ["3.9", "3.10", "3.11", "3.12", "3.13", "3.13t", pypy-3.10, pypy-3.11] 71 | platform: 72 | [ 73 | { 74 | os: "macos-latest", 75 | python-architecture: "arm64", 76 | rust-target: "aarch64-apple-darwin", 77 | }, 78 | { 79 | os: "ubuntu-latest", 80 | python-architecture: "x64", 81 | rust-target: "x86_64-unknown-linux-gnu", 82 | }, 83 | { 84 | os: "windows-latest", 85 | python-architecture: "x64", 86 | rust-target: "x86_64-pc-windows-msvc", 87 | }, 88 | ] 89 | include: 90 | # Just test one x86 Windows Python for simplicity 91 | - python-version: 3.12 92 | platform: 93 | { 94 | os: "windows-latest", 95 | python-architecture: "x86", 96 | rust-target: "i686-pc-windows-msvc", 97 | } 98 | # Just test one x64 macOS Python for simplicity 99 | - python-version: 3.12 100 | platform: 101 | { 102 | os: "macos-13", 103 | python-architecture: "x64", 104 | rust-target: "x86_64-apple-darwin", 105 | } 106 | # Just test one x64 macOS Python for simplicity 107 | exclude: 108 | # macOS arm doesn't have Python builds before 3.10 109 | - python-version: 3.9 110 | platform: 111 | { 112 | os: "macos-latest", 113 | python-architecture: "arm64", 114 | rust-target: "aarch64-apple-darwin", 115 | } 116 | 117 | env: 118 | SETUPTOOLS_RUST_CARGO_PROFILE: dev 119 | steps: 120 | - uses: actions/checkout@v4 121 | 122 | - name: Set up Python ${{ matrix.python-version }} 123 | uses: actions/setup-python@v5 124 | with: 125 | python-version: ${{ matrix.python-version }} 126 | architecture: ${{ matrix.platform.python-architecture }} 127 | allow-prereleases: true 128 | 129 | - name: Install Rust toolchain 130 | uses: dtolnay/rust-toolchain@stable 131 | with: 132 | targets: ${{ matrix.platform.rust-target }} 133 | 134 | - name: Install test dependencies 135 | run: pip install --upgrade nox 136 | 137 | - name: Build package 138 | run: pip install -e . 139 | 140 | - name: Test examples 141 | shell: bash 142 | env: 143 | PYTHON: ${{ matrix.python-version }} 144 | run: nox -s test-examples 145 | 146 | - name: Test macOS universal2 147 | if: ${{ startsWith(matrix.platform.os, 'macos') && !startsWith(matrix.python-version, 'pypy') }} 148 | shell: bash 149 | env: 150 | MACOSX_DEPLOYMENT_TARGET: "10.9" 151 | ARCHFLAGS: -arch x86_64 -arch arm64 152 | run: | 153 | rustup target add aarch64-apple-darwin 154 | rustup target add x86_64-apple-darwin 155 | cd examples/namespace_package 156 | pip install build 'setuptools>=70.1' 157 | python -m build --no-isolation 158 | ls -l dist/ 159 | pip install --force-reinstall dist/namespace_package*_universal2.whl 160 | cd - 161 | python -c "from namespace_package import rust; assert rust.rust_func() == 14" 162 | python -c "from namespace_package import python; assert python.python_func() == 15" 163 | 164 | - name: Test sdist vendor Rust crates 165 | shell: bash 166 | run: nox -s test-sdist-vendor 167 | env: 168 | CARGO_BUILD_TARGET: ${{ matrix.platform.rust-target }} 169 | 170 | test-abi3: 171 | runs-on: ${{ matrix.os }} 172 | strategy: 173 | # If one platform fails, allow the rest to keep testing if `CI-no-fail-fast` label is present 174 | fail-fast: ${{ !contains(github.event.pull_request.labels.*.name, 'CI-no-fail-fast') }} 175 | matrix: 176 | os: [ubuntu-latest, macos-latest, windows-latest] 177 | steps: 178 | - uses: actions/checkout@v4 179 | - name: Setup python 180 | uses: actions/setup-python@v5 181 | with: 182 | python-version: 3.9 183 | 184 | - uses: dtolnay/rust-toolchain@stable 185 | 186 | - name: Build package 187 | run: pip install -e . 188 | 189 | - name: Build an abi3 wheel 190 | shell: bash 191 | env: 192 | # https://github.com/actions/setup-python/issues/26 193 | MACOSX_DEPLOYMENT_TARGET: 10.9 194 | DIST_EXTRA_CONFIG: /tmp/build-opts.cfg 195 | run: | 196 | set -e 197 | cd examples/rust_with_cffi/ 198 | python --version 199 | pip install -U build cffi 'setuptools>=70.1' 200 | echo -e "[bdist_wheel]\npy_limited_api=cp39" > $DIST_EXTRA_CONFIG 201 | python -m build --no-isolation 202 | ls -la dist/ 203 | 204 | # Now we switch to a differnet Python version and ensure we can install 205 | # the wheel we just built. 206 | - name: Setup python 207 | uses: actions/setup-python@v5 208 | with: 209 | python-version: "3.10" 210 | 211 | - name: Install abi3 wheel and run tests 212 | shell: bash 213 | run: | 214 | set -e 215 | cd examples/ 216 | python --version 217 | pip install rust_with_cffi/dist/rust_with_cffi*.whl 218 | python -c "from rust_with_cffi import rust; assert rust.rust_func() == 14" 219 | python -c "from rust_with_cffi.cffi import lib; assert lib.cffi_func() == 15" 220 | 221 | - name: Run abi3audit 222 | shell: bash 223 | run: | 224 | set -e 225 | pip install abi3audit 226 | cd examples/ 227 | abi3audit rust_with_cffi/dist/rust_with_cffi*.whl 228 | 229 | test-crossenv: 230 | runs-on: ubuntu-latest 231 | strategy: 232 | # If one platform fails, allow the rest to keep testing if `CI-no-fail-fast` label is present 233 | fail-fast: ${{ !contains(github.event.pull_request.labels.*.name, 'CI-no-fail-fast') }} 234 | matrix: 235 | platform: [{ arch: "aarch64" }, { arch: "armv7" }] 236 | steps: 237 | - uses: actions/checkout@v4 238 | - name: Set up Python 239 | uses: actions/setup-python@v5 240 | with: 241 | python-version: "3.x" 242 | - uses: docker/setup-qemu-action@v3 243 | - run: pip install nox 244 | - run: nox -s test-crossenv -- ${{ matrix.platform.arch }} 245 | 246 | test-cross: 247 | runs-on: ubuntu-latest 248 | steps: 249 | - uses: actions/checkout@v4 250 | - name: Setup python 251 | uses: actions/setup-python@v5 252 | with: 253 | python-version: "3.10" # must match the ubuntu version in the install, and also the STANDALONE_PYTHON_VERSION below 254 | - uses: dtolnay/rust-toolchain@stable 255 | - name: Install cross 256 | uses: taiki-e/install-action@v2 257 | with: 258 | tool: cross 259 | - name: Build package 260 | run: pip install -e . 261 | - name: Build wheel using cross 262 | shell: bash 263 | env: 264 | CARGO: cross 265 | CARGO_BUILD_TARGET: aarch64-unknown-linux-gnu 266 | PYO3_CROSS_LIB_DIR: /opt/python/cp310-cp310/lib 267 | DIST_EXTRA_CONFIG: /tmp/build-opts.cfg 268 | run: | 269 | cd examples/namespace_package 270 | docker build \ 271 | --build-arg STANDALONE_PYTHON_VERSION=3.10.15 \ 272 | --build-arg STANDALONE_PYTHON_RELEASE=20241016 \ 273 | -t cross-pyo3:aarch64-unknown-linux-gnu \ 274 | . 275 | python -m pip install build wheel 276 | echo -e "[bdist_wheel]\nplat_name=manylinux2014_aarch64" > $DIST_EXTRA_CONFIG 277 | python -m build --no-isolation 278 | ls -la dist/ 279 | unzip -l dist/*.whl # debug all files inside wheel file 280 | - uses: uraimo/run-on-arch-action@v3.0.1 281 | name: Install built wheel 282 | with: 283 | arch: aarch64 284 | distro: ubuntu22.04 285 | dockerRunArgs: | 286 | --volume "${PWD}/examples/namespace_package:/io" 287 | install: | 288 | apt-get update 289 | apt-get install -y --no-install-recommends python3 python3-venv python3-pip 290 | pip3 install -U pip 291 | run: | 292 | python3 -m venv .venv 293 | source .venv/bin/activate 294 | pip install namespace_package --no-index --find-links /io/dist/ --force-reinstall 295 | python -c "from namespace_package import rust; assert rust.rust_func() == 14" 296 | python -c "from namespace_package import python; assert python.python_func() == 15" 297 | 298 | test-zigbuild: 299 | runs-on: ubuntu-latest 300 | steps: 301 | - uses: actions/checkout@v4 302 | - name: Setup python 303 | uses: actions/setup-python@v5 304 | with: 305 | python-version: "3.10" # must match the ubuntu version in the install, and also the version copied out of the docker image below 306 | - uses: dtolnay/rust-toolchain@stable 307 | with: 308 | targets: aarch64-unknown-linux-gnu 309 | - name: Install cargo-zigbuild 310 | run: | 311 | python3 -m pip install cargo-zigbuild 312 | - name: Build package 313 | run: pip install -e . 314 | - name: Build wheel using cargo-zigbuild 315 | shell: bash 316 | env: 317 | CARGO: cargo-zigbuild 318 | CARGO_BUILD_TARGET: aarch64-unknown-linux-gnu 319 | PYO3_CROSS_LIB_DIR: /opt/python/cp310-cp310/lib 320 | DIST_EXTRA_CONFIG: /tmp/build-opts.cfg 321 | run: | 322 | mkdir -p $PYO3_CROSS_LIB_DIR 323 | docker cp -L $(docker create --rm quay.io/pypa/manylinux2014_aarch64:latest):/opt/python/cp310-cp310 /opt/python 324 | cd examples/namespace_package 325 | python -m pip install build wheel 326 | echo -e "[bdist_wheel]\nplat_name=manylinux2014_aarch64" > $DIST_EXTRA_CONFIG 327 | python -m build --no-isolation 328 | ls -la dist/ 329 | unzip -l dist/*.whl # debug all files inside wheel file 330 | - uses: uraimo/run-on-arch-action@v3.0.1 331 | name: Install built wheel 332 | with: 333 | arch: aarch64 334 | distro: ubuntu22.04 335 | dockerRunArgs: | 336 | --volume "${PWD}/examples/namespace_package:/io" 337 | install: | 338 | apt-get update 339 | apt-get install -y --no-install-recommends python3 python3-venv python3-pip 340 | run: | 341 | python3 -m venv .venv 342 | source .venv/bin/activate 343 | pip install namespace_package --no-index --find-links /io/dist/ --force-reinstall 344 | python -c "from namespace_package import rust; assert rust.rust_func() == 14" 345 | python -c "from namespace_package import python; assert python.python_func() == 15" 346 | 347 | test-cibuildwheel: 348 | runs-on: macos-latest 349 | steps: 350 | - uses: actions/checkout@v4 351 | - uses: dtolnay/rust-toolchain@stable 352 | with: 353 | targets: aarch64-apple-darwin,x86_64-apple-darwin 354 | - uses: pypa/cibuildwheel@v2.23.3 355 | env: 356 | CIBW_BUILD: cp39-* 357 | CIBW_BEFORE_BUILD: > 358 | pip install -U 'pip>=23.2.1' 'setuptools>=70.1.0' 'auditwheel>=5.4.0' 359 | && pip install -e . 360 | && pip list 361 | CIBW_ARCHS_MACOS: "x86_64 universal2 arm64" 362 | CIBW_BUILD_VERBOSITY: 3 363 | CIBW_BUILD_FRONTEND: "build; args: --no-isolation" 364 | MACOSX_DEPLOYMENT_TARGET: 10.12 365 | with: 366 | package-dir: examples/namespace_package 367 | 368 | test-mingw: 369 | runs-on: windows-latest 370 | name: ${{ matrix.python-version }} mingw-${{ matrix.arch }} 371 | strategy: 372 | fail-fast: false 373 | matrix: 374 | include: 375 | [ 376 | { 377 | msystem: MINGW64, 378 | arch: x86_64, 379 | path: mingw64, 380 | rust_target: x86_64-pc-windows-gnu, 381 | }, 382 | { 383 | msystem: MINGW32, 384 | arch: i686, 385 | path: mingw32, 386 | rust_target: i686-pc-windows-gnu, 387 | }, 388 | ] 389 | steps: 390 | - uses: actions/checkout@v4 391 | - name: Install MSys2 and dependencies 392 | uses: msys2/setup-msys2@v2 393 | with: 394 | update: true 395 | msystem: ${{ matrix.msystem }} 396 | install: >- 397 | git 398 | mingw-w64-${{ matrix.arch }}-python 399 | mingw-w64-${{ matrix.arch }}-python-pip 400 | mingw-w64-${{ matrix.arch }}-openssl 401 | mingw-w64-${{ matrix.arch }}-toolchain 402 | 403 | - uses: dtolnay/rust-toolchain@master 404 | with: 405 | toolchain: stable-${{ matrix.rust_target }} 406 | 407 | - name: Install test dependencies 408 | shell: msys2 {0} 409 | run: python -m pip install --upgrade pip nox 410 | 411 | - name: Create libpython symlink 412 | shell: msys2 {0} 413 | run: | 414 | PYTHON_MINOR_VER=$(python -c "import sys; print(sys.version_info.minor)") 415 | ln -s /${{ matrix.path }}/lib/libpython3.${PYTHON_MINOR_VER}.dll.a /${{ matrix.path }}/lib/libpython3${PYTHON_MINOR_VER}.dll.a 416 | 417 | - name: Test examples 418 | shell: msys2 {0} 419 | run: PATH="$PATH:/c/Users/runneradmin/.cargo/bin" nox -s test-examples 420 | 421 | test-emscripten: 422 | name: Test Emscripten 423 | runs-on: ubuntu-latest 424 | steps: 425 | - uses: actions/checkout@v4 426 | - uses: actions/setup-node@v4 427 | with: 428 | node-version: 18 429 | - run: | 430 | PYODIDE_VERSION=0.21.0 431 | 432 | cd emscripten 433 | npm i pyodide@0.21.0 434 | cd node_modules/pyodide/ 435 | npx -y prettier -w pyodide.asm.js 436 | EMSCRIPTEN_VERSION=$(node -p "require('./repodata.json').info.platform.split('_').slice(1).join('.')") 437 | PYTHON_VERSION=3.10 438 | 439 | echo "PYODIDE_VERSION=$PYODIDE_VERSION" >> $GITHUB_ENV 440 | echo "EMSCRIPTEN_VERSION=$EMSCRIPTEN_VERSION" >> $GITHUB_ENV 441 | echo "PYTHON_VERSION=$PYTHON_VERSION" >> $GITHUB_ENV 442 | echo "ORIG_PATH=$PATH" >> $GITHUB_ENV 443 | - uses: dtolnay/rust-toolchain@nightly 444 | with: 445 | components: rust-src 446 | targets: wasm32-unknown-emscripten 447 | - uses: mymindstorm/setup-emsdk@v14 448 | with: 449 | version: ${{env.EMSCRIPTEN_VERSION}} 450 | actions-cache-folder: emsdk-cache 451 | - uses: actions/setup-python@v5 452 | id: setup-python 453 | with: 454 | python-version: ${{env.PYTHON_VERSION}} 455 | - run: pip install nox 456 | - uses: actions/cache@v4 457 | with: 458 | path: | 459 | tests/pyodide 460 | key: ${{ hashFiles('tests/*.js') }} - ${{ hashFiles('noxfile.py') }} - ${{ steps.setup-python.outputs.python-path }} 461 | - uses: Swatinem/rust-cache@v2 462 | - name: Test 463 | run: | 464 | export PATH=$ORIG_PATH:$PATH 465 | nox -s test-examples-emscripten 466 | -------------------------------------------------------------------------------- /.github/workflows/python-publish.yml: -------------------------------------------------------------------------------- 1 | # This workflows will upload a Python Package using Twine when a release is created 2 | # For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries 3 | 4 | name: Upload Python Package 5 | 6 | on: 7 | release: 8 | types: [published] 9 | workflow_dispatch: 10 | 11 | jobs: 12 | deploy: 13 | 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - uses: actions/checkout@v4 18 | with: 19 | # Fetch all history so that setuptools_scm works correctly 20 | fetch-depth: 0 21 | - name: Set up Python 22 | uses: actions/setup-python@v5 23 | with: 24 | python-version: '3.x' 25 | - name: Install dependencies 26 | # Install build and packaging dependencies. 27 | # setuptools-scm is necessary for ensuring all files from VCS (such as 28 | # examples) are in the sdist. 29 | run: pip install -U pip build twine 30 | - name: Build and publish 31 | env: 32 | TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} 33 | TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} 34 | run: | 35 | python -m build 36 | twine upload dist/* 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.pyo 3 | *.so 4 | .eggs 5 | .nox 6 | dist 7 | build 8 | target 9 | .pytest_cache 10 | .ruff_cache 11 | *.egg-info 12 | pyo3 13 | docs/_build 14 | 15 | Cargo.lock 16 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | build: 4 | os: ubuntu-22.04 5 | tools: 6 | python: "3.11" 7 | 8 | python: 9 | install: 10 | - requirements: docs/requirements.txt 11 | - method: pip 12 | path: . 13 | 14 | sphinx: 15 | configuration: docs/conf.py 16 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 1.11.1 (2025-04-04) 4 | ### Fixed 5 | - Fix finding cargo artifacts when filenames are empty. [#521](https://github.com/PyO3/setuptools-rust/pull/521) 6 | 7 | ## 1.11.0 (2025-03-14) 8 | ### Packaging 9 | - Drop support for Python 3.8. [#479](https://github.com/PyO3/setuptools-rust/pull/479) 10 | - Support free-threaded Python. [#502](https://github.com/PyO3/setuptools-rust/pull/502) 11 | - Support adding custom env vars. [#504](https://github.com/PyO3/setuptools-rust/pull/504) 12 | 13 | ## 1.10.2 (2024-10-02) 14 | ### Fixed 15 | - Fix deprecation warning from use of `wheel.bdist_wheel`. 16 | 17 | ## 1.10.1 (2024-08-04) 18 | ### Fixed 19 | - Fix regression in 1.10.0 where editable builds would be built in release mode 20 | 21 | ## 1.10.0 (2024-08-03) 22 | ### Packaging 23 | - Extend macOS build flags to iOS, tvOS and watchOS. [#436](https://github.com/PyO3/setuptools-rust/pull/436) 24 | - Support Python 3.13. [#446](https://github.com/PyO3/setuptools-rust/pull/446) 25 | 26 | ### Changed 27 | - Add `SETUPTOOLS_RUST_PEP517_USE_BASE_PYTHON` environment variable to use the base interpreter path when running inside a virtual environment to avoid recompilation when switching between virtual environments. [#429](https://github.com/PyO3/setuptools-rust/pull/429) 28 | - Delay import of dependencies until use to avoid import errors during a partially complete install when multiple packages are installing at once. [#437](https://github.com/PyO3/setuptools-rust/pull/437) 29 | - Deprecate `--build-temp` argument to `build_rust` command (it does nothing). [#457](https://github.com/PyO3/setuptools-rust/pull/457) 30 | 31 | ## 1.9.0 (2024-02-24) 32 | ### Changed 33 | - Deprecate `py_limited_api` option to `RustExtension` in favour of always using `"auto"` to configure this from `bdist_wheel`. [#410](https://github.com/PyO3/setuptools-rust/pull/410) 34 | 35 | ## 1.8.1 (2023-10-30) 36 | ### Fixed 37 | - Fix regression in `install_extension` crashing since 1.8.0. [#380](https://github.com/PyO3/setuptools-rust/pull/380) 38 | 39 | ## 1.8.0 (2023-10-26) 40 | ### Packaging 41 | - Drop support for Python 3.7. [#357](https://github.com/PyO3/setuptools-rust/pull/357) 42 | - Remove direct imports from `pkg_resources`. [#359](https://github.com/PyO3/setuptools-rust/pull/359) 43 | 44 | ### Added 45 | - Add support for setting a custom cargo profile with the `SETUPTOOLS_RUST_CARGO_PROFILE` environment variable. [#364](https://github.com/PyO3/setuptools-rust/pull/364) 46 | 47 | ## 1.7.0 (2023-08-22) 48 | ### Packaging 49 | - Remove direct imports from `distutils`. [#336](https://github.com/PyO3/setuptools-rust/pull/336) 50 | - Include `py.typed` when packaging to denote that setuptools-rust includes type hints. [#338](https://github.com/PyO3/setuptools-rust/pull/338) 51 | 52 | ### Added 53 | - Add support for `pyproject.toml` configuration using `[tool.setuptools-rust]` options. [#348](https://github.com/PyO3/setuptools-rust/pull/348) 54 | 55 | ### Fixed 56 | - Fix `plat_name` handling in the case `bdist_wheel.plat_name` is set via configuration file (e.g., `setup.cfg`). [#352](https://github.com/PyO3/setuptools-rust/pull/352) 57 | 58 | ## 1.6.0 (2023-04-27) 59 | ### Changed 60 | - Prefer passing `--crate-type` option to cargo if "toolchain >= 1.64". [#322](https://github.com/PyO3/setuptools-rust/pull/322) 61 | 62 | ### Fixed 63 | - Fix a bug where rebuilding the library would cause any running processes using it to segfault. [#295](https://github.com/PyO3/setuptools-rust/pull/295) 64 | - Fix `setup.cfg` format for compatibility with "poetry==1.4.0". [#319](https://github.com/PyO3/setuptools-rust/pull/319) 65 | 66 | ## 1.5.2 (2022-09-19) 67 | ### Fixed 68 | - Fix regression in `dylib` build artifacts not being found since 1.5.0. [#290](https://github.com/PyO3/setuptools-rust/pull/290) 69 | - Fix regression in sdist missing examples and other supplementary files since 1.5.0. [#291](https://github.com/PyO3/setuptools-rust/pull/291) 70 | 71 | ## 1.5.1 (2022-08-14) 72 | ### Fixed 73 | - Fix regression in `get_lib_name` crashing since 1.5.0. [#280](https://github.com/PyO3/setuptools-rust/pull/280) 74 | - Fix regression in `Binding.Exec` builds with multiple executables not finding built executables since 1.5.0. [#283](https://github.com/PyO3/setuptools-rust/pull/283) 75 | 76 | ## 1.5.0 (2022-08-09) 77 | ### Added 78 | - Add support for extension modules built for wasm32-unknown-emscripten with Pyodide. [#244](https://github.com/PyO3/setuptools-rust/pull/244) 79 | 80 | ### Changed 81 | - Locate cdylib artifacts by handling messages from cargo instead of searching target dir (fixes build on MSYS2). [#267](https://github.com/PyO3/setuptools-rust/pull/267) 82 | - No longer guess cross-compile environment using `HOST_GNU_TYPE` / `BUILD_GNU_TYPE` sysconfig variables. [#269](https://github.com/PyO3/setuptools-rust/pull/269) 83 | 84 | ### Fixed 85 | - Fix RustBin build without wheel. [#273](https://github.com/PyO3/setuptools-rust/pull/273) 86 | - Fix RustBin setuptools install. [#275](https://github.com/PyO3/setuptools-rust/pull/275) 87 | 88 | ## 1.4.1 (2022-07-05) 89 | ### Fixed 90 | - Fix crash when checking Rust version. [#263](https://github.com/PyO3/setuptools-rust/pull/263) 91 | 92 | ## 1.4.0 (2022-07-05) 93 | ### Packaging 94 | - Increase minimum `setuptools` version to 62.4. [#246](https://github.com/PyO3/setuptools-rust/pull/246) 95 | 96 | ### Added 97 | - Add `cargo_manifest_args` to support locked, frozen and offline builds. [#234](https://github.com/PyO3/setuptools-rust/pull/234) 98 | - Add `RustBin` for packaging binaries in scripts data directory. [#248](https://github.com/PyO3/setuptools-rust/pull/248) 99 | 100 | ### Changed 101 | - `Exec` binding `RustExtension` with `script=True` is deprecated in favor of `RustBin`. [#248](https://github.com/PyO3/setuptools-rust/pull/248) 102 | - Errors while calling `cargo metadata` are now reported back to the user [#254](https://github.com/PyO3/setuptools-rust/pull/254) 103 | - `quiet` option will now suppress output of `cargo metadata`. [#256](https://github.com/PyO3/setuptools-rust/pull/256) 104 | - `setuptools-rust` will now match `cargo` behavior of not setting `--target` when the selected target is the rust host. [#258](https://github.com/PyO3/setuptools-rust/pull/258) 105 | - Deprecate `native` option of `RustExtension`. [#258](https://github.com/PyO3/setuptools-rust/pull/258) 106 | 107 | ### Fixed 108 | - If the sysconfig for `BLDSHARED` has no flags, `setuptools-rust` won't crash anymore. [#241](https://github.com/PyO3/setuptools-rust/pull/241) 109 | 110 | ## 1.3.0 (2022-04-26) 111 | ### Packaging 112 | - Increase minimum `setuptools` version to 58. [#222](https://github.com/PyO3/setuptools-rust/pull/222) 113 | 114 | ### Fixed 115 | - Fix crash when `python-distutils-extra` linux package is installed. [#222](https://github.com/PyO3/setuptools-rust/pull/222) 116 | - Fix sdist built with vendored dependencies on Windows having incorrect cargo config. [#223](https://github.com/PyO3/setuptools-rust/pull/223) 117 | 118 | ## 1.2.0 (2022-03-22) 119 | ### Packaging 120 | - Drop support for Python 3.6. [#209](https://github.com/PyO3/setuptools-rust/pull/209) 121 | 122 | ### Added 123 | - Add support for `kebab-case` executable names. [#205](https://github.com/PyO3/setuptools-rust/pull/205) 124 | - Add support for custom cargo profiles. [#216](https://github.com/PyO3/setuptools-rust/pull/216) 125 | 126 | ### Fixed 127 | - Fix building macOS arm64 wheel with cibuildwheel. [#217](https://github.com/PyO3/setuptools-rust/pull/217) 128 | 129 | ## 1.1.2 (2021-12-05) 130 | ### Changed 131 | - Removed dependency on `tomli` to simplify installation. [#200](https://github.com/PyO3/setuptools-rust/pull/200) 132 | - Improve error messages on invalid inputs to `rust_extensions` keyword. [#203](https://github.com/PyO3/setuptools-rust/pull/203) 133 | 134 | ## 1.1.1 (2021-12-01) 135 | ### Fixed 136 | - Fix regression from `setuptools-rust` 1.1.0 which broke builds for the `x86_64-unknown-linux-musl` target. [#194](https://github.com/PyO3/setuptools-rust/pull/194) 137 | - Fix `--target` command line option being unable to take a value. [#195](https://github.com/PyO3/setuptools-rust/pull/195) 138 | - Fix regression from `setuptools-rust` 1.0.0 which broke builds on arm64 macos conda builds. [#196](https://github.com/PyO3/setuptools-rust/pull/196) 139 | - Fix regression from `setuptools-rust` 1.1.0 which incorrectly converted library extension suffixes to the "abi3" suffix when `py_limited_api` was unspecified. [#197](https://github.com/PyO3/setuptools-rust/pull/197) 140 | 141 | ## 1.1.0 (2021-11-30) 142 | ### Added 143 | - Add support for cross-compiling using [`cross`](https://github.com/rust-embedded/cross). [#185](https://github.com/PyO3/setuptools-rust/pull/185) 144 | 145 | ### Fixed 146 | - Fix incompatibility with Python 3.6.0 using default values for NamedTuple classes. [#184](https://github.com/PyO3/setuptools-rust/pull/184) 147 | - Stop forcing the `msvc` Rust toolchain for Windows environments using the `gnu` toolchain. [#187](https://github.com/PyO3/setuptools-rust/pull/187) 148 | 149 | ## 1.0.0 (2021-11-21) 150 | ### Added 151 | - Add `--target` command line option for specifying target triple. [#136](https://github.com/PyO3/setuptools-rust/pull/136) 152 | - Add new default `"auto"` setting for `RustExtension.py_limited_api`. [#137](https://github.com/PyO3/setuptools-rust/pull/137) 153 | - Support very verbose cargo build.rs output. [#140](https://github.com/PyO3/setuptools-rust/pull/140) 154 | 155 | ### Changed 156 | - Switch to `tomli` dependency. [#174](https://github.com/PyO3/setuptools-rust/pull/174) 157 | 158 | ### Removed 159 | - Remove `test_rust` command. (`python setup.py test` is deprecated.) [#129](https://github.com/PyO3/setuptools-rust/pull/129) 160 | - Remove `check_rust` command. [#131](https://github.com/PyO3/setuptools-rust/pull/131) 161 | - Move `tomlgen_rust` command to separate `setuptools-rust-tomlgen` package. [#167](https://github.com/PyO3/setuptools-rust/pull/167) 162 | 163 | ### Fixed 164 | - Use info from sysconfig when cross-compiling. [#139](https://github.com/PyO3/setuptools-rust/pull/139) 165 | - Put Rust extension module binary under `build/lib.*` directory. [#150](https://github.com/PyO3/setuptools-rust/pull/150) 166 | - Fix `Exec` binding with console scripts. [#154](https://github.com/PyO3/setuptools-rust/pull/154) 167 | 168 | ## 0.12.1 (2021-03-11) 169 | ### Fixed 170 | - Fix some files unexpectedly missing from `sdist` command output. [#125](https://github.com/PyO3/setuptools-rust/pull/125) 171 | 172 | ## 0.12.0 (2021-03-08) 173 | ### Packaging 174 | - Bump minimum Python version to Python 3.6. 175 | 176 | ### Added 177 | - Support building x86-64 wheel on arm64 macOS machine. [#114](https://github.com/PyO3/setuptools-rust/pull/114) 178 | - Add macOS universal2 wheel building support. [#115](https://github.com/PyO3/setuptools-rust/pull/115) 179 | - Add option to cargo vendor crates into sdist. [#118](https://github.com/PyO3/setuptools-rust/pull/118) 180 | 181 | ### Changed 182 | - Respect `PYO3_PYTHON` and `PYTHON_SYS_EXECUTABLE` environment variables if set. [#96](https://github.com/PyO3/setuptools-rust/pull/96) 183 | - Add runtime dependency on setuptools >= 46.1. [#102](https://github.com/PyO3/setuptools-rust/pull/102) 184 | - Append to, rather than replace, existing `RUSTFLAGS` when building. [#103](https://github.com/PyO3/setuptools-rust/pull/103) 185 | 186 | ### Fixed 187 | - Set executable bit on shared library. [#110](https://github.com/PyO3/setuptools-rust/pull/110) 188 | - Don't require optional `wheel` dependency. [#111](https://github.com/PyO3/setuptools-rust/pull/111) 189 | - Set a more reasonable LC_ID_DYLIB entry on macOS. [#119](https://github.com/PyO3/setuptools-rust/pull/119) 190 | 191 | ## 0.11.6 (2020-12-13) 192 | 193 | - Respect `CARGO_BUILD_TARGET` environment variable if set. [#90](https://github.com/PyO3/setuptools-rust/pull/90) 194 | - Add `setuptools_rust.__version__` and require setuptools >= 46.1. [#93](https://github.com/PyO3/setuptools-rust/pull/93) 195 | 196 | ## 0.11.5 (2020-11-10) 197 | 198 | - Fix support for Python 3.5. [#86](https://github.com/PyO3/setuptools-rust/pull/86) 199 | - Fix further cases of building for 32-bit Python on 64-bit Windows. [#87](https://github.com/PyO3/setuptools-rust/pull/87) 200 | 201 | ## 0.11.4 (2020-11-03) 202 | 203 | - Fix `tomlgen` functionality on Windows. [#78](https://github.com/PyO3/setuptools-rust/pull/78) 204 | - Add support for building abi3 shared objects. [#82](https://github.com/PyO3/setuptools-rust/pull/82) 205 | 206 | ## 0.11.3 (2020-08-24) 207 | 208 | - Fix building on Linux distributions that use musl (e.g. Alpine) out of the box. [#80](https://github.com/PyO3/setuptools-rust/pull/80) 209 | 210 | ## 0.11.2 (2020-08-10) 211 | 212 | - Fix support for namespace packages. [#79](https://github.com/PyO3/setuptools-rust/pull/79) 213 | 214 | ## 0.11.1 (2020-08-07) 215 | 216 | - Fix building for 32-bit Python on 64-bit Windows. [#77](https://github.com/PyO3/setuptools-rust/pull/77) 217 | 218 | ## 0.11.0 (2020-08-06) 219 | 220 | - Remove python 2 support. [#53](https://github.com/PyO3/setuptools-rust/pull/53) 221 | - Fix compatibility with `cffi`. [#68](https://github.com/PyO3/setuptools-rust/pull/68) 222 | - Add support for pyo3 `0.12`'s `PYO3_PYTHON` setting. [#71](https://github.com/PyO3/setuptools-rust/pull/71) 223 | 224 | ## 0.10.6 (2018-11-07) 225 | 226 | - Fix tomlgen\_rust generating invalid `Cargo.toml` files. 227 | - Fix tomlgen\_rust setting wrong path in `.cargo/config`. 228 | 229 | ## 0.10.5 (2018-09-09) 230 | 231 | - Added license file [#41](https://github.com/PyO3/setuptools-rust/pull/41) 232 | 233 | ## 0.10.4 (2018-09-09) 234 | 235 | - Add `html-py-ever` example 236 | 237 | ## 0.10.3 (2018-09-06) 238 | 239 | - `path` in `RustExtension` now defaults to `Cargo.toml` 240 | 241 | ## 0.10.2 (2018-08-09) 242 | 243 | - Add `rustc_flags` and `verbose` as options 244 | - Adopted black code style 245 | - Moved changelog to markdown 246 | 247 | ## 0.10.0 (2018-05-06) 248 | 249 | - This release significantly improves performance 250 | 251 | ## 0.9.2 (2018-05-11) 252 | 253 | - Fix build\_rust crashing on Cargo.toml manifests without a name key 254 | in the \[lib\] section 255 | - Fix single quotes not being handled when parsing Cargo.toml 256 | 257 | ## 0.9.1 (2018-03-22) 258 | 259 | - Remove unicode\_literals import as Python 2 `distutils` does not 260 | support Unicode 261 | 262 | ## 0.9.0 (2018-03-07) 263 | 264 | - Find inplace extensions and automatically generate `Cargo.toml` 265 | manifests \#29 266 | 267 | ## 0.8.4 (2018-02-27) 268 | 269 | - Improve compatibility of build\_rust with build\_ext \#28 270 | 271 | ## 0.8.3 (2017-12-05) 272 | 273 | - Ignore strip option when platform is win32 \#26 274 | 275 | ## 0.8.2 (2017-09-08) 276 | 277 | - Fix script generation for bdist\_wheel 278 | 279 | ## 0.8.1 (2017-09-08) 280 | 281 | - Added native parameter 282 | - Fix script generation for executables 283 | 284 | ## 0.8.0 (2017-09-05) 285 | 286 | - Support multiple rust binaries \#24 287 | 288 | ## 0.7.2 (2017-09-01) 289 | 290 | - Generate console-script for Binding.Exec \#22 291 | - Do not run cargo check for sdist command \#18 292 | - Remove extra python3 file extension for executables. 293 | 294 | ## 0.7.1 (2017-08-18) 295 | 296 | - Allow to strip symbols from executable or dynamic library. 297 | - Use PyO3 0.2 for example. 298 | 299 | ## 0.7.0 (2017-08-11) 300 | 301 | - Allow to build executable and pack with python package. 302 | - Use PyO3 0.1 for example. 303 | 304 | ## 0.6.4 (2017-07-31) 305 | 306 | - check command respects optional option 307 | - Don't fail when Rust isn't installed while all extensions are 308 | optional 309 | 310 | ## 0.6.3 (2017-07-31) 311 | 312 | - Fix pypi source distribution 313 | 314 | ## 0.6.2 (2017-07-31) 315 | 316 | - Add optional option to RustExtension \#16 317 | 318 | ## 0.6.1 (2017-06-30) 319 | 320 | - Support CARGO\_TARGET\_DIR variable \#14 321 | 322 | ## 0.6.0 (2017-06-20) 323 | 324 | - Add support for PyO3 project 325 | - Add support for no-binding mode 326 | 327 | ## 0.5.1 (2017-05-03) 328 | 329 | - Added support for "cargo test" 330 | - Fixed unbound method type error \#4 331 | 332 | ## 0.5.0 (2017-03-26) 333 | 334 | - Added support for "cargo check" 335 | 336 | ## 0.4.2 (2017-03-15) 337 | 338 | - Added "--qbuild" option for "build\_rust" command. Set "quiet" mode 339 | for all extensions. 340 | - Added "--debug" and "--release" options for "build\_rust" command. 341 | 342 | ## 0.4.1 (2017-03-10) 343 | 344 | - Fixed cargo manifest absolute path detection 345 | 346 | ## 0.4 (2017-03-10) 347 | 348 | - Fixed bdist\_egg and bdist\_wheel support 349 | - setuptool's clean command cleans rust project as well 350 | - Use absolute path to cargo manifest 351 | - Enable debug builds for inplace builds, otherwise build release 352 | - Simplify monkey patches 353 | 354 | ## 0.3.1 (2017-03-09) 355 | 356 | - Fix compatibility with some old versions of setuptools 357 | 358 | ## 0.3 (2017-03-09) 359 | 360 | - Fixed OSX extension compilation 361 | - Use distutils exceptions for errors 362 | - Add rust version check for extension 363 | - Cleanup example project 364 | 365 | ## 0.2 (2017-03-08) 366 | 367 | - Fix bdist\_egg and bdist\_wheel commands 368 | 369 | ## 0.1 (2017-03-08) 370 | 371 | - Initial release 372 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017-2018 PyO3 project & contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 9 | of the Software, and to permit persons to whom the Software is furnished to do 10 | so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md 2 | include CHANGELOG.md 3 | include LICENSE 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Setuptools plugin for Rust extensions 2 | 3 | [![github actions](https://github.com/PyO3/setuptools-rust/actions/workflows/ci.yml/badge.svg)](https://github.com/PyO3/setuptools-rust/actions/workflows/ci.yml) 4 | [![pypi package](https://badge.fury.io/py/setuptools-rust.svg)](https://pypi.org/project/setuptools-rust/) 5 | [![readthedocs](https://readthedocs.org/projects/pip/badge/)](https://setuptools-rust.readthedocs.io/en/latest/) 6 | [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff) 7 | 8 | `setuptools-rust` is a plugin for `setuptools` to build Rust Python extensions implemented with [PyO3](https://github.com/PyO3/pyo3) or [rust-cpython](https://github.com/dgrunwald/rust-cpython). 9 | 10 | Compile and distribute Python extensions written in Rust as easily as if 11 | they were written in C. 12 | 13 | ## Quickstart 14 | 15 | The following is a very basic tutorial that shows how to use `setuptools-rust` in `pyproject.toml`. 16 | It assumes that you already have a bunch of Python and Rust files that you want 17 | to distribute. You can see examples for these files in the 18 | [`examples/hello-world`](https://github.com/PyO3/setuptools-rust/tree/main/examples/hello-world) 19 | directory in the [github repository](https://github.com/PyO3/setuptools-rust). 20 | The [PyO3 docs](https://pyo3.rs) have detailed information on how to write Python 21 | modules in Rust. 22 | 23 | ``` 24 | hello-world 25 | ├── python 26 | │ └── hello_world 27 | │ └── __init__.py 28 | └── rust 29 | └── lib.rs 30 | ``` 31 | 32 | Once the implementation files are in place, we need to add a `pyproject.toml` 33 | file that tells anyone that wants to use your project how to build it. 34 | In this file, we use an [array of tables](https://toml.io/en/v1.0.0#array-of-tables) 35 | (TOML jargon equivalent to Python's list of dicts) for ``[[tool.setuptools-rust.ext-modules]]``, 36 | to specify different extension modules written in Rust: 37 | 38 | 39 | ```toml 40 | # pyproject.toml 41 | [build-system] 42 | requires = ["setuptools", "setuptools-rust"] 43 | build-backend = "setuptools.build_meta" 44 | 45 | [project] 46 | name = "hello-world" 47 | version = "1.0" 48 | 49 | [tool.setuptools.packages] 50 | # Pure Python packages/modules 51 | find = { where = ["python"] } 52 | 53 | [[tool.setuptools-rust.ext-modules]] 54 | # Private Rust extension module to be nested into the Python package 55 | target = "hello_world._lib" # The last part of the name (e.g. "_lib") has to match lib.name in Cargo.toml, 56 | # but you can add a prefix to nest it inside of a Python package. 57 | path = "Cargo.toml" # Default value, can be omitted 58 | binding = "PyO3" # Default value, can be omitted 59 | ``` 60 | 61 | Each extension module should map directly into the corresponding `[lib]` table on the 62 | [Cargo manifest file](https://doc.rust-lang.org/cargo/reference/manifest.html): 63 | 64 | ```toml 65 | # Cargo.toml 66 | [package] 67 | name = "hello-world" 68 | version = "0.1.0" 69 | edition = "2021" 70 | 71 | [dependencies] 72 | pyo3 = "0.24" 73 | 74 | [lib] 75 | name = "_lib" # private module to be nested into Python package, 76 | # needs to match the name of the function with the `[#pymodule]` attribute 77 | path = "rust/lib.rs" 78 | crate-type = ["cdylib"] # required for shared library for Python to import from. 79 | 80 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 81 | # See also PyO3 docs on writing Cargo.toml files at https://pyo3.rs 82 | ``` 83 | 84 | You will also need to tell Setuptools that the Rust files are required to build your 85 | project from the [source distribution](https://setuptools.pypa.io/en/latest/userguide/miscellaneous.html). 86 | That can be done either via `MANIFEST.in` (see example below) or via a plugin like 87 | [`setuptools-scm`](https://pypi.org/project/setuptools-scm/). 88 | 89 | ``` 90 | # MANIFEST.in 91 | include Cargo.toml 92 | recursive-include rust *.rs 93 | ``` 94 | 95 | With these files in place, you can install the project in a virtual environment 96 | for testing and making sure everything is working correctly: 97 | 98 | ```powershell 99 | # cd hello-world 100 | python3 -m venv .venv 101 | source .venv/bin/activate # on Linux or macOS 102 | .venv\Scripts\activate # on Windows 103 | python -m pip install -e . 104 | python 105 | >>> import hello_world 106 | # ... try running something from your new extension module ... 107 | # ... better write some tests with pytest ... 108 | ``` 109 | 110 | ## Environment variables for configuration 111 | 112 | As well as all [environment variables supported by Cargo](https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-reads), `setuptools-rust` also supports the following: 113 | 114 | - `SETUPTOOLS_RUST_CARGO_PROFILE`: used to override the profile of the Rust build. Defaults to `release`, e.g. set to `dev` to do a debug build. 115 | 116 | ## Next steps and final remarks 117 | 118 | - When you are ready to distribute your project, have a look on 119 | [the notes in the documentation about building wheels](https://setuptools-rust.readthedocs.io/en/latest/building_wheels.html). 120 | 121 | - Cross-compiling is also supported, using one of 122 | [`crossenv`](https://github.com/benfogle/crossenv), 123 | [`cross`](https://github.com/rust-embedded/cross) or 124 | [`cargo-zigbuild`](https://github.com/messense/cargo-zigbuild). 125 | For examples see the `test-crossenv` and `test-cross` and `test-zigbuild` Github actions jobs in 126 | [`ci.yml`](https://github.com/PyO3/setuptools-rust/blob/main/.github/workflows/ci.yml). 127 | 128 | - You can also use `[[tool.setuptools-rust.bins]]` (instead of `[[tool.setuptools-rust.ext-modules]]`), 129 | if you want to distribute a binary executable written in Rust (instead of a library that can be imported by the Python runtime). 130 | Note however that distributing both library and executable (or multiple executables), 131 | may significantly increase the size of the 132 | [wheel](https://packaging.python.org/en/latest/glossary/#term-Wheel) 133 | file distributed by the 134 | [package index](https://packaging.python.org/en/latest/glossary/#term-Package-Index) 135 | and therefore increase build, download and installation times. 136 | Another approach is to use a Python entry-point that calls the Rust 137 | implementation (exposed via PyO3 bindings). 138 | See the [hello-world](https://github.com/PyO3/setuptools-rust/tree/main/examples/hello-world) 139 | example for more insights. 140 | 141 | - For a complete reference of the configuration options, see the 142 | [API reference](https://setuptools-rust.readthedocs.io/en/latest/reference.html). 143 | You can use any parameter defined by the `RustExtension` class with 144 | `[[tool.setuptools-rust.ext-modules]]` and any parameter defined by the 145 | `RustBin` class with `[[tool.setuptools-rust.bins]]`; just remember to replace 146 | underscore characters `_` with dashes `-` in your `pyproject.toml` file. 147 | 148 | - `Cargo.toml` allow only one `[lib]` table per file. 149 | If you require multiple extension modules you will need to write multiple `Cargo.toml` files. 150 | Alternatively you can create a single private Rust top-level module that exposes 151 | multiple submodules (using [PyO3's submodules](https://pyo3.rs/v0.20.0/module#python-submodules)), 152 | which may also reduce the size of the build artifacts. 153 | You can always keep your extension modules private and wrap them in pure Python 154 | to have fine control over the public API. 155 | 156 | - If want to include both `[[tool.setuptools-rust.bins]]` and `[[tool.setuptools-rust.ext-modules]]` 157 | in the same macOS wheel, you might have to manually add an extra `build.rs` file, 158 | see [PyO3/setuptools-rust#351](https://github.com/PyO3/setuptools-rust/pull/351) 159 | for more information about the workaround. 160 | 161 | - For more examples, see: 162 | - [`hello-world`](https://github.com/PyO3/setuptools-rust/tree/main/examples/hello-world): 163 | a more complete version of the code used in this tutorial that mixes both 164 | `[[tool.setuptools-rust.ext-modules]]` and `[[tool.setuptools-rust.bins]]` 165 | in a single distribution. 166 | - [`html-py-ever`](https://github.com/PyO3/setuptools-rust/tree/main/examples/html-py-ever): 167 | a more advanced example that uses Rust crates as dependencies. 168 | - [`rust_with_cffi`](https://github.com/PyO3/setuptools-rust/tree/main/examples/rust_with_cffi): 169 | uses both Rust and [CFFI](https://cffi.readthedocs.io/en/latest/). 170 | - [`namespace_package`](https://github.com/PyO3/setuptools-rust/tree/main/examples/namespace_package): 171 | integrates Rust-written modules into PEP 420 namespace packages. 172 | - [`hello-world-script`](https://github.com/PyO3/setuptools-rust/tree/main/examples/hello-world-script): 173 | uses Rust only for creating binary executables, not library modules. 174 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | ```{include} ../README.md 2 | ``` 3 | -------------------------------------------------------------------------------- /docs/building_wheels.md: -------------------------------------------------------------------------------- 1 | # Building wheels 2 | 3 | Because `setuptools-rust` is an extension to `setuptools`, the standard [`python -m build`](https://pypa-build.readthedocs.io/en/stable/) command 4 | (or [`pip wheel --no-deps . --wheel-dir dist`](https://pip.pypa.io/en/stable/cli/pip_wheel/)) can be used to build distributable wheels. 5 | These wheels can be uploaded to PyPI using standard tools such as [twine](https://github.com/pypa/twine). 6 | 7 | A key choice to make is whether to upload [PEP 384](https://www.python.org/dev/peps/pep-0384/) "stable" (aka "limited") API wheels which support multiple Python versions in a single binary, or to build individual artifacts for each Python version. There is a longer discussion of this [in the PyO3 docs](https://pyo3.rs/latest/building_and_distribution#py_limited_apiabi3). 8 | 9 | This chapter covers each of these options below. 10 | 11 | ## Building for ABI3 12 | 13 | `setuptools-rust` will automatically configure for the limited API when this is set in the `[bdist_wheel]` configuration section of [`setup.cfg`](https://setuptools.pypa.io/en/latest/deprecated/distutils/configfile.html#writing-the-setup-configuration-file): 14 | 15 | ```ini 16 | [bdist_wheel] 17 | py_limited_api=cp37 # replace with desired minimum Python version 18 | ``` 19 | 20 | If using a `pyproject.toml`-based build, then save the above in a file and use the `DIST_EXTRA_CONFIG` environment variable to instruct `setuptools` to pick up this extra configuration. (`DIST_EXTRA_CONFIG` is documented [on this page](https://setuptools.pypa.io/en/latest/deprecated/distutils/configfile.html#writing-the-setup-configuration-file) of the `setuptools` docs.) 21 | 22 | It is also possible to pass this setting via the command line, e.g. 23 | 24 | ``` 25 | python -m build --config-settings=--build-option=--py-limited-api=cp37 26 | ``` 27 | 28 | ## Building for multiple Python versions 29 | 30 | ### Using `cibuildwheel` 31 | 32 | [`cibuildwheel`][cibuildwheel] is a tool to build wheels for multiple platforms using Github Actions. 33 | 34 | The [`rtoml` package does this, for example](https://github.com/samuelcolvin/rtoml/blob/143ee0907bba616cbcd5cc58eefe9000fcc2b5f2/.github/workflows/ci.yml#L99-L195). 35 | 36 | ### Building manually 37 | 38 | Place a script called `build-wheels.sh` with the following contents in your project root (next to the `setup.py` file): 39 | 40 | ```{eval-rst} 41 | .. literalinclude:: ../examples/html-py-ever/build-wheels.sh 42 | :language: bash 43 | ``` 44 | 45 | This script can be used to produce wheels for multiple Python versions. 46 | 47 | #### Binary wheels on linux 48 | 49 | To build binary wheels on linux, you need to use the [manylinux docker container](https://github.com/pypa/manylinux). You will run the `build-wheels.sh` from above inside that container. 50 | 51 | First, pull the `manylinux2014` Docker image: 52 | 53 | ```bash 54 | docker pull quay.io/pypa/manylinux2014_x86_64 55 | ``` 56 | 57 | Then use the following command to build wheels for supported Python versions: 58 | 59 | ```bash 60 | docker run --rm -v `pwd`:/io quay.io/pypa/manylinux2014_x86_64 bash /io/build-wheels.sh 61 | ``` 62 | 63 | This will create wheels in the `dist` directory: 64 | 65 | ```bash 66 | $ ls dist 67 | hello_rust-0.1.0-cp37-cp37m-linux_x86_64.whl hello_rust-0.1.0-cp37-cp37m-manylinux2014_x86_64.whl 68 | hello_rust-0.1.0-cp38-cp38-linux_x86_64.whl hello_rust-0.1.0-cp38-cp38-manylinux2014_x86_64.whl 69 | hello_rust-0.1.0-cp39-cp39-linux_x86_64.whl hello_rust-0.1.0-cp39-cp39-manylinux2014_x86_64.whl 70 | ``` 71 | 72 | It is possible to use any of the `manylinux` docker images: `manylinux1`, `manylinux2010` or `manylinux2014`. (Just replace `manylinux2014` in the above instructions with the alternative version you wish to use.) 73 | 74 | #### Binary wheels on macOS 75 | 76 | For building wheels on macOS it is sufficient to use one of the default `python -m build` or `pip wheel --no-deps . --wheel-dir dist` commands. 77 | 78 | To build `universal2` wheels set the `ARCHFLAGS` environment variable to contain both `x86_64` and `arm64`, for example `ARCHFLAGS="-arch x86_64 -arch arm64"`. Wheel-building solutions such as [`cibuildwheel`][cibuildwheel] set this environment variable automatically. 79 | 80 | [cibuildwheel]: https://github.com/pypa/cibuildwheel 81 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # This file only contains a selection of the most common options. For a full 4 | # list see the documentation: 5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 6 | 7 | from sphinx.transforms import SphinxTransform 8 | 9 | # -- Path setup -------------------------------------------------------------- 10 | 11 | # If extensions (or modules to document with autodoc) are in another directory, 12 | # add these directories to sys.path here. If the directory is relative to the 13 | # documentation root, use os.path.abspath to make it absolute, like shown here. 14 | 15 | # -- Project information ----------------------------------------------------- 16 | 17 | project = "setuptools-rust" 18 | copyright = "2021, The PyO3 Contributors" 19 | author = "The PyO3 Contributors" 20 | 21 | 22 | # -- General configuration --------------------------------------------------- 23 | 24 | # Add any Sphinx extension module names here, as strings. They can be 25 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 26 | # ones. 27 | extensions = [ 28 | "sphinx.ext.autodoc", 29 | "sphinx.ext.napoleon", 30 | "sphinx_autodoc_typehints", 31 | "myst_parser", 32 | ] 33 | 34 | # Add any paths that contain templates here, relative to this directory. 35 | templates_path = ["_templates"] 36 | 37 | # List of patterns, relative to source directory, that match files and 38 | # directories to ignore when looking for source files. 39 | # This pattern also affects html_static_path and html_extra_path. 40 | exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] 41 | 42 | 43 | # -- Options for HTML output ------------------------------------------------- 44 | 45 | # The theme to use for HTML and HTML Help pages. See the documentation for 46 | # a list of builtin themes. 47 | # 48 | html_theme = "furo" 49 | 50 | # Add any paths that contain custom static files (such as style sheets) here, 51 | # relative to this directory. They are copied after the builtin static files, 52 | # so a file named "default.css" will overwrite the builtin "default.css". 53 | html_static_path = ["_static"] 54 | 55 | html_theme_options = {} 56 | 57 | # -- Custom HTML link transformation to make documentation links relative -- 58 | 59 | # This is necessary because the README.md (for example) has links to the latest 60 | # documentation, but we want them to be relative to the specific docs version. 61 | 62 | DOCS_URL = "https://setuptools-rust.readthedocs.io/en/latest/" 63 | 64 | 65 | class RelativeDocLinks(SphinxTransform): 66 | default_priority = 750 67 | 68 | def apply(self): 69 | from docutils.nodes import Text, reference 70 | 71 | def baseref(o): 72 | return isinstance(o, reference) and o.get("refuri", "").startswith(DOCS_URL) 73 | 74 | def basetext(o): 75 | return isinstance(o, Text) and o.startswith(DOCS_URL) 76 | 77 | for node in self.document.traverse(baseref): 78 | target = node["refuri"].replace(DOCS_URL, "", 1) 79 | node.replace_attr("refuri", target) 80 | for t in node.traverse(basetext): 81 | t1 = Text(t.replace(DOCS_URL, "", 1), t.rawsource) 82 | t.parent.replace(t, t1) 83 | return 84 | 85 | 86 | # end of class 87 | 88 | 89 | def setup(app): 90 | app.add_transform(RelativeDocLinks) 91 | return 92 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | ```{eval-rst} 2 | .. toctree:: 3 | :maxdepth: 2 4 | :hidden: 5 | 6 | README.md 7 | setuppy_tutorial 8 | building_wheels 9 | reference 10 | ``` 11 | 12 | ```{include} ../README.md 13 | ``` 14 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /docs/reference.rst: -------------------------------------------------------------------------------- 1 | API Reference 2 | ============= 3 | 4 | .. py:module:: setuptools_rust 5 | 6 | .. autoclass:: RustExtension 7 | .. autoclass:: RustBin 8 | .. autoclass:: Binding 9 | .. autoclass:: Strip 10 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | Sphinx==8.2.3 2 | sphinx-autodoc-typehints==3.1.0 3 | furo==2024.8.6 4 | myst-parser==4.0.1 5 | -------------------------------------------------------------------------------- /docs/setuppy_tutorial.md: -------------------------------------------------------------------------------- 1 | # Usage with `setup.py` 2 | 3 | While `pyproject.toml`-based configuration will be enough for most projects, 4 | sometimes you may need to use custom logic and imperative programming during the build. 5 | For those scenarios, `setuptools` also allows you to specify project configuration 6 | via `setup.py` in addition to `pyproject.toml`. 7 | 8 | The following is a very basic tutorial that shows how to use `setuptools-rust` in 9 | your `setup.py`. 10 | 11 | 12 | ## Basic implementation files 13 | 14 | Let's start by assuming that you already have a bunch of Python and Rust files[^1] 15 | that you would like to package for distribution in PyPI inside of a project directory 16 | named `hello-world-setuppy`[^2][^3]: 17 | 18 | [^1]: To know more about how to write Rust to be integrated into Python packages, 19 | please have a look on the [PyO3 docs](https://pyo3.rs) 20 | [^2]: You can have a look on the 21 | [examples/hello-world-setuppy](https://github.com/PyO3/setuptools-rust/tree/main/examples/hello-world-setuppy) 22 | directory in the `setuptools-rust` repository. 23 | [^3]: If you are an experienced Python or Rust programmer, you may notice that we 24 | avoid using the `src` directory and explicitly instruct Setuptools and Cargo to 25 | look into the `python` and `rust` directories respectively. 26 | Since both Python and Rust ecosystem will try to claim the `src` directory as 27 | their default, we prefer to be explicit and avoid confusion. 28 | 29 | 30 | ``` 31 | hello-world-setuppy 32 | ├── Cargo.lock 33 | ├── Cargo.toml 34 | ├── python 35 | │ └── hello_world 36 | │ └── __init__.py 37 | └── rust 38 | └── lib.rs 39 | ``` 40 | 41 | ```{literalinclude} ../examples/hello-world-setuppy/python/hello_world/__init__.py 42 | :language: python 43 | ``` 44 | 45 | ```{literalinclude} ../examples/hello-world-setuppy/rust/lib.rs 46 | :language: rust 47 | ``` 48 | 49 | ```{literalinclude} ../examples/hello-world-setuppy/Cargo.toml 50 | :language: toml 51 | ``` 52 | 53 | 54 | ## Adding files to support packaging 55 | 56 | Now we start by adding a `pyproject.toml` which tells anyone that wants to use 57 | our project to use `setuptools` and `setuptools-rust` to build it: 58 | 59 | ```{literalinclude} ../examples/hello-world-setuppy/pyproject.toml 60 | :language: toml 61 | ``` 62 | 63 | … and a [`setup.py` configuration file](https://setuptools.pypa.io/en/latest/references/keywords.html) 64 | that tells Setuptools how to build the Rust extensions using our `Cargo.toml` and `setuptools-rust`: 65 | 66 | ```{literalinclude} ../examples/hello-world-setuppy/setup.py 67 | :language: python 68 | ``` 69 | 70 | For a complete reference of the options supported by the `RustExtension` class, see the 71 | [API reference](https://setuptools-rust.readthedocs.io/en/latest/reference.html). 72 | 73 | 74 | We also add a [`MANIFEST.in` file](https://setuptools.pypa.io/en/latest/userguide/miscellaneous.html) 75 | to control which files we want in the source distribution[^4]: 76 | 77 | ```{literalinclude} ../examples/hello-world-setuppy/MANIFEST.in 78 | ``` 79 | 80 | [^4]: Alternatively you can also use `setuptools-scm` to add all the files under revision control 81 | to the `sdist`, see the [docs](https://pypi.org/project/setuptools-scm/) for more information. 82 | 83 | 84 | ## Testing the extension 85 | 86 | With these files in place, you can install the project in a virtual environment 87 | for testing and making sure everything is working correctly: 88 | 89 | 90 | ```powershell 91 | # cd hello-world-setuppy 92 | python3 -m venv .venv 93 | source .venv/bin/activate # on Linux or macOS 94 | .venv\Scripts\activate # on Windows 95 | python -m pip install -e . 96 | python -c 'import hello_world; print(hello_world.sum_as_string(5, 7))' # => 12 97 | # ... better write some tests with pytest ... 98 | ``` 99 | 100 | 101 | ## Next steps and final remarks 102 | 103 | - When you are ready to distribute your project, have a look on 104 | [the notes in the documentation about building wheels](https://setuptools-rust.readthedocs.io/en/latest/building_wheels.html). 105 | 106 | - You can also use a [`RustBin`](https://setuptools-rust.readthedocs.io/en/latest/reference.html) object 107 | (instead of a `RustExtension`), if you want to distribute a binary executable 108 | written in Rust (instead of a library that can be imported by the Python runtime). 109 | Note however that distributing both library and executable (or multiple executables), 110 | may significantly increase the size of the 111 | [wheel](https://packaging.python.org/en/latest/glossary/#term-Wheel) 112 | file distributed by the 113 | [package index](https://packaging.python.org/en/latest/glossary/#term-Package-Index) 114 | and therefore increase build, download and installation times. 115 | Another approach is to use a Python entry-point that calls the Rust 116 | implementation (exposed via PyO3 bindings). 117 | See the [hello-world](https://github.com/PyO3/setuptools-rust/tree/main/examples/hello-world) 118 | example for more insights. 119 | 120 | - If want to include both `RustBin` and `RustExtension` same macOS wheel, you might have 121 | to manually add an extra `build.rs` file, see [PyO3/setuptools-rust#351](https://github.com/PyO3/setuptools-rust/pull/351) 122 | for more information about the workaround. 123 | 124 | - Since the adoption of {pep}`517`, running `python setup.py ...` directly as a CLI tool is 125 | [considered deprecated](https://blog.ganssle.io/articles/2021/10/setup-py-deprecated.html). 126 | Nevertheless, `setup.py` can be safely used as a configuration file 127 | (the same way `conftest.py` is used by `pytest` or `noxfile.py` is used by `nox`). 128 | There is a different mindset that comes with this change, though: 129 | for example, it does not make sense to use `sys.exit(0)` in a `setup.py` file 130 | or use a overarching `try...except...` block to re-run a failed build with different parameters. 131 | -------------------------------------------------------------------------------- /emscripten/.gitignore: -------------------------------------------------------------------------------- 1 | builddir 2 | main.* 3 | !main.c 4 | pybuilddir.txt 5 | pyodide 6 | node_modules 7 | -------------------------------------------------------------------------------- /emscripten/.ruff.toml: -------------------------------------------------------------------------------- 1 | [lint] 2 | extend-ignore = ["TID251"] 3 | -------------------------------------------------------------------------------- /emscripten/_sysconfigdata__emscripten_wasm32-emscripten.py: -------------------------------------------------------------------------------- 1 | # system configuration generated and used by the sysconfig module 2 | build_time_vars = { 3 | "ABIFLAGS": "", 4 | "AR": "/src/emsdk/emsdk/upstream/emscripten/emar", 5 | "ARFLAGS": "rcs", 6 | "BLDSHARED": "emcc -sSIDE_MODULE=1 -L/src/emscripten/python-lib/", 7 | "CC": "emcc -I/src/emscripten/python-include/", 8 | "CCSHARED": "", 9 | "CFLAGS": "-Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g " 10 | "-fwrapv -O3 -Wall -O2 -g0 -fPIC", 11 | "EXT_SUFFIX": ".cpython-310-wasm32-emscripten.so", 12 | "HOST_GNU_TYPE": "wasm32-unknown-emscripten", 13 | "LDSHARED": "emcc -sSIDE_MODULE=1", 14 | "Py_DEBUG": "0", 15 | "py_version_nodot": "310", 16 | } 17 | -------------------------------------------------------------------------------- /emscripten/emcc_wrapper.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import subprocess 3 | import sys 4 | 5 | 6 | def update_args(args): 7 | # remove -lc. Not sure if it makes a difference but -lc doesn't belong here. 8 | # https://github.com/emscripten-core/emscripten/issues/17191 9 | for i in reversed(range(len(args))): 10 | if args[i] == "c" and args[i - 1] == "-l": 11 | del args[i - 1 : i + 1] 12 | 13 | return args 14 | 15 | 16 | def main(args): 17 | args = update_args(args) 18 | return subprocess.call(["emcc"] + args) 19 | 20 | 21 | if __name__ == "__main__": 22 | args = sys.argv[1:] 23 | sys.exit(main(args)) 24 | -------------------------------------------------------------------------------- /emscripten/pyo3_config.ini: -------------------------------------------------------------------------------- 1 | implementation=CPython 2 | version=3.10 3 | shared=true 4 | abi3=false 5 | lib_name=python3.10 6 | pointer_width=32 7 | suppress_build_script_link_lines=false 8 | -------------------------------------------------------------------------------- /emscripten/runner.js: -------------------------------------------------------------------------------- 1 | const { opendir } = require("node:fs/promises"); 2 | const { loadPyodide } = require("pyodide"); 3 | 4 | async function findWheel(distDir) { 5 | const dir = await opendir(distDir); 6 | for await (const dirent of dir) { 7 | if (dirent.name.includes("emscripten") && dirent.name.endsWith("whl")) { 8 | return dirent.name; 9 | } 10 | } 11 | } 12 | 13 | function make_tty_ops(stream){ 14 | return { 15 | // get_char has 3 particular return values: 16 | // a.) the next character represented as an integer 17 | // b.) undefined to signal that no data is currently available 18 | // c.) null to signal an EOF 19 | get_char(tty) { 20 | if (!tty.input.length) { 21 | var result = null; 22 | var BUFSIZE = 256; 23 | var buf = Buffer.alloc(BUFSIZE); 24 | var bytesRead = fs.readSync(process.stdin.fd, buf, 0, BUFSIZE, -1); 25 | if (bytesRead === 0) { 26 | return null; 27 | } 28 | result = buf.slice(0, bytesRead); 29 | tty.input = Array.from(result); 30 | } 31 | return tty.input.shift(); 32 | }, 33 | put_char(tty, val) { 34 | try { 35 | if(val !== null){ 36 | tty.output.push(val); 37 | } 38 | if (val === null || val === 10) { 39 | process.stdout.write(Buffer.from(tty.output)); 40 | tty.output = []; 41 | } 42 | } catch(e){ 43 | console.warn(e); 44 | } 45 | }, 46 | flush(tty) { 47 | if (!tty.output || tty.output.length === 0) { 48 | return; 49 | } 50 | stream.write(Buffer.from(tty.output)); 51 | tty.output = []; 52 | } 53 | }; 54 | } 55 | 56 | function setupStreams(FS, TTY){ 57 | let mytty = FS.makedev(FS.createDevice.major++, 0); 58 | let myttyerr = FS.makedev(FS.createDevice.major++, 0); 59 | TTY.register(mytty, make_tty_ops(process.stdout)) 60 | TTY.register(myttyerr, make_tty_ops(process.stderr)) 61 | FS.mkdev('/dev/mytty', mytty); 62 | FS.mkdev('/dev/myttyerr', myttyerr); 63 | FS.unlink('/dev/stdin'); 64 | FS.unlink('/dev/stdout'); 65 | FS.unlink('/dev/stderr'); 66 | FS.symlink('/dev/mytty', '/dev/stdin'); 67 | FS.symlink('/dev/mytty', '/dev/stdout'); 68 | FS.symlink('/dev/myttyerr', '/dev/stderr'); 69 | FS.closeStream(0); 70 | FS.closeStream(1); 71 | FS.closeStream(2); 72 | var stdin = FS.open('/dev/stdin', 0); 73 | var stdout = FS.open('/dev/stdout', 1); 74 | var stderr = FS.open('/dev/stderr', 1); 75 | } 76 | 77 | 78 | const pkgDir = process.argv[2]; 79 | const distDir = pkgDir + "/dist"; 80 | const testDir = pkgDir + "/tests"; 81 | 82 | async function main() { 83 | const wheelName = await findWheel(distDir); 84 | const wheelURL = `file:${distDir}/${wheelName}`; 85 | let errcode = 1; 86 | 87 | try { 88 | pyodide = await loadPyodide(); 89 | const FS = pyodide.FS; 90 | setupStreams(FS, pyodide._module.TTY); 91 | const NODEFS = FS.filesystems.NODEFS; 92 | FS.mkdir("/test_dir"); 93 | FS.mount(NODEFS, { root: testDir }, "/test_dir"); 94 | await pyodide.loadPackage(["micropip", "pytest", "tomli"]); 95 | const micropip = pyodide.pyimport("micropip"); 96 | await micropip.install("beautifulsoup4"); 97 | await micropip.install(wheelURL); 98 | const pytest = pyodide.pyimport("pytest"); 99 | errcode = pytest.main(pyodide.toPy(["/test_dir"])); 100 | } catch (e) { 101 | console.error(e); 102 | process.exit(1); 103 | } 104 | process.exit(errcode); 105 | } 106 | 107 | main(); 108 | -------------------------------------------------------------------------------- /examples/hello-world-script/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "hello-world-script" 7 | version = "0.1.0" 8 | -------------------------------------------------------------------------------- /examples/hello-world-script/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hello-world-script" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | 10 | [profile.release-lto] 11 | inherits = "release" 12 | lto = true 13 | 14 | [[bin]] 15 | name = "hello-world-script" 16 | path = "rust/main.rs" 17 | # See https://doc.rust-lang.org/cargo/reference/cargo-targets.html#binaries 18 | # 19 | # If [[bin]] is not specified, but the file ``src/main.rs`` exists, 20 | # you can also rely on an implicit definition which will behave similarly to: 21 | # 22 | # [[bin]] 23 | # name = 24 | # path = "src/main.rs" 25 | -------------------------------------------------------------------------------- /examples/hello-world-script/MANIFEST.in: -------------------------------------------------------------------------------- 1 | include Cargo.toml 2 | recursive-include rust * 3 | recursive-include python * 4 | -------------------------------------------------------------------------------- /examples/hello-world-script/noxfile.py: -------------------------------------------------------------------------------- 1 | from os.path import dirname 2 | 3 | import nox 4 | 5 | SETUPTOOLS_RUST = dirname(dirname(dirname(__file__))) 6 | 7 | 8 | @nox.session() 9 | def test(session: nox.Session): 10 | session.install(SETUPTOOLS_RUST) 11 | # Ensure build uses version of setuptools-rust under development 12 | session.install("--no-build-isolation", ".") 13 | # Test Rust binary 14 | session.run("hello-world-script", *session.posargs) 15 | # Test Python package 16 | session.run("python", "-c", "import hello_world; print(hello_world)") 17 | -------------------------------------------------------------------------------- /examples/hello-world-script/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools", "setuptools-rust"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [project] 6 | name = "hello-world" 7 | version = "1.0" 8 | 9 | [tool.setuptools.packages] 10 | # Pure Python packages/modules 11 | find = { where = ["python"] } 12 | 13 | [[tool.setuptools-rust.bins]] 14 | # Private Rust extension module to be nested into Python package 15 | target = "hello-world-script" # Matches bin.name in Cargo.toml 16 | args = ["--profile", "release-lto"] # Extra args for Cargo 17 | # See reference for RustBin in https://setuptools-rust.readthedocs.io/en/latest/reference.html 18 | -------------------------------------------------------------------------------- /examples/hello-world-script/pytest.ini: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyO3/setuptools-rust/f21ef63b03752f98e60f51c754c738166e350d4f/examples/hello-world-script/pytest.ini -------------------------------------------------------------------------------- /examples/hello-world-script/python/hello_world/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyO3/setuptools-rust/f21ef63b03752f98e60f51c754c738166e350d4f/examples/hello-world-script/python/hello_world/__init__.py -------------------------------------------------------------------------------- /examples/hello-world-script/rust/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("Hello, world!"); 3 | } 4 | -------------------------------------------------------------------------------- /examples/hello-world-setuppy/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "autocfg" 7 | version = "1.3.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" 10 | 11 | [[package]] 12 | name = "cfg-if" 13 | version = "1.0.0" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 16 | 17 | [[package]] 18 | name = "heck" 19 | version = "0.5.0" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 22 | 23 | [[package]] 24 | name = "hello-world-setuppy" 25 | version = "0.1.0" 26 | dependencies = [ 27 | "pyo3", 28 | ] 29 | 30 | [[package]] 31 | name = "indoc" 32 | version = "2.0.5" 33 | source = "registry+https://github.com/rust-lang/crates.io-index" 34 | checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" 35 | 36 | [[package]] 37 | name = "libc" 38 | version = "0.2.154" 39 | source = "registry+https://github.com/rust-lang/crates.io-index" 40 | checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" 41 | 42 | [[package]] 43 | name = "memoffset" 44 | version = "0.9.1" 45 | source = "registry+https://github.com/rust-lang/crates.io-index" 46 | checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" 47 | dependencies = [ 48 | "autocfg", 49 | ] 50 | 51 | [[package]] 52 | name = "once_cell" 53 | version = "1.19.0" 54 | source = "registry+https://github.com/rust-lang/crates.io-index" 55 | checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" 56 | 57 | [[package]] 58 | name = "portable-atomic" 59 | version = "1.6.0" 60 | source = "registry+https://github.com/rust-lang/crates.io-index" 61 | checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0" 62 | 63 | [[package]] 64 | name = "proc-macro2" 65 | version = "1.0.82" 66 | source = "registry+https://github.com/rust-lang/crates.io-index" 67 | checksum = "8ad3d49ab951a01fbaafe34f2ec74122942fe18a3f9814c3268f1bb72042131b" 68 | dependencies = [ 69 | "unicode-ident", 70 | ] 71 | 72 | [[package]] 73 | name = "pyo3" 74 | version = "0.24.2" 75 | source = "registry+https://github.com/rust-lang/crates.io-index" 76 | checksum = "e5203598f366b11a02b13aa20cab591229ff0a89fd121a308a5df751d5fc9219" 77 | dependencies = [ 78 | "cfg-if", 79 | "indoc", 80 | "libc", 81 | "memoffset", 82 | "once_cell", 83 | "portable-atomic", 84 | "pyo3-build-config", 85 | "pyo3-ffi", 86 | "pyo3-macros", 87 | "unindent", 88 | ] 89 | 90 | [[package]] 91 | name = "pyo3-build-config" 92 | version = "0.24.2" 93 | source = "registry+https://github.com/rust-lang/crates.io-index" 94 | checksum = "99636d423fa2ca130fa5acde3059308006d46f98caac629418e53f7ebb1e9999" 95 | dependencies = [ 96 | "once_cell", 97 | "target-lexicon", 98 | ] 99 | 100 | [[package]] 101 | name = "pyo3-ffi" 102 | version = "0.24.2" 103 | source = "registry+https://github.com/rust-lang/crates.io-index" 104 | checksum = "78f9cf92ba9c409279bc3305b5409d90db2d2c22392d443a87df3a1adad59e33" 105 | dependencies = [ 106 | "libc", 107 | "pyo3-build-config", 108 | ] 109 | 110 | [[package]] 111 | name = "pyo3-macros" 112 | version = "0.24.2" 113 | source = "registry+https://github.com/rust-lang/crates.io-index" 114 | checksum = "0b999cb1a6ce21f9a6b147dcf1be9ffedf02e0043aec74dc390f3007047cecd9" 115 | dependencies = [ 116 | "proc-macro2", 117 | "pyo3-macros-backend", 118 | "quote", 119 | "syn", 120 | ] 121 | 122 | [[package]] 123 | name = "pyo3-macros-backend" 124 | version = "0.24.2" 125 | source = "registry+https://github.com/rust-lang/crates.io-index" 126 | checksum = "822ece1c7e1012745607d5cf0bcb2874769f0f7cb34c4cde03b9358eb9ef911a" 127 | dependencies = [ 128 | "heck", 129 | "proc-macro2", 130 | "pyo3-build-config", 131 | "quote", 132 | "syn", 133 | ] 134 | 135 | [[package]] 136 | name = "quote" 137 | version = "1.0.36" 138 | source = "registry+https://github.com/rust-lang/crates.io-index" 139 | checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" 140 | dependencies = [ 141 | "proc-macro2", 142 | ] 143 | 144 | [[package]] 145 | name = "syn" 146 | version = "2.0.61" 147 | source = "registry+https://github.com/rust-lang/crates.io-index" 148 | checksum = "c993ed8ccba56ae856363b1845da7266a7cb78e1d146c8a32d54b45a8b831fc9" 149 | dependencies = [ 150 | "proc-macro2", 151 | "quote", 152 | "unicode-ident", 153 | ] 154 | 155 | [[package]] 156 | name = "target-lexicon" 157 | version = "0.13.2" 158 | source = "registry+https://github.com/rust-lang/crates.io-index" 159 | checksum = "e502f78cdbb8ba4718f566c418c52bc729126ffd16baee5baa718cf25dd5a69a" 160 | 161 | [[package]] 162 | name = "unicode-ident" 163 | version = "1.0.12" 164 | source = "registry+https://github.com/rust-lang/crates.io-index" 165 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 166 | 167 | [[package]] 168 | name = "unindent" 169 | version = "0.2.3" 170 | source = "registry+https://github.com/rust-lang/crates.io-index" 171 | checksum = "c7de7d73e1754487cb58364ee906a499937a0dfabd86bcb980fa99ec8c8fa2ce" 172 | -------------------------------------------------------------------------------- /examples/hello-world-setuppy/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hello-world-setuppy" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | pyo3 = "0.24.2" 8 | 9 | [lib] 10 | # See https://github.com/PyO3/pyo3 for details 11 | name = "_lib" # private module to be nested into Python package 12 | path = "rust/lib.rs" 13 | crate-type = ["cdylib"] 14 | 15 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 16 | -------------------------------------------------------------------------------- /examples/hello-world-setuppy/MANIFEST.in: -------------------------------------------------------------------------------- 1 | include Cargo.toml 2 | recursive-include rust * 3 | recursive-include python * 4 | -------------------------------------------------------------------------------- /examples/hello-world-setuppy/noxfile.py: -------------------------------------------------------------------------------- 1 | from os.path import dirname 2 | 3 | import nox 4 | 5 | SETUPTOOLS_RUST = dirname(dirname(dirname(__file__))) 6 | 7 | 8 | @nox.session() 9 | def test(session: nox.Session): 10 | session.install(SETUPTOOLS_RUST) 11 | # Ensure build uses version of setuptools-rust under development 12 | session.install("--no-build-isolation", ".") 13 | # Test Rust binary 14 | session.run("python", "-c", "from hello_world import _lib; print(_lib)") 15 | session.run("python", "-c", "__import__('hello_world').sum_as_string(5, 7)") 16 | -------------------------------------------------------------------------------- /examples/hello-world-setuppy/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools", "setuptools-rust"] 3 | build-backend = "setuptools.build_meta" 4 | -------------------------------------------------------------------------------- /examples/hello-world-setuppy/pytest.ini: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyO3/setuptools-rust/f21ef63b03752f98e60f51c754c738166e350d4f/examples/hello-world-setuppy/pytest.ini -------------------------------------------------------------------------------- /examples/hello-world-setuppy/python/hello_world/__init__.py: -------------------------------------------------------------------------------- 1 | from ._lib import sum_as_string 2 | 3 | __all__ = ["sum_as_string"] 4 | 5 | # It is a common practice in Python packaging to keep the extension modules 6 | # private and use Pure Python modules to wrap them. 7 | # This allows you to have a very fine control over the public API. 8 | -------------------------------------------------------------------------------- /examples/hello-world-setuppy/rust/lib.rs: -------------------------------------------------------------------------------- 1 | use pyo3::prelude::*; 2 | 3 | /// A Python module implemented in Rust. The name of this function must match 4 | /// the `lib.name` setting in the `Cargo.toml`, else Python will not be able to 5 | /// import the module. 6 | #[pymodule] 7 | mod _lib { 8 | use pyo3::prelude::*; 9 | 10 | /// Formats the sum of two numbers as string. 11 | #[pyfunction] 12 | fn sum_as_string(a: usize, b: usize) -> PyResult { 13 | Ok((a + b).to_string()) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /examples/hello-world-setuppy/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import find_packages, setup 2 | 3 | from setuptools_rust import Binding, RustExtension 4 | 5 | setup( 6 | name="hello-world", 7 | version="1.0", 8 | packages=find_packages(where="python"), 9 | package_dir={"": "python"}, 10 | rust_extensions=[ 11 | RustExtension( 12 | "hello_world._lib", 13 | # ^-- The last part of the name (e.g. "_lib") has to match lib.name 14 | # in Cargo.toml and the function name in the `.rs` file, 15 | # but you can add a prefix to nest it inside of a Python package. 16 | path="Cargo.toml", # Default value, can be omitted 17 | binding=Binding.PyO3, # Default value, can be omitted 18 | ) 19 | ], 20 | # rust extensions are not zip safe, just like C-extensions. 21 | # But `zip_safe=False` is an obsolete config that does not affect how `pip` 22 | # or `importlib.{resources,metadata}` handle the package. 23 | ) 24 | # See reference for RustExtension in https://setuptools-rust.readthedocs.io/en/latest/reference.html 25 | -------------------------------------------------------------------------------- /examples/hello-world/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "autocfg" 7 | version = "1.3.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" 10 | 11 | [[package]] 12 | name = "cfg-if" 13 | version = "1.0.0" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 16 | 17 | [[package]] 18 | name = "heck" 19 | version = "0.5.0" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 22 | 23 | [[package]] 24 | name = "hello-world" 25 | version = "0.1.0" 26 | dependencies = [ 27 | "pyo3", 28 | ] 29 | 30 | [[package]] 31 | name = "indoc" 32 | version = "2.0.5" 33 | source = "registry+https://github.com/rust-lang/crates.io-index" 34 | checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" 35 | 36 | [[package]] 37 | name = "libc" 38 | version = "0.2.154" 39 | source = "registry+https://github.com/rust-lang/crates.io-index" 40 | checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" 41 | 42 | [[package]] 43 | name = "memoffset" 44 | version = "0.9.1" 45 | source = "registry+https://github.com/rust-lang/crates.io-index" 46 | checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" 47 | dependencies = [ 48 | "autocfg", 49 | ] 50 | 51 | [[package]] 52 | name = "once_cell" 53 | version = "1.19.0" 54 | source = "registry+https://github.com/rust-lang/crates.io-index" 55 | checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" 56 | 57 | [[package]] 58 | name = "portable-atomic" 59 | version = "1.6.0" 60 | source = "registry+https://github.com/rust-lang/crates.io-index" 61 | checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0" 62 | 63 | [[package]] 64 | name = "proc-macro2" 65 | version = "1.0.82" 66 | source = "registry+https://github.com/rust-lang/crates.io-index" 67 | checksum = "8ad3d49ab951a01fbaafe34f2ec74122942fe18a3f9814c3268f1bb72042131b" 68 | dependencies = [ 69 | "unicode-ident", 70 | ] 71 | 72 | [[package]] 73 | name = "pyo3" 74 | version = "0.24.2" 75 | source = "registry+https://github.com/rust-lang/crates.io-index" 76 | checksum = "e5203598f366b11a02b13aa20cab591229ff0a89fd121a308a5df751d5fc9219" 77 | dependencies = [ 78 | "cfg-if", 79 | "indoc", 80 | "libc", 81 | "memoffset", 82 | "once_cell", 83 | "portable-atomic", 84 | "pyo3-build-config", 85 | "pyo3-ffi", 86 | "pyo3-macros", 87 | "unindent", 88 | ] 89 | 90 | [[package]] 91 | name = "pyo3-build-config" 92 | version = "0.24.2" 93 | source = "registry+https://github.com/rust-lang/crates.io-index" 94 | checksum = "99636d423fa2ca130fa5acde3059308006d46f98caac629418e53f7ebb1e9999" 95 | dependencies = [ 96 | "once_cell", 97 | "target-lexicon", 98 | ] 99 | 100 | [[package]] 101 | name = "pyo3-ffi" 102 | version = "0.24.2" 103 | source = "registry+https://github.com/rust-lang/crates.io-index" 104 | checksum = "78f9cf92ba9c409279bc3305b5409d90db2d2c22392d443a87df3a1adad59e33" 105 | dependencies = [ 106 | "libc", 107 | "pyo3-build-config", 108 | ] 109 | 110 | [[package]] 111 | name = "pyo3-macros" 112 | version = "0.24.2" 113 | source = "registry+https://github.com/rust-lang/crates.io-index" 114 | checksum = "0b999cb1a6ce21f9a6b147dcf1be9ffedf02e0043aec74dc390f3007047cecd9" 115 | dependencies = [ 116 | "proc-macro2", 117 | "pyo3-macros-backend", 118 | "quote", 119 | "syn", 120 | ] 121 | 122 | [[package]] 123 | name = "pyo3-macros-backend" 124 | version = "0.24.2" 125 | source = "registry+https://github.com/rust-lang/crates.io-index" 126 | checksum = "822ece1c7e1012745607d5cf0bcb2874769f0f7cb34c4cde03b9358eb9ef911a" 127 | dependencies = [ 128 | "heck", 129 | "proc-macro2", 130 | "pyo3-build-config", 131 | "quote", 132 | "syn", 133 | ] 134 | 135 | [[package]] 136 | name = "quote" 137 | version = "1.0.36" 138 | source = "registry+https://github.com/rust-lang/crates.io-index" 139 | checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" 140 | dependencies = [ 141 | "proc-macro2", 142 | ] 143 | 144 | [[package]] 145 | name = "syn" 146 | version = "2.0.61" 147 | source = "registry+https://github.com/rust-lang/crates.io-index" 148 | checksum = "c993ed8ccba56ae856363b1845da7266a7cb78e1d146c8a32d54b45a8b831fc9" 149 | dependencies = [ 150 | "proc-macro2", 151 | "quote", 152 | "unicode-ident", 153 | ] 154 | 155 | [[package]] 156 | name = "target-lexicon" 157 | version = "0.13.2" 158 | source = "registry+https://github.com/rust-lang/crates.io-index" 159 | checksum = "e502f78cdbb8ba4718f566c418c52bc729126ffd16baee5baa718cf25dd5a69a" 160 | 161 | [[package]] 162 | name = "unicode-ident" 163 | version = "1.0.12" 164 | source = "registry+https://github.com/rust-lang/crates.io-index" 165 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 166 | 167 | [[package]] 168 | name = "unindent" 169 | version = "0.2.3" 170 | source = "registry+https://github.com/rust-lang/crates.io-index" 171 | checksum = "c7de7d73e1754487cb58364ee906a499937a0dfabd86bcb980fa99ec8c8fa2ce" 172 | -------------------------------------------------------------------------------- /examples/hello-world/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hello-world" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | pyo3 = "0.24" 10 | 11 | [profile.release-lto] 12 | inherits = "release" 13 | lto = true 14 | 15 | [lib] 16 | # See https://github.com/PyO3/pyo3 for details 17 | name = "_lib" # private module to be nested into Python package 18 | crate-type = ["cdylib"] 19 | path = "rust/lib.rs" 20 | 21 | [[bin]] 22 | name = "print-hello" 23 | path = "rust/print_hello.rs" 24 | -------------------------------------------------------------------------------- /examples/hello-world/MANIFEST.in: -------------------------------------------------------------------------------- 1 | graft python 2 | graft rust 3 | graft tests 4 | include Cargo.toml noxfile.py build.rs 5 | global-exclude */__pycache__/* 6 | global-exclude *.pyc 7 | -------------------------------------------------------------------------------- /examples/hello-world/noxfile.py: -------------------------------------------------------------------------------- 1 | from os.path import dirname 2 | 3 | import nox 4 | 5 | SETUPTOOLS_RUST = dirname(dirname(dirname(__file__))) 6 | 7 | 8 | @nox.session() 9 | def test(session: nox.Session): 10 | session.install(SETUPTOOLS_RUST, "build", "pytest") 11 | # Ensure build works as intended 12 | session.install("--no-build-isolation", ".") 13 | # Test Rust binary 14 | session.run("print-hello") 15 | # Test script wrapper for Python entry-point 16 | session.run("sum-cli", "5", "7") 17 | session.run("rust-demo", "5", "7") 18 | # Test library 19 | session.run("pytest", "tests", *session.posargs) 20 | session.run("python", "-c", "from hello_world import _lib; print(_lib)") 21 | -------------------------------------------------------------------------------- /examples/hello-world/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools", "setuptools-rust"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [project] 6 | name = "hello-world" 7 | version = "1.0" 8 | 9 | [project.scripts] 10 | # Python entry-point wrapper to be installed in `$venv/bin` 11 | sum-cli = "hello_world.sum_cli:main" # Python function that uses Rust 12 | rust-demo = "hello_world._lib:demo" # Rust function that uses Python 13 | 14 | [tool.setuptools.packages] 15 | # Pure Python packages/modules 16 | find = { where = ["python"] } 17 | 18 | [[tool.setuptools-rust.ext-modules]] 19 | # Private Rust extension module to be nested into Python package 20 | target = "hello_world._lib" # The last part of the name (e.g. "_lib") has to match lib.name in Cargo.toml, 21 | # but you can add a prefix to nest it inside of a Python package. 22 | binding = "PyO3" # Default value, can be omitted 23 | # See reference for RustExtension in https://setuptools-rust.readthedocs.io/en/latest/reference.html 24 | 25 | [[tool.setuptools-rust.bins]] 26 | # Rust executable to be installed in `$venv/bin` 27 | target = "print-hello" # Needs to match bin.name in Cargo.toml 28 | args = ["--profile", "release-lto"] # Extra args for Cargo 29 | # See reference for RustBin in https://setuptools-rust.readthedocs.io/en/latest/reference.html 30 | # Note that you can also use Python entry-points as alternative to Rust binaries 31 | -------------------------------------------------------------------------------- /examples/hello-world/python/hello_world/__init__.py: -------------------------------------------------------------------------------- 1 | from ._lib import sum_as_string # export public parts of the binary extension 2 | 3 | __all__ = ["sum_as_string"] 4 | -------------------------------------------------------------------------------- /examples/hello-world/python/hello_world/sum_cli.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | 3 | from ._lib import sum_as_string 4 | 5 | 6 | def main(): 7 | parser = argparse.ArgumentParser("sum 2 integers") 8 | parser.add_argument("x", type=int) 9 | parser.add_argument("y", type=int) 10 | args = parser.parse_args() 11 | print(f"{args.x} + {args.y} = {sum_as_string(args.x, args.y)}") 12 | 13 | 14 | if __name__ == "__main__": 15 | main() 16 | -------------------------------------------------------------------------------- /examples/hello-world/rust/lib.rs: -------------------------------------------------------------------------------- 1 | use pyo3::prelude::*; 2 | 3 | /// A Python module implemented in Rust. The name of this function must match 4 | /// the `lib.name` setting in the `Cargo.toml`, else Python will not be able to 5 | /// import the module. 6 | #[pymodule] 7 | mod _lib { 8 | use pyo3::prelude::*; 9 | use std::env; 10 | 11 | /// Formats the sum of two numbers as string. 12 | #[pyfunction] 13 | fn sum_as_string(a: usize, b: usize) -> PyResult { 14 | Ok((a + b).to_string()) 15 | } 16 | 17 | /// Calls Python (see https://pyo3.rs for details) 18 | #[pyfunction] 19 | fn demo(py: Python) -> PyResult<()> { 20 | let argv = env::args().collect::>(); 21 | println!("argv = {:?}", argv); 22 | // argv[0]: Python path, argv[1]: program name, argv[2..]: given args 23 | 24 | let numbers: Vec = argv[2..].iter().map(|s| s.parse().unwrap()).collect(); 25 | 26 | let python_sum = PyModule::import_bound(py, "builtins")?.getattr("sum")?; 27 | let total: i32 = python_sum.call1((numbers,))?.extract()?; 28 | println!("sum({}) = {:?}", argv[2..].join(", "), total); 29 | Ok(()) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /examples/hello-world/rust/print_hello.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("Hello, world!"); 3 | } 4 | -------------------------------------------------------------------------------- /examples/hello-world/tests/test_hello_world.py: -------------------------------------------------------------------------------- 1 | import hello_world 2 | 3 | 4 | def test_sum_as_string(): 5 | assert hello_world.sum_as_string(5, 20) == "25" 6 | -------------------------------------------------------------------------------- /examples/html-py-ever/.github/workflows/upload.yml: -------------------------------------------------------------------------------- 1 | name: Upload Python Package 2 | 3 | on: 4 | release: 5 | types: [created] 6 | 7 | jobs: 8 | deploy: 9 | 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v3 14 | - name: Set up Python 15 | uses: actions/setup-python@v2 16 | with: 17 | python-version: '3.x' 18 | - name: Install dependencies 19 | run: | 20 | python -m pip install --upgrade pip 21 | curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain=stable 22 | pip install setuptools wheel twine setuptools-rust 23 | - name: Build , Test and publish 24 | env: 25 | TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} 26 | TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} 27 | run: | 28 | python setup.py install --user 29 | cd test && python3 run_all.py 30 | twine upload dist/* 31 | -------------------------------------------------------------------------------- /examples/html-py-ever/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "autocfg" 7 | version = "1.3.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" 10 | 11 | [[package]] 12 | name = "bitflags" 13 | version = "1.3.2" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 16 | 17 | [[package]] 18 | name = "bitflags" 19 | version = "2.5.0" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" 22 | 23 | [[package]] 24 | name = "byteorder" 25 | version = "1.5.0" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 28 | 29 | [[package]] 30 | name = "cfg-if" 31 | version = "1.0.0" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 34 | 35 | [[package]] 36 | name = "convert_case" 37 | version = "0.4.0" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" 40 | 41 | [[package]] 42 | name = "cssparser" 43 | version = "0.27.2" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "754b69d351cdc2d8ee09ae203db831e005560fc6030da058f86ad60c92a9cb0a" 46 | dependencies = [ 47 | "cssparser-macros", 48 | "dtoa-short", 49 | "itoa", 50 | "matches", 51 | "phf", 52 | "proc-macro2", 53 | "quote", 54 | "smallvec", 55 | "syn 1.0.109", 56 | ] 57 | 58 | [[package]] 59 | name = "cssparser-macros" 60 | version = "0.6.1" 61 | source = "registry+https://github.com/rust-lang/crates.io-index" 62 | checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" 63 | dependencies = [ 64 | "quote", 65 | "syn 2.0.61", 66 | ] 67 | 68 | [[package]] 69 | name = "derive_more" 70 | version = "0.99.17" 71 | source = "registry+https://github.com/rust-lang/crates.io-index" 72 | checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" 73 | dependencies = [ 74 | "convert_case", 75 | "proc-macro2", 76 | "quote", 77 | "rustc_version", 78 | "syn 1.0.109", 79 | ] 80 | 81 | [[package]] 82 | name = "dtoa" 83 | version = "1.0.9" 84 | source = "registry+https://github.com/rust-lang/crates.io-index" 85 | checksum = "dcbb2bf8e87535c23f7a8a321e364ce21462d0ff10cb6407820e8e96dfff6653" 86 | 87 | [[package]] 88 | name = "dtoa-short" 89 | version = "0.3.4" 90 | source = "registry+https://github.com/rust-lang/crates.io-index" 91 | checksum = "dbaceec3c6e4211c79e7b1800fb9680527106beb2f9c51904a3210c03a448c74" 92 | dependencies = [ 93 | "dtoa", 94 | ] 95 | 96 | [[package]] 97 | name = "futf" 98 | version = "0.1.5" 99 | source = "registry+https://github.com/rust-lang/crates.io-index" 100 | checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" 101 | dependencies = [ 102 | "mac", 103 | "new_debug_unreachable", 104 | ] 105 | 106 | [[package]] 107 | name = "fxhash" 108 | version = "0.2.1" 109 | source = "registry+https://github.com/rust-lang/crates.io-index" 110 | checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" 111 | dependencies = [ 112 | "byteorder", 113 | ] 114 | 115 | [[package]] 116 | name = "getrandom" 117 | version = "0.1.16" 118 | source = "registry+https://github.com/rust-lang/crates.io-index" 119 | checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" 120 | dependencies = [ 121 | "cfg-if", 122 | "libc", 123 | "wasi 0.9.0+wasi-snapshot-preview1", 124 | ] 125 | 126 | [[package]] 127 | name = "getrandom" 128 | version = "0.2.15" 129 | source = "registry+https://github.com/rust-lang/crates.io-index" 130 | checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" 131 | dependencies = [ 132 | "cfg-if", 133 | "libc", 134 | "wasi 0.11.0+wasi-snapshot-preview1", 135 | ] 136 | 137 | [[package]] 138 | name = "heck" 139 | version = "0.5.0" 140 | source = "registry+https://github.com/rust-lang/crates.io-index" 141 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 142 | 143 | [[package]] 144 | name = "html-py-ever" 145 | version = "0.1.0" 146 | dependencies = [ 147 | "kuchiki", 148 | "pyo3", 149 | "tendril", 150 | ] 151 | 152 | [[package]] 153 | name = "html5ever" 154 | version = "0.25.2" 155 | source = "registry+https://github.com/rust-lang/crates.io-index" 156 | checksum = "e5c13fb08e5d4dfc151ee5e88bae63f7773d61852f3bdc73c9f4b9e1bde03148" 157 | dependencies = [ 158 | "log", 159 | "mac", 160 | "markup5ever", 161 | "proc-macro2", 162 | "quote", 163 | "syn 1.0.109", 164 | ] 165 | 166 | [[package]] 167 | name = "indoc" 168 | version = "2.0.5" 169 | source = "registry+https://github.com/rust-lang/crates.io-index" 170 | checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" 171 | 172 | [[package]] 173 | name = "itoa" 174 | version = "0.4.8" 175 | source = "registry+https://github.com/rust-lang/crates.io-index" 176 | checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" 177 | 178 | [[package]] 179 | name = "kuchiki" 180 | version = "0.8.1" 181 | source = "registry+https://github.com/rust-lang/crates.io-index" 182 | checksum = "1ea8e9c6e031377cff82ee3001dc8026cdf431ed4e2e6b51f98ab8c73484a358" 183 | dependencies = [ 184 | "cssparser", 185 | "html5ever", 186 | "matches", 187 | "selectors", 188 | ] 189 | 190 | [[package]] 191 | name = "libc" 192 | version = "0.2.154" 193 | source = "registry+https://github.com/rust-lang/crates.io-index" 194 | checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" 195 | 196 | [[package]] 197 | name = "lock_api" 198 | version = "0.4.12" 199 | source = "registry+https://github.com/rust-lang/crates.io-index" 200 | checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" 201 | dependencies = [ 202 | "autocfg", 203 | "scopeguard", 204 | ] 205 | 206 | [[package]] 207 | name = "log" 208 | version = "0.4.21" 209 | source = "registry+https://github.com/rust-lang/crates.io-index" 210 | checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" 211 | 212 | [[package]] 213 | name = "mac" 214 | version = "0.1.1" 215 | source = "registry+https://github.com/rust-lang/crates.io-index" 216 | checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" 217 | 218 | [[package]] 219 | name = "markup5ever" 220 | version = "0.10.1" 221 | source = "registry+https://github.com/rust-lang/crates.io-index" 222 | checksum = "a24f40fb03852d1cdd84330cddcaf98e9ec08a7b7768e952fad3b4cf048ec8fd" 223 | dependencies = [ 224 | "log", 225 | "phf", 226 | "phf_codegen", 227 | "string_cache", 228 | "string_cache_codegen", 229 | "tendril", 230 | ] 231 | 232 | [[package]] 233 | name = "matches" 234 | version = "0.1.10" 235 | source = "registry+https://github.com/rust-lang/crates.io-index" 236 | checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" 237 | 238 | [[package]] 239 | name = "memoffset" 240 | version = "0.9.1" 241 | source = "registry+https://github.com/rust-lang/crates.io-index" 242 | checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" 243 | dependencies = [ 244 | "autocfg", 245 | ] 246 | 247 | [[package]] 248 | name = "new_debug_unreachable" 249 | version = "1.0.6" 250 | source = "registry+https://github.com/rust-lang/crates.io-index" 251 | checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" 252 | 253 | [[package]] 254 | name = "nodrop" 255 | version = "0.1.14" 256 | source = "registry+https://github.com/rust-lang/crates.io-index" 257 | checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" 258 | 259 | [[package]] 260 | name = "once_cell" 261 | version = "1.19.0" 262 | source = "registry+https://github.com/rust-lang/crates.io-index" 263 | checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" 264 | 265 | [[package]] 266 | name = "parking_lot" 267 | version = "0.12.2" 268 | source = "registry+https://github.com/rust-lang/crates.io-index" 269 | checksum = "7e4af0ca4f6caed20e900d564c242b8e5d4903fdacf31d3daf527b66fe6f42fb" 270 | dependencies = [ 271 | "lock_api", 272 | "parking_lot_core", 273 | ] 274 | 275 | [[package]] 276 | name = "parking_lot_core" 277 | version = "0.9.10" 278 | source = "registry+https://github.com/rust-lang/crates.io-index" 279 | checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" 280 | dependencies = [ 281 | "cfg-if", 282 | "libc", 283 | "redox_syscall", 284 | "smallvec", 285 | "windows-targets", 286 | ] 287 | 288 | [[package]] 289 | name = "phf" 290 | version = "0.8.0" 291 | source = "registry+https://github.com/rust-lang/crates.io-index" 292 | checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" 293 | dependencies = [ 294 | "phf_macros", 295 | "phf_shared 0.8.0", 296 | "proc-macro-hack", 297 | ] 298 | 299 | [[package]] 300 | name = "phf_codegen" 301 | version = "0.8.0" 302 | source = "registry+https://github.com/rust-lang/crates.io-index" 303 | checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815" 304 | dependencies = [ 305 | "phf_generator 0.8.0", 306 | "phf_shared 0.8.0", 307 | ] 308 | 309 | [[package]] 310 | name = "phf_generator" 311 | version = "0.8.0" 312 | source = "registry+https://github.com/rust-lang/crates.io-index" 313 | checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526" 314 | dependencies = [ 315 | "phf_shared 0.8.0", 316 | "rand 0.7.3", 317 | ] 318 | 319 | [[package]] 320 | name = "phf_generator" 321 | version = "0.10.0" 322 | source = "registry+https://github.com/rust-lang/crates.io-index" 323 | checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" 324 | dependencies = [ 325 | "phf_shared 0.10.0", 326 | "rand 0.8.5", 327 | ] 328 | 329 | [[package]] 330 | name = "phf_macros" 331 | version = "0.8.0" 332 | source = "registry+https://github.com/rust-lang/crates.io-index" 333 | checksum = "7f6fde18ff429ffc8fe78e2bf7f8b7a5a5a6e2a8b58bc5a9ac69198bbda9189c" 334 | dependencies = [ 335 | "phf_generator 0.8.0", 336 | "phf_shared 0.8.0", 337 | "proc-macro-hack", 338 | "proc-macro2", 339 | "quote", 340 | "syn 1.0.109", 341 | ] 342 | 343 | [[package]] 344 | name = "phf_shared" 345 | version = "0.8.0" 346 | source = "registry+https://github.com/rust-lang/crates.io-index" 347 | checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" 348 | dependencies = [ 349 | "siphasher", 350 | ] 351 | 352 | [[package]] 353 | name = "phf_shared" 354 | version = "0.10.0" 355 | source = "registry+https://github.com/rust-lang/crates.io-index" 356 | checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" 357 | dependencies = [ 358 | "siphasher", 359 | ] 360 | 361 | [[package]] 362 | name = "portable-atomic" 363 | version = "1.6.0" 364 | source = "registry+https://github.com/rust-lang/crates.io-index" 365 | checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0" 366 | 367 | [[package]] 368 | name = "ppv-lite86" 369 | version = "0.2.17" 370 | source = "registry+https://github.com/rust-lang/crates.io-index" 371 | checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" 372 | 373 | [[package]] 374 | name = "precomputed-hash" 375 | version = "0.1.1" 376 | source = "registry+https://github.com/rust-lang/crates.io-index" 377 | checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" 378 | 379 | [[package]] 380 | name = "proc-macro-hack" 381 | version = "0.5.20+deprecated" 382 | source = "registry+https://github.com/rust-lang/crates.io-index" 383 | checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" 384 | 385 | [[package]] 386 | name = "proc-macro2" 387 | version = "1.0.82" 388 | source = "registry+https://github.com/rust-lang/crates.io-index" 389 | checksum = "8ad3d49ab951a01fbaafe34f2ec74122942fe18a3f9814c3268f1bb72042131b" 390 | dependencies = [ 391 | "unicode-ident", 392 | ] 393 | 394 | [[package]] 395 | name = "pyo3" 396 | version = "0.24.2" 397 | source = "registry+https://github.com/rust-lang/crates.io-index" 398 | checksum = "e5203598f366b11a02b13aa20cab591229ff0a89fd121a308a5df751d5fc9219" 399 | dependencies = [ 400 | "cfg-if", 401 | "indoc", 402 | "libc", 403 | "memoffset", 404 | "once_cell", 405 | "portable-atomic", 406 | "pyo3-build-config", 407 | "pyo3-ffi", 408 | "pyo3-macros", 409 | "unindent", 410 | ] 411 | 412 | [[package]] 413 | name = "pyo3-build-config" 414 | version = "0.24.2" 415 | source = "registry+https://github.com/rust-lang/crates.io-index" 416 | checksum = "99636d423fa2ca130fa5acde3059308006d46f98caac629418e53f7ebb1e9999" 417 | dependencies = [ 418 | "once_cell", 419 | "target-lexicon", 420 | ] 421 | 422 | [[package]] 423 | name = "pyo3-ffi" 424 | version = "0.24.2" 425 | source = "registry+https://github.com/rust-lang/crates.io-index" 426 | checksum = "78f9cf92ba9c409279bc3305b5409d90db2d2c22392d443a87df3a1adad59e33" 427 | dependencies = [ 428 | "libc", 429 | "pyo3-build-config", 430 | ] 431 | 432 | [[package]] 433 | name = "pyo3-macros" 434 | version = "0.24.2" 435 | source = "registry+https://github.com/rust-lang/crates.io-index" 436 | checksum = "0b999cb1a6ce21f9a6b147dcf1be9ffedf02e0043aec74dc390f3007047cecd9" 437 | dependencies = [ 438 | "proc-macro2", 439 | "pyo3-macros-backend", 440 | "quote", 441 | "syn 2.0.61", 442 | ] 443 | 444 | [[package]] 445 | name = "pyo3-macros-backend" 446 | version = "0.24.2" 447 | source = "registry+https://github.com/rust-lang/crates.io-index" 448 | checksum = "822ece1c7e1012745607d5cf0bcb2874769f0f7cb34c4cde03b9358eb9ef911a" 449 | dependencies = [ 450 | "heck", 451 | "proc-macro2", 452 | "pyo3-build-config", 453 | "quote", 454 | "syn 2.0.61", 455 | ] 456 | 457 | [[package]] 458 | name = "quote" 459 | version = "1.0.36" 460 | source = "registry+https://github.com/rust-lang/crates.io-index" 461 | checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" 462 | dependencies = [ 463 | "proc-macro2", 464 | ] 465 | 466 | [[package]] 467 | name = "rand" 468 | version = "0.7.3" 469 | source = "registry+https://github.com/rust-lang/crates.io-index" 470 | checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" 471 | dependencies = [ 472 | "getrandom 0.1.16", 473 | "libc", 474 | "rand_chacha 0.2.2", 475 | "rand_core 0.5.1", 476 | "rand_hc", 477 | "rand_pcg", 478 | ] 479 | 480 | [[package]] 481 | name = "rand" 482 | version = "0.8.5" 483 | source = "registry+https://github.com/rust-lang/crates.io-index" 484 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 485 | dependencies = [ 486 | "libc", 487 | "rand_chacha 0.3.1", 488 | "rand_core 0.6.4", 489 | ] 490 | 491 | [[package]] 492 | name = "rand_chacha" 493 | version = "0.2.2" 494 | source = "registry+https://github.com/rust-lang/crates.io-index" 495 | checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" 496 | dependencies = [ 497 | "ppv-lite86", 498 | "rand_core 0.5.1", 499 | ] 500 | 501 | [[package]] 502 | name = "rand_chacha" 503 | version = "0.3.1" 504 | source = "registry+https://github.com/rust-lang/crates.io-index" 505 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 506 | dependencies = [ 507 | "ppv-lite86", 508 | "rand_core 0.6.4", 509 | ] 510 | 511 | [[package]] 512 | name = "rand_core" 513 | version = "0.5.1" 514 | source = "registry+https://github.com/rust-lang/crates.io-index" 515 | checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" 516 | dependencies = [ 517 | "getrandom 0.1.16", 518 | ] 519 | 520 | [[package]] 521 | name = "rand_core" 522 | version = "0.6.4" 523 | source = "registry+https://github.com/rust-lang/crates.io-index" 524 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 525 | dependencies = [ 526 | "getrandom 0.2.15", 527 | ] 528 | 529 | [[package]] 530 | name = "rand_hc" 531 | version = "0.2.0" 532 | source = "registry+https://github.com/rust-lang/crates.io-index" 533 | checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" 534 | dependencies = [ 535 | "rand_core 0.5.1", 536 | ] 537 | 538 | [[package]] 539 | name = "rand_pcg" 540 | version = "0.2.1" 541 | source = "registry+https://github.com/rust-lang/crates.io-index" 542 | checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" 543 | dependencies = [ 544 | "rand_core 0.5.1", 545 | ] 546 | 547 | [[package]] 548 | name = "redox_syscall" 549 | version = "0.5.1" 550 | source = "registry+https://github.com/rust-lang/crates.io-index" 551 | checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" 552 | dependencies = [ 553 | "bitflags 2.5.0", 554 | ] 555 | 556 | [[package]] 557 | name = "rustc_version" 558 | version = "0.4.0" 559 | source = "registry+https://github.com/rust-lang/crates.io-index" 560 | checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" 561 | dependencies = [ 562 | "semver", 563 | ] 564 | 565 | [[package]] 566 | name = "scopeguard" 567 | version = "1.2.0" 568 | source = "registry+https://github.com/rust-lang/crates.io-index" 569 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 570 | 571 | [[package]] 572 | name = "selectors" 573 | version = "0.22.0" 574 | source = "registry+https://github.com/rust-lang/crates.io-index" 575 | checksum = "df320f1889ac4ba6bc0cdc9c9af7af4bd64bb927bccdf32d81140dc1f9be12fe" 576 | dependencies = [ 577 | "bitflags 1.3.2", 578 | "cssparser", 579 | "derive_more", 580 | "fxhash", 581 | "log", 582 | "matches", 583 | "phf", 584 | "phf_codegen", 585 | "precomputed-hash", 586 | "servo_arc", 587 | "smallvec", 588 | "thin-slice", 589 | ] 590 | 591 | [[package]] 592 | name = "semver" 593 | version = "1.0.23" 594 | source = "registry+https://github.com/rust-lang/crates.io-index" 595 | checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" 596 | 597 | [[package]] 598 | name = "serde" 599 | version = "1.0.200" 600 | source = "registry+https://github.com/rust-lang/crates.io-index" 601 | checksum = "ddc6f9cc94d67c0e21aaf7eda3a010fd3af78ebf6e096aa6e2e13c79749cce4f" 602 | dependencies = [ 603 | "serde_derive", 604 | ] 605 | 606 | [[package]] 607 | name = "serde_derive" 608 | version = "1.0.200" 609 | source = "registry+https://github.com/rust-lang/crates.io-index" 610 | checksum = "856f046b9400cee3c8c94ed572ecdb752444c24528c035cd35882aad6f492bcb" 611 | dependencies = [ 612 | "proc-macro2", 613 | "quote", 614 | "syn 2.0.61", 615 | ] 616 | 617 | [[package]] 618 | name = "servo_arc" 619 | version = "0.1.1" 620 | source = "registry+https://github.com/rust-lang/crates.io-index" 621 | checksum = "d98238b800e0d1576d8b6e3de32827c2d74bee68bb97748dcf5071fb53965432" 622 | dependencies = [ 623 | "nodrop", 624 | "stable_deref_trait", 625 | ] 626 | 627 | [[package]] 628 | name = "siphasher" 629 | version = "0.3.11" 630 | source = "registry+https://github.com/rust-lang/crates.io-index" 631 | checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" 632 | 633 | [[package]] 634 | name = "smallvec" 635 | version = "1.13.2" 636 | source = "registry+https://github.com/rust-lang/crates.io-index" 637 | checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" 638 | 639 | [[package]] 640 | name = "stable_deref_trait" 641 | version = "1.2.0" 642 | source = "registry+https://github.com/rust-lang/crates.io-index" 643 | checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" 644 | 645 | [[package]] 646 | name = "string_cache" 647 | version = "0.8.7" 648 | source = "registry+https://github.com/rust-lang/crates.io-index" 649 | checksum = "f91138e76242f575eb1d3b38b4f1362f10d3a43f47d182a5b359af488a02293b" 650 | dependencies = [ 651 | "new_debug_unreachable", 652 | "once_cell", 653 | "parking_lot", 654 | "phf_shared 0.10.0", 655 | "precomputed-hash", 656 | "serde", 657 | ] 658 | 659 | [[package]] 660 | name = "string_cache_codegen" 661 | version = "0.5.2" 662 | source = "registry+https://github.com/rust-lang/crates.io-index" 663 | checksum = "6bb30289b722be4ff74a408c3cc27edeaad656e06cb1fe8fa9231fa59c728988" 664 | dependencies = [ 665 | "phf_generator 0.10.0", 666 | "phf_shared 0.10.0", 667 | "proc-macro2", 668 | "quote", 669 | ] 670 | 671 | [[package]] 672 | name = "syn" 673 | version = "1.0.109" 674 | source = "registry+https://github.com/rust-lang/crates.io-index" 675 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 676 | dependencies = [ 677 | "proc-macro2", 678 | "quote", 679 | "unicode-ident", 680 | ] 681 | 682 | [[package]] 683 | name = "syn" 684 | version = "2.0.61" 685 | source = "registry+https://github.com/rust-lang/crates.io-index" 686 | checksum = "c993ed8ccba56ae856363b1845da7266a7cb78e1d146c8a32d54b45a8b831fc9" 687 | dependencies = [ 688 | "proc-macro2", 689 | "quote", 690 | "unicode-ident", 691 | ] 692 | 693 | [[package]] 694 | name = "target-lexicon" 695 | version = "0.13.2" 696 | source = "registry+https://github.com/rust-lang/crates.io-index" 697 | checksum = "e502f78cdbb8ba4718f566c418c52bc729126ffd16baee5baa718cf25dd5a69a" 698 | 699 | [[package]] 700 | name = "tendril" 701 | version = "0.4.3" 702 | source = "registry+https://github.com/rust-lang/crates.io-index" 703 | checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" 704 | dependencies = [ 705 | "futf", 706 | "mac", 707 | "utf-8", 708 | ] 709 | 710 | [[package]] 711 | name = "thin-slice" 712 | version = "0.1.1" 713 | source = "registry+https://github.com/rust-lang/crates.io-index" 714 | checksum = "8eaa81235c7058867fa8c0e7314f33dcce9c215f535d1913822a2b3f5e289f3c" 715 | 716 | [[package]] 717 | name = "unicode-ident" 718 | version = "1.0.12" 719 | source = "registry+https://github.com/rust-lang/crates.io-index" 720 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 721 | 722 | [[package]] 723 | name = "unindent" 724 | version = "0.2.3" 725 | source = "registry+https://github.com/rust-lang/crates.io-index" 726 | checksum = "c7de7d73e1754487cb58364ee906a499937a0dfabd86bcb980fa99ec8c8fa2ce" 727 | 728 | [[package]] 729 | name = "utf-8" 730 | version = "0.7.6" 731 | source = "registry+https://github.com/rust-lang/crates.io-index" 732 | checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" 733 | 734 | [[package]] 735 | name = "wasi" 736 | version = "0.9.0+wasi-snapshot-preview1" 737 | source = "registry+https://github.com/rust-lang/crates.io-index" 738 | checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" 739 | 740 | [[package]] 741 | name = "wasi" 742 | version = "0.11.0+wasi-snapshot-preview1" 743 | source = "registry+https://github.com/rust-lang/crates.io-index" 744 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 745 | 746 | [[package]] 747 | name = "windows-targets" 748 | version = "0.52.5" 749 | source = "registry+https://github.com/rust-lang/crates.io-index" 750 | checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" 751 | dependencies = [ 752 | "windows_aarch64_gnullvm", 753 | "windows_aarch64_msvc", 754 | "windows_i686_gnu", 755 | "windows_i686_gnullvm", 756 | "windows_i686_msvc", 757 | "windows_x86_64_gnu", 758 | "windows_x86_64_gnullvm", 759 | "windows_x86_64_msvc", 760 | ] 761 | 762 | [[package]] 763 | name = "windows_aarch64_gnullvm" 764 | version = "0.52.5" 765 | source = "registry+https://github.com/rust-lang/crates.io-index" 766 | checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" 767 | 768 | [[package]] 769 | name = "windows_aarch64_msvc" 770 | version = "0.52.5" 771 | source = "registry+https://github.com/rust-lang/crates.io-index" 772 | checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" 773 | 774 | [[package]] 775 | name = "windows_i686_gnu" 776 | version = "0.52.5" 777 | source = "registry+https://github.com/rust-lang/crates.io-index" 778 | checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" 779 | 780 | [[package]] 781 | name = "windows_i686_gnullvm" 782 | version = "0.52.5" 783 | source = "registry+https://github.com/rust-lang/crates.io-index" 784 | checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" 785 | 786 | [[package]] 787 | name = "windows_i686_msvc" 788 | version = "0.52.5" 789 | source = "registry+https://github.com/rust-lang/crates.io-index" 790 | checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" 791 | 792 | [[package]] 793 | name = "windows_x86_64_gnu" 794 | version = "0.52.5" 795 | source = "registry+https://github.com/rust-lang/crates.io-index" 796 | checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" 797 | 798 | [[package]] 799 | name = "windows_x86_64_gnullvm" 800 | version = "0.52.5" 801 | source = "registry+https://github.com/rust-lang/crates.io-index" 802 | checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" 803 | 804 | [[package]] 805 | name = "windows_x86_64_msvc" 806 | version = "0.52.5" 807 | source = "registry+https://github.com/rust-lang/crates.io-index" 808 | checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" 809 | -------------------------------------------------------------------------------- /examples/html-py-ever/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "html-py-ever" 3 | version = "0.1.0" 4 | authors = ["konstin "] 5 | edition = "2021" 6 | 7 | [dependencies] 8 | kuchiki = "0.8.0" 9 | pyo3 = "0.24" 10 | tendril = "0.4.3" 11 | 12 | [lib] 13 | name = "html_py_ever" 14 | crate-type = ["cdylib"] 15 | path = "rust/lib.rs" 16 | 17 | [[bin]] 18 | name = "html-py-ever" 19 | path = "rust/main.rs" 20 | -------------------------------------------------------------------------------- /examples/html-py-ever/MANIFEST.in: -------------------------------------------------------------------------------- 1 | include Cargo.toml 2 | recursive-include rust * 3 | recursive-include python * 4 | -------------------------------------------------------------------------------- /examples/html-py-ever/README.md: -------------------------------------------------------------------------------- 1 | # html-py-ever 2 | 3 | Demoing how to use [html5ever](https://github.com/servo/html5ever) through [kuchiki](https://github.com/kuchiki-rs/kuchiki) to speed up html parsing and css-selecting. 4 | 5 | ## Usage 6 | 7 | `parse_file` and `parse_text` return a parsed `Document`, which then lets you select elements by css selectors using the `select` method. All elements are returned as strings 8 | 9 | ## Benchmarking 10 | 11 | Run `tox -e py`. 12 | 13 | ## Example benchmark results 14 | 15 | Running on Intel(R) Core(TM) i7-10750H CPU @ 2.60GHz with Python 3.9.5 and Rust 1.55.0 16 | 17 | **run_all.py** 18 | 19 | ``` 20 | $ ./test/run_all.py 21 | /home/david/dev/setuptools-rust/examples/html-py-ever/test/empty.html 0 0.000026s 22 | Parse py 0.000070s 2.693x 23 | Select py 0.000105s 12.221x 24 | Parse lxml 0.000209s 8.023x 25 | Select lxml 0.000151s 17.535x 26 | /home/david/dev/setuptools-rust/examples/html-py-ever/test/small.html 0 0.000032s 27 | Parse py 0.000286s 9.066x 28 | Select py 0.000080s 3.038x 29 | Parse lxml 0.000396s 12.525x 30 | Select lxml 0.000087s 3.264x 31 | /home/david/dev/setuptools-rust/examples/html-py-ever/test/rust.html 733 0.015430s 32 | Parse py 0.257859s 16.711x 33 | Select py 0.024799s 32.135x 34 | Parse lxml 0.166995s 10.822x 35 | Select lxml 0.024668s 31.966x 36 | /home/david/dev/setuptools-rust/examples/html-py-ever/test/python.html 1518 0.065441s 37 | Parse py 1.371898s 20.964x 38 | Select py 0.138580s 43.215x 39 | Parse lxml 0.917728s 14.024x 40 | Select lxml 0.146618s 45.721x 41 | /home/david/dev/setuptools-rust/examples/html-py-ever/test/monty-python.html 1400 0.007463s 42 | Parse py 0.184073s 24.664x 43 | Select py 0.015596s 29.757x 44 | Parse lxml 0.076753s 10.284x 45 | Select lxml 0.017100s 32.628x 46 | ``` 47 | 48 | **test_parsing.py** 49 | 50 | ``` 51 | ------------------------------------------------------------------------------------------------------------------------------------------------ benchmark: 10 tests ------------------------------------------------------------------------------------------------------------------------------------------------- 52 | Name (time in us) Min Max Mean 53 | StdDev Median IQR Outliers OPS Rounds Iterations 54 | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 55 | test_bench_parsing_rust[/home/david/dev/setuptools-rust/examples/html-py-ever/test/empty.html] 2.1000 (1.0) 155.2000 (1.0) 2.7308 (1.0) 56 | 2.0262 (1.0) 2.4000 (1.0) 0.1000 (1.0) 341;2539 366,186.4074 (1.0) 18762 1 57 | test_bench_parsing_rust[/home/david/dev/setuptools-rust/examples/html-py-ever/test/small.html] 9.6000 (4.57) 559.3000 (3.60) 10.4213 (3.82) 58 | 4.6027 (2.27) 10.2000 (4.25) 0.3000 (3.00) 294;850 95,957.4914 (0.26) 24571 1 59 | test_bench_parsing_python[/home/david/dev/setuptools-rust/examples/html-py-ever/test/empty.html] 24.1000 (11.48) 525.8000 (3.39) 30.5076 (11.17) 13.4886 (6.66) 26.6000 (11.08) 1.7000 (17.00) 919;1597 32,778.7273 (0.09) 10236 1 60 | test_bench_parsing_python[/home/david/dev/setuptools-rust/examples/html-py-ever/test/small.html] 187.2000 (89.14) 582.8000 (3.76) 215.0146 (78.74) 35.1708 (17.36) 200.6000 (83.58) 21.8000 (218.00) 340;336 4,650.8477 (0.01) 3158 1 61 | test_bench_parsing_rust[/home/david/dev/setuptools-rust/examples/html-py-ever/test/monty-python.html] 6,668.5000 (>1000.0) 16,104.0000 (103.76) 7,878.4104 (>1000.0) 1,223.6380 (603.90) 7,504.4000 (>1000.0) 776.1000 (>1000.0) 10;9 126.9292 (0.00) 134 1 62 | test_bench_parsing_rust[/home/david/dev/setuptools-rust/examples/html-py-ever/test/rust.html] 14,551.0000 (>1000.0) 16,078.2000 (103.60) 15,117.5525 (>1000.0) 237.0122 (116.97) 15,072.3000 (>1000.0) 155.1500 (>1000.0) 11;10 66.1483 (0.00) 61 1 63 | test_bench_parsing_rust[/home/david/dev/setuptools-rust/examples/html-py-ever/test/python.html] 69,374.7000 (>1000.0) 88,828.3000 (572.35) 73,736.0067 (>1000.0) 6,102.6659 (>1000.0) 71,318.8000 (>1000.0) 3,288.9000 (>1000.0) 2;3 13.5619 (0.00) 15 1 64 | test_bench_parsing_python[/home/david/dev/setuptools-rust/examples/html-py-ever/test/monty-python.html] 119,087.1000 (>1000.0) 140,231.5000 (903.55) 124,006.4333 (>1000.0) 8,041.2631 (>1000.0) 120,803.8000 (>1000.0) 2,573.4000 (>1000.0) 1;1 8.0641 (0.00) 6 1 65 | test_bench_parsing_python[/home/david/dev/setuptools-rust/examples/html-py-ever/test/rust.html] 256,079.1000 (>1000.0) 283,591.4000 (>1000.0) 272,005.6800 (>1000.0) 11,993.9084 (>1000.0) 276,622.5000 (>1000.0) 20,551.0250 (>1000.0) 1;0 3.6764 (0.00) 5 1 66 | test_bench_parsing_python[/home/david/dev/setuptools-rust/examples/html-py-ever/test/python.html] 1,388,658.5000 (>1000.0) 1,417,244.1000 (>1000.0) 1,407,207.0600 (>1000.0) 11,658.8211 (>1000.0) 1,407,273.7000 (>1000.0) 15,582.4000 (>1000.0) 1;0 0.7106 (0.00) 5 1 67 | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 68 | ``` 69 | 70 | **test_selector.py** 71 | 72 | ``` 73 | -------------------------------------------------------------------------------------------------------------------------------------------------------- benchmark: 10 tests -------------------------------------------------------------------------------------------------------------------------------------------------------- 74 | Name (time in ns) Min Max Mean 75 | StdDev Median IQR Outliers OPS Rounds Iterations 76 | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 77 | test_bench_selector_rust[/home/david/dev/setuptools-rust/examples/html-py-ever/test/empty.html] 799.9997 (1.0) 682,700.0007 (11.08) 1,079.2724 (1.0) 5,056.3097 (6.85) 999.9994 (1.0) 99.9999 (>1000.0) 87;499 926,550.1666 (1.0) 53764 1 78 | test_bench_selector_rust[/home/david/dev/setuptools-rust/examples/html-py-ever/test/small.html] 899.9996 (1.12) 102,799.9997 (1.67) 1,134.4583 (1.05) 738.3883 (1.0) 1,100.0002 (1.10) 0.0009 (1.0) 664;51478 881,477.9722 (0.95) 158731 1 79 | test_bench_selector_python[/home/david/dev/setuptools-rust/examples/html-py-ever/test/empty.html] 7,000.0006 (8.75) 61,600.0007 (1.0) 7,896.1815 (7.32) 2,197.4336 (2.98) 7,600.0006 (7.60) 300.0005 (>1000.0) 159;411 126,643.4926 (0.14) 9192 1 80 | test_bench_selector_python[/home/david/dev/setuptools-rust/examples/html-py-ever/test/small.html] 24,600.0000 (30.75) 1,270,499.9999 (20.62) 26,831.8769 (24.86) 10,644.6522 (14.42) 26,300.0002 (26.30) 599.9991 (>1000.0) 237;871 37,269.1035 (0.04) 15083 1 81 | test_bench_selector_rust[/home/david/dev/setuptools-rust/examples/html-py-ever/test/monty-python.html] 288,299.9997 (360.38) 1,328,100.0001 (21.56) 330,258.3420 (306.00) 36,035.7334 (48.80) 323,899.9998 (323.90) 9,299.9999 (>1000.0) 148;273 3,027.9326 (0.00) 1930 1 82 | test_bench_selector_rust[/home/david/dev/setuptools-rust/examples/html-py-ever/test/rust.html] 323,400.0005 (404.25) 2,079,099.9997 (33.75) 361,308.3042 (334.77) 61,858.2904 (83.77) 354,000.0002 (354.00) 16,300.0004 (>1000.0) 39;115 2,767.7194 (0.00) 1144 1 83 | test_bench_selector_rust[/home/david/dev/setuptools-rust/examples/html-py-ever/test/python.html] 2,952,400.0001 (>1000.0) 4,020,800.0000 (65.27) 3,093,027.3333 (>1000.0) 117,355.5598 (158.93) 3,067,149.9999 (>1000.0) 82,000.0000 (>1000.0) 26;18 323.3078 (0.00) 300 1 84 | test_bench_selector_python[/home/david/dev/setuptools-rust/examples/html-py-ever/test/monty-python.html] 14,984,299.9999 (>1000.0) 16,412,400.0003 (266.44) 15,363,483.8710 (>1000.0) 385,910.8544 (522.64) 15,212,300.0003 (>1000.0) 228,699.9988 (>1000.0) 9;9 65.0894 (0.00) 62 1 85 | test_bench_selector_python[/home/david/dev/setuptools-rust/examples/html-py-ever/test/rust.html] 22,151,300.0006 (>1000.0) 27,046,000.0002 (439.06) 24,152,934.1463 (>1000.0) 1,014,946.2212 (>1000.0) 23,943,899.9997 (>1000.0) 420,224.9995 (>1000.0) 9;10 41.4028 (0.00) 41 1 86 | test_bench_selector_python[/home/david/dev/setuptools-rust/examples/html-py-ever/test/python.html] 139,399,100.0004 (>1000.0) 148,564,900.0006 (>1000.0) 143,540,675.0002 (>1000.0) 3,466,075.6279 (>1000.0) 143,609,199.9999 (>1000.0) 6,241,799.9993 (>1000.0) 4;0 6.9667 (0.00) 8 1 87 | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 88 | ``` 89 | 90 | ## build instructions 91 | 92 | **Requirements:** 93 | 94 | - rust-toolchain (i.e cargo, rustc) 95 | - python3-dev or python3-devel 96 | 97 | **building and installing** 98 | ``` 99 | pip install setuptools-rust setuptools 100 | python3 setup.py install --user 101 | ``` 102 | 103 | github workflows example to test and upload the module to pypi [here](./.github/workflows/upload.yml) 104 | -------------------------------------------------------------------------------- /examples/html-py-ever/build-wheels.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -ex 3 | 4 | curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y 5 | export PATH="$HOME/.cargo/bin:$PATH" 6 | 7 | # Compile wheels 8 | for PYBIN in /opt/python/cp{37,38,39,310}*/bin; do 9 | rm -rf /io/build/ 10 | "${PYBIN}/pip" install -U setuptools setuptools-rust 11 | "${PYBIN}/pip" wheel /io/ -w /io/dist/ --no-deps 12 | done 13 | 14 | # Bundle external shared libraries into the wheels 15 | for whl in /io/dist/*{cp37,cp38,cp39,cp310}*.whl; do 16 | auditwheel repair "$whl" -w /io/dist/ 17 | done 18 | 19 | # Install packages and test 20 | for PYBIN in /opt/python/cp{37,38,39,310}*/bin; do 21 | "${PYBIN}/pip" install html-py-ever -f /io/dist/ 22 | done 23 | -------------------------------------------------------------------------------- /examples/html-py-ever/noxfile.py: -------------------------------------------------------------------------------- 1 | from os.path import dirname 2 | 3 | import nox 4 | 5 | SETUPTOOLS_RUST = dirname(dirname(dirname(__file__))) 6 | 7 | 8 | @nox.session() 9 | def test(session: nox.Session): 10 | session.install(SETUPTOOLS_RUST, "pytest", "pytest-benchmark", "beautifulsoup4") 11 | # Ensure build uses version of setuptools-rust under development 12 | session.install("--no-build-isolation", ".") 13 | # Test Python package 14 | session.run("pytest", *session.posargs) 15 | -------------------------------------------------------------------------------- /examples/html-py-ever/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools", "setuptools-rust"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [project] 6 | name = "html-py-ever" 7 | version = "0.1.0" 8 | license = { text = "MIT" } 9 | readme = "README.md" 10 | requires-python = ">=3.6" 11 | classifiers = [ 12 | "License :: OSI Approved :: MIT License", 13 | "Intended Audience :: Developers", 14 | "Programming Language :: Python :: 3", 15 | "Programming Language :: Python :: 3.6", 16 | "Programming Language :: Python :: 3.7", 17 | "Programming Language :: Python :: 3.8", 18 | "Programming Language :: Python :: 3.9", 19 | "Development Status :: 5 - Production/Stable", 20 | "Operating System :: POSIX", 21 | "Operating System :: MacOS :: MacOS X", 22 | "Operating System :: Microsoft :: Windows", 23 | ] 24 | 25 | [project.urls] 26 | Repository = "https://github.com/PyO3/setuptools-rust/blob/main/examples/html-py-ever" 27 | 28 | [tool.setuptools.packages] 29 | # Pure Python packages/modules 30 | find = { where = ["python"] } 31 | 32 | 33 | [[tool.setuptools-rust.ext-modules]] 34 | # Rust extension module to be nested into Python package 35 | target = "html_py_ever.html_py_ever" 36 | # ^-- The last part of the name (e.g. "html_py_ever") has to match lib.name in Cargo.toml, 37 | # but you can add a prefix to nest it inside of a Python package. 38 | # See reference for RustExtension in https://setuptools-rust.readthedocs.io/en/latest/reference.html 39 | -------------------------------------------------------------------------------- /examples/html-py-ever/pytest.ini: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyO3/setuptools-rust/f21ef63b03752f98e60f51c754c738166e350d4f/examples/html-py-ever/pytest.ini -------------------------------------------------------------------------------- /examples/html-py-ever/python/html_py_ever/__init__.py: -------------------------------------------------------------------------------- 1 | from .html_py_ever import * 2 | -------------------------------------------------------------------------------- /examples/html-py-ever/requirements.txt: -------------------------------------------------------------------------------- 1 | setuptools-rust 2 | setuptools>=70.1.0 3 | -------------------------------------------------------------------------------- /examples/html-py-ever/rust/lib.rs: -------------------------------------------------------------------------------- 1 | use pyo3::prelude::*; 2 | 3 | #[pymodule] 4 | mod html_py_ever { 5 | use pyo3::prelude::*; 6 | use std::io::Read; 7 | use std::path::Path; 8 | use tendril::stream::TendrilSink; 9 | 10 | /// A parsed html document 11 | #[pyclass(unsendable)] 12 | struct Document { 13 | node: kuchiki::NodeRef, 14 | } 15 | 16 | #[pymethods] 17 | impl Document { 18 | /// Returns the selected elements as strings 19 | fn select(&self, selector: &str) -> Vec { 20 | self.node 21 | .select(selector) 22 | .unwrap() 23 | .map(|css_match| css_match.text_contents()) 24 | .collect() 25 | } 26 | } 27 | 28 | impl Document { 29 | fn from_reader(reader: &mut impl Read) -> PyResult { 30 | let node = kuchiki::parse_html().from_utf8().read_from(reader)?; 31 | Ok(Document { node }) 32 | } 33 | 34 | fn from_file(path: &Path) -> PyResult { 35 | let node = kuchiki::parse_html().from_utf8().from_file(path)?; 36 | Ok(Document { node }) 37 | } 38 | } 39 | 40 | /// Parses the File from the specified Path into a document 41 | #[pyfunction] 42 | fn parse_file(path: &str) -> PyResult { 43 | let document = Document::from_file(Path::new(path))?; 44 | Ok(document) 45 | } 46 | 47 | /// Parses the given html test into a document 48 | #[pyfunction] 49 | fn parse_text(text: &str) -> PyResult { 50 | let document = Document::from_reader(&mut text.as_bytes())?; 51 | Ok(document) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /examples/html-py-ever/rust/main.rs: -------------------------------------------------------------------------------- 1 | //! Pure rust version for comparing with python based calls 2 | 3 | use kuchiki; 4 | 5 | use std::env; 6 | use std::path::PathBuf; 7 | use std::time::Instant; 8 | use tendril::stream::TendrilSink; 9 | 10 | fn main() { 11 | let path = PathBuf::from( 12 | env::args() 13 | .nth(1) 14 | .expect("You need to pass the file name as first argument"), 15 | ); 16 | 17 | let now = Instant::now(); 18 | let document = kuchiki::parse_html().from_utf8().from_file(&path).unwrap(); 19 | println!("{:?}", now.elapsed()); 20 | let now2 = Instant::now(); 21 | let links: Vec = document 22 | .select("a[href]") 23 | .unwrap() 24 | .map(|css_match| css_match.text_contents()) 25 | .collect(); 26 | println!("{} {:?}", links.len(), now2.elapsed()); 27 | } 28 | -------------------------------------------------------------------------------- /examples/html-py-ever/tests/conftest.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import pytest 3 | 4 | if sys.platform == "emscripten": 5 | 6 | @pytest.fixture 7 | def benchmark(): 8 | def result(func, *args, **kwargs): 9 | return func(*args, **kwargs) 10 | 11 | return result 12 | -------------------------------------------------------------------------------- /examples/html-py-ever/tests/empty.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyO3/setuptools-rust/f21ef63b03752f98e60f51c754c738166e350d4f/examples/html-py-ever/tests/empty.html -------------------------------------------------------------------------------- /examples/html-py-ever/tests/run_all.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import os 3 | from glob import glob 4 | from time import perf_counter 5 | from typing import Tuple 6 | 7 | import html_py_ever 8 | from bs4 import BeautifulSoup 9 | 10 | try: 11 | import lxml 12 | except ImportError: 13 | lxml = None 14 | 15 | 16 | def rust(filename: str) -> Tuple[int, float, float]: 17 | start_load = perf_counter() 18 | doc = html_py_ever.parse_file(filename) 19 | end_load = perf_counter() 20 | 21 | start_search = perf_counter() 22 | links = doc.select("a[href]") 23 | end_search = perf_counter() 24 | 25 | return len(links), end_load - start_load, end_search - start_search 26 | 27 | 28 | def python(filename: str, parser: str) -> Tuple[int, float, float]: 29 | start_load = perf_counter() 30 | with open(filename, encoding="utf8") as fp: 31 | soup = BeautifulSoup(fp, parser) 32 | 33 | end_load = perf_counter() 34 | start_search = perf_counter() 35 | 36 | links = soup.select("a[href]") 37 | end_search = perf_counter() 38 | 39 | return len(links), end_load - start_load, end_search - start_search 40 | 41 | 42 | def main(): 43 | files_glob = os.path.abspath(os.path.join(os.path.dirname(__file__), "*.html")) 44 | for filename in glob(files_glob): 45 | count_rs, parse_rs, select_rs = rust(filename) 46 | count_py, parse_py, select_py = python(filename, "html.parser") 47 | assert count_rs == count_py 48 | print(f"{filename} {count_rs} {parse_rs:6f}s") 49 | print(f"Parse py {parse_py:6f}s {parse_py / parse_rs:6.3f}x") 50 | print(f"Select py {select_py:6f}s {select_py / select_rs:6.3f}x") 51 | 52 | if lxml is not None: 53 | count_lxml, parse_lxml, select_lxml = python(filename, "lxml") 54 | assert count_rs == count_lxml 55 | print(f"Parse lxml {parse_lxml:6f}s {parse_lxml / parse_rs:6.3f}x") 56 | print(f"Select lxml {select_lxml:6f}s {select_lxml / select_rs:6.3f}x") 57 | 58 | 59 | if __name__ == "__main__": 60 | main() 61 | -------------------------------------------------------------------------------- /examples/html-py-ever/tests/small.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Title 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /examples/html-py-ever/tests/test_parsing.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from glob import glob 3 | import os 4 | 5 | import html_py_ever 6 | import pytest 7 | from bs4 import BeautifulSoup 8 | from html_py_ever import Document 9 | 10 | 11 | HTML_FILES = glob(os.path.join(os.path.dirname(__file__), "*.html")) 12 | 13 | 14 | def rust(filename: str) -> Document: 15 | return html_py_ever.parse_file(filename) 16 | 17 | 18 | def python(filename: str) -> BeautifulSoup: 19 | with open(filename, encoding="utf8") as fp: 20 | soup = BeautifulSoup(fp, "html.parser") 21 | 22 | return soup 23 | 24 | 25 | @pytest.mark.parametrize("filename", HTML_FILES) 26 | def test_bench_parsing_rust(benchmark, filename): 27 | benchmark(rust, filename) 28 | 29 | 30 | @pytest.mark.parametrize("filename", HTML_FILES) 31 | def test_bench_parsing_python(benchmark, filename): 32 | benchmark(python, filename) 33 | -------------------------------------------------------------------------------- /examples/html-py-ever/tests/test_selector.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from glob import glob 3 | import os 4 | 5 | import html_py_ever 6 | import pytest 7 | from bs4 import BeautifulSoup 8 | 9 | 10 | HTML_FILES = glob(os.path.join(os.path.dirname(__file__), "*.html")) 11 | 12 | 13 | @pytest.mark.parametrize("filename", HTML_FILES) 14 | def test_bench_selector_rust(benchmark, filename): 15 | document = html_py_ever.parse_file(filename) 16 | benchmark(document.select, "a[href]") 17 | 18 | 19 | @pytest.mark.parametrize("filename", HTML_FILES) 20 | def test_bench_selector_python(benchmark, filename): 21 | with open(filename, encoding="utf8") as fp: 22 | soup = BeautifulSoup(fp, "html.parser") 23 | benchmark(soup.select, "a[href]") 24 | -------------------------------------------------------------------------------- /examples/namespace_package/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [target.x86_64-apple-darwin] 2 | rustflags = [ 3 | "-C", "link-arg=-undefined", 4 | "-C", "link-arg=dynamic_lookup", 5 | ] 6 | 7 | [target.aarch64-apple-darwin] 8 | rustflags = [ 9 | "-C", "link-arg=-undefined", 10 | "-C", "link-arg=dynamic_lookup", 11 | ] 12 | -------------------------------------------------------------------------------- /examples/namespace_package/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "autocfg" 7 | version = "1.3.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" 10 | 11 | [[package]] 12 | name = "cfg-if" 13 | version = "1.0.0" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 16 | 17 | [[package]] 18 | name = "heck" 19 | version = "0.5.0" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 22 | 23 | [[package]] 24 | name = "indoc" 25 | version = "2.0.5" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" 28 | 29 | [[package]] 30 | name = "libc" 31 | version = "0.2.154" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" 34 | 35 | [[package]] 36 | name = "memoffset" 37 | version = "0.9.1" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" 40 | dependencies = [ 41 | "autocfg", 42 | ] 43 | 44 | [[package]] 45 | name = "namespace_package_rust" 46 | version = "0.1.0" 47 | dependencies = [ 48 | "pyo3", 49 | ] 50 | 51 | [[package]] 52 | name = "once_cell" 53 | version = "1.19.0" 54 | source = "registry+https://github.com/rust-lang/crates.io-index" 55 | checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" 56 | 57 | [[package]] 58 | name = "portable-atomic" 59 | version = "1.6.0" 60 | source = "registry+https://github.com/rust-lang/crates.io-index" 61 | checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0" 62 | 63 | [[package]] 64 | name = "proc-macro2" 65 | version = "1.0.82" 66 | source = "registry+https://github.com/rust-lang/crates.io-index" 67 | checksum = "8ad3d49ab951a01fbaafe34f2ec74122942fe18a3f9814c3268f1bb72042131b" 68 | dependencies = [ 69 | "unicode-ident", 70 | ] 71 | 72 | [[package]] 73 | name = "pyo3" 74 | version = "0.24.2" 75 | source = "registry+https://github.com/rust-lang/crates.io-index" 76 | checksum = "e5203598f366b11a02b13aa20cab591229ff0a89fd121a308a5df751d5fc9219" 77 | dependencies = [ 78 | "cfg-if", 79 | "indoc", 80 | "libc", 81 | "memoffset", 82 | "once_cell", 83 | "portable-atomic", 84 | "pyo3-build-config", 85 | "pyo3-ffi", 86 | "pyo3-macros", 87 | "unindent", 88 | ] 89 | 90 | [[package]] 91 | name = "pyo3-build-config" 92 | version = "0.24.2" 93 | source = "registry+https://github.com/rust-lang/crates.io-index" 94 | checksum = "99636d423fa2ca130fa5acde3059308006d46f98caac629418e53f7ebb1e9999" 95 | dependencies = [ 96 | "once_cell", 97 | "target-lexicon", 98 | ] 99 | 100 | [[package]] 101 | name = "pyo3-ffi" 102 | version = "0.24.2" 103 | source = "registry+https://github.com/rust-lang/crates.io-index" 104 | checksum = "78f9cf92ba9c409279bc3305b5409d90db2d2c22392d443a87df3a1adad59e33" 105 | dependencies = [ 106 | "libc", 107 | "pyo3-build-config", 108 | ] 109 | 110 | [[package]] 111 | name = "pyo3-macros" 112 | version = "0.24.2" 113 | source = "registry+https://github.com/rust-lang/crates.io-index" 114 | checksum = "0b999cb1a6ce21f9a6b147dcf1be9ffedf02e0043aec74dc390f3007047cecd9" 115 | dependencies = [ 116 | "proc-macro2", 117 | "pyo3-macros-backend", 118 | "quote", 119 | "syn", 120 | ] 121 | 122 | [[package]] 123 | name = "pyo3-macros-backend" 124 | version = "0.24.2" 125 | source = "registry+https://github.com/rust-lang/crates.io-index" 126 | checksum = "822ece1c7e1012745607d5cf0bcb2874769f0f7cb34c4cde03b9358eb9ef911a" 127 | dependencies = [ 128 | "heck", 129 | "proc-macro2", 130 | "pyo3-build-config", 131 | "quote", 132 | "syn", 133 | ] 134 | 135 | [[package]] 136 | name = "quote" 137 | version = "1.0.36" 138 | source = "registry+https://github.com/rust-lang/crates.io-index" 139 | checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" 140 | dependencies = [ 141 | "proc-macro2", 142 | ] 143 | 144 | [[package]] 145 | name = "syn" 146 | version = "2.0.61" 147 | source = "registry+https://github.com/rust-lang/crates.io-index" 148 | checksum = "c993ed8ccba56ae856363b1845da7266a7cb78e1d146c8a32d54b45a8b831fc9" 149 | dependencies = [ 150 | "proc-macro2", 151 | "quote", 152 | "unicode-ident", 153 | ] 154 | 155 | [[package]] 156 | name = "target-lexicon" 157 | version = "0.13.2" 158 | source = "registry+https://github.com/rust-lang/crates.io-index" 159 | checksum = "e502f78cdbb8ba4718f566c418c52bc729126ffd16baee5baa718cf25dd5a69a" 160 | 161 | [[package]] 162 | name = "unicode-ident" 163 | version = "1.0.12" 164 | source = "registry+https://github.com/rust-lang/crates.io-index" 165 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 166 | 167 | [[package]] 168 | name = "unindent" 169 | version = "0.2.3" 170 | source = "registry+https://github.com/rust-lang/crates.io-index" 171 | checksum = "c7de7d73e1754487cb58364ee906a499937a0dfabd86bcb980fa99ec8c8fa2ce" 172 | -------------------------------------------------------------------------------- /examples/namespace_package/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "namespace_package_rust" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | # When omitted, the name field will be assumed to have the same value as package.name 8 | # https://doc.rust-lang.org/cargo/reference/cargo-targets.html#the-name-field 9 | crate-type = ["cdylib", "rlib"] 10 | path = "rust/lib.rs" # When omitted, the value of "src/lib.rs" is assumed 11 | # See https://doc.rust-lang.org/cargo/reference/cargo-targets.html#library 12 | # 13 | # If [lib] is not specified, but the file ``src/lib.rs`` exists, 14 | # you can also rely on an implicit definition which will behave similarly to: 15 | # 16 | # [lib] 17 | # name = 18 | # path = "src/lib.rs" 19 | 20 | [dependencies] 21 | pyo3 = "0.24" 22 | -------------------------------------------------------------------------------- /examples/namespace_package/Cross.toml: -------------------------------------------------------------------------------- 1 | [target.aarch64-unknown-linux-gnu] 2 | image = "cross-pyo3:aarch64-unknown-linux-gnu" 3 | env.passthrough = [ 4 | "RUST_BACKTRACE", 5 | "RUST_LOG", 6 | "PYO3_CROSS_LIB_DIR", 7 | ] 8 | -------------------------------------------------------------------------------- /examples/namespace_package/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM quay.io/pypa/manylinux2014_aarch64 AS manylinux 2 | 3 | FROM ghcr.io/cross-rs/aarch64-unknown-linux-gnu:edge 4 | 5 | ARG STANDALONE_PYTHON_RELEASE 6 | ARG STANDALONE_PYTHON_VERSION 7 | 8 | RUN test -n "$STANDALONE_PYTHON_RELEASE" || (echo "STANDALONE_PYTHON_RELEASE not set" && false) 9 | RUN test -n "$STANDALONE_PYTHON_VERSION" || (echo "STANDALONE_PYTHON_VERSION not set" && false) 10 | 11 | RUN curl -L https://github.com/indygreg/python-build-standalone/releases/download/${STANDALONE_PYTHON_RELEASE}/cpython-${STANDALONE_PYTHON_VERSION}+${STANDALONE_PYTHON_RELEASE}-x86_64-unknown-linux-gnu-install_only.tar.gz | tar -xz -C /usr/local 12 | 13 | ENV PATH=/usr/local/python/bin:$PATH 14 | 15 | COPY --from=manylinux /opt/_internal /opt/_internal 16 | COPY --from=manylinux /opt/python /opt/python 17 | -------------------------------------------------------------------------------- /examples/namespace_package/MANIFEST.in: -------------------------------------------------------------------------------- 1 | include Cargo.toml 2 | include Cross.toml 3 | recursive-include rust * 4 | recursive-include python * 5 | -------------------------------------------------------------------------------- /examples/namespace_package/noxfile.py: -------------------------------------------------------------------------------- 1 | from os.path import dirname 2 | 3 | import nox 4 | 5 | SETUPTOOLS_RUST = dirname(dirname(dirname(__file__))) 6 | 7 | 8 | @nox.session() 9 | def test(session: nox.Session): 10 | session.install(SETUPTOOLS_RUST) 11 | # Ensure build uses version of setuptools-rust under development 12 | session.install("--no-build-isolation", ".[dev]") 13 | # Test Python package 14 | session.run("pytest", *session.posargs) 15 | -------------------------------------------------------------------------------- /examples/namespace_package/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools", "setuptools-rust"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [project] 6 | name="namespace_package" 7 | version="0.1.0" 8 | 9 | [project.optional-dependencies] 10 | dev = ["pytest"] 11 | 12 | [tool.pytest.ini_options] 13 | 14 | [tool.setuptools.packages] 15 | # Pure Python packages/modules 16 | find = { where = ["python"] } 17 | 18 | [[tool.setuptools-rust.ext-modules]] 19 | target = "namespace_package.rust" 20 | # ^-- The last part of the target name (e.g. "rust") should match lib.name in Cargo.toml, 21 | # but you can add a prefix to nest it inside of a parent Python package or namespace. 22 | # Note that lib.name may not be defined in the Cargo.toml, but you still 23 | # have to match the name of the function with the `#[pymodule]` attribute. 24 | path = "Cargo.toml" 25 | # ^-- Default value for cargo's manifest (can be omitted) 26 | # Each manifest can have a single [lib] definition. 27 | # To specify multiple extension modules you can use different toml files (one each). 28 | binding = "PyO3" # Default value, can be omitted 29 | debug = false 30 | # See reference for RustExtension in https://setuptools-rust.readthedocs.io/en/latest/reference.html 31 | -------------------------------------------------------------------------------- /examples/namespace_package/python/namespace_package/python/__init__.py: -------------------------------------------------------------------------------- 1 | def python_func(): 2 | return 15 3 | -------------------------------------------------------------------------------- /examples/namespace_package/rust/lib.rs: -------------------------------------------------------------------------------- 1 | use pyo3::prelude::*; 2 | 3 | /// A Python module implemented in Rust. 4 | #[pymodule] 5 | mod rust { 6 | use pyo3::prelude::*; 7 | 8 | #[pyfunction] 9 | fn rust_func() -> usize { 10 | 14 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /examples/namespace_package/tests/test_namespace_package.py: -------------------------------------------------------------------------------- 1 | from namespace_package import rust, python 2 | 3 | 4 | def test_rust(): 5 | assert rust.rust_func() == 14 6 | 7 | 8 | def test_cffi(): 9 | assert python.python_func() == 15 10 | -------------------------------------------------------------------------------- /examples/rust_with_cffi/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "autocfg" 7 | version = "1.3.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" 10 | 11 | [[package]] 12 | name = "cfg-if" 13 | version = "1.0.0" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 16 | 17 | [[package]] 18 | name = "heck" 19 | version = "0.5.0" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 22 | 23 | [[package]] 24 | name = "indoc" 25 | version = "2.0.5" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" 28 | 29 | [[package]] 30 | name = "libc" 31 | version = "0.2.154" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" 34 | 35 | [[package]] 36 | name = "memoffset" 37 | version = "0.9.1" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" 40 | dependencies = [ 41 | "autocfg", 42 | ] 43 | 44 | [[package]] 45 | name = "once_cell" 46 | version = "1.19.0" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" 49 | 50 | [[package]] 51 | name = "portable-atomic" 52 | version = "1.6.0" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0" 55 | 56 | [[package]] 57 | name = "proc-macro2" 58 | version = "1.0.82" 59 | source = "registry+https://github.com/rust-lang/crates.io-index" 60 | checksum = "8ad3d49ab951a01fbaafe34f2ec74122942fe18a3f9814c3268f1bb72042131b" 61 | dependencies = [ 62 | "unicode-ident", 63 | ] 64 | 65 | [[package]] 66 | name = "pyo3" 67 | version = "0.24.2" 68 | source = "registry+https://github.com/rust-lang/crates.io-index" 69 | checksum = "e5203598f366b11a02b13aa20cab591229ff0a89fd121a308a5df751d5fc9219" 70 | dependencies = [ 71 | "cfg-if", 72 | "indoc", 73 | "libc", 74 | "memoffset", 75 | "once_cell", 76 | "portable-atomic", 77 | "pyo3-build-config", 78 | "pyo3-ffi", 79 | "pyo3-macros", 80 | "unindent", 81 | ] 82 | 83 | [[package]] 84 | name = "pyo3-build-config" 85 | version = "0.24.2" 86 | source = "registry+https://github.com/rust-lang/crates.io-index" 87 | checksum = "99636d423fa2ca130fa5acde3059308006d46f98caac629418e53f7ebb1e9999" 88 | dependencies = [ 89 | "once_cell", 90 | "target-lexicon", 91 | ] 92 | 93 | [[package]] 94 | name = "pyo3-ffi" 95 | version = "0.24.2" 96 | source = "registry+https://github.com/rust-lang/crates.io-index" 97 | checksum = "78f9cf92ba9c409279bc3305b5409d90db2d2c22392d443a87df3a1adad59e33" 98 | dependencies = [ 99 | "libc", 100 | "pyo3-build-config", 101 | ] 102 | 103 | [[package]] 104 | name = "pyo3-macros" 105 | version = "0.24.2" 106 | source = "registry+https://github.com/rust-lang/crates.io-index" 107 | checksum = "0b999cb1a6ce21f9a6b147dcf1be9ffedf02e0043aec74dc390f3007047cecd9" 108 | dependencies = [ 109 | "proc-macro2", 110 | "pyo3-macros-backend", 111 | "quote", 112 | "syn", 113 | ] 114 | 115 | [[package]] 116 | name = "pyo3-macros-backend" 117 | version = "0.24.2" 118 | source = "registry+https://github.com/rust-lang/crates.io-index" 119 | checksum = "822ece1c7e1012745607d5cf0bcb2874769f0f7cb34c4cde03b9358eb9ef911a" 120 | dependencies = [ 121 | "heck", 122 | "proc-macro2", 123 | "pyo3-build-config", 124 | "quote", 125 | "syn", 126 | ] 127 | 128 | [[package]] 129 | name = "quote" 130 | version = "1.0.36" 131 | source = "registry+https://github.com/rust-lang/crates.io-index" 132 | checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" 133 | dependencies = [ 134 | "proc-macro2", 135 | ] 136 | 137 | [[package]] 138 | name = "rust_with_cffi" 139 | version = "0.1.0" 140 | dependencies = [ 141 | "pyo3", 142 | ] 143 | 144 | [[package]] 145 | name = "syn" 146 | version = "2.0.61" 147 | source = "registry+https://github.com/rust-lang/crates.io-index" 148 | checksum = "c993ed8ccba56ae856363b1845da7266a7cb78e1d146c8a32d54b45a8b831fc9" 149 | dependencies = [ 150 | "proc-macro2", 151 | "quote", 152 | "unicode-ident", 153 | ] 154 | 155 | [[package]] 156 | name = "target-lexicon" 157 | version = "0.13.2" 158 | source = "registry+https://github.com/rust-lang/crates.io-index" 159 | checksum = "e502f78cdbb8ba4718f566c418c52bc729126ffd16baee5baa718cf25dd5a69a" 160 | 161 | [[package]] 162 | name = "unicode-ident" 163 | version = "1.0.12" 164 | source = "registry+https://github.com/rust-lang/crates.io-index" 165 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 166 | 167 | [[package]] 168 | name = "unindent" 169 | version = "0.2.3" 170 | source = "registry+https://github.com/rust-lang/crates.io-index" 171 | checksum = "c7de7d73e1754487cb58364ee906a499937a0dfabd86bcb980fa99ec8c8fa2ce" 172 | -------------------------------------------------------------------------------- /examples/rust_with_cffi/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rust_with_cffi" 3 | version = "0.1.0" 4 | authors = ["Alex Gaynor "] 5 | edition = "2021" 6 | 7 | [dependencies] 8 | pyo3 = "0.24" 9 | 10 | [lib] 11 | name = "rust_with_cffi" 12 | crate-type = ["cdylib"] 13 | path = "rust/lib.rs" 14 | -------------------------------------------------------------------------------- /examples/rust_with_cffi/MANIFEST.in: -------------------------------------------------------------------------------- 1 | include Cargo.toml 2 | include cffi_module.py 3 | recursive-include rust * 4 | recursive-include python * 5 | -------------------------------------------------------------------------------- /examples/rust_with_cffi/cffi_module.py: -------------------------------------------------------------------------------- 1 | import cffi 2 | 3 | 4 | ffi = cffi.FFI() 5 | ffi.cdef( 6 | """ 7 | int cffi_func(void); 8 | """ 9 | ) 10 | ffi.set_source( 11 | "rust_with_cffi.cffi", 12 | """ 13 | int cffi_func(void) { 14 | return 15; 15 | } 16 | """, 17 | ) 18 | -------------------------------------------------------------------------------- /examples/rust_with_cffi/noxfile.py: -------------------------------------------------------------------------------- 1 | from os.path import dirname 2 | 3 | import nox 4 | 5 | SETUPTOOLS_RUST = dirname(dirname(dirname(__file__))) 6 | 7 | 8 | @nox.session() 9 | def test(session: nox.Session): 10 | session.install(SETUPTOOLS_RUST, "pytest") 11 | 12 | try: 13 | session.install("cffi", "--only-binary=cffi") 14 | except nox.command.CommandFailed: 15 | session.skip("cffi not available on this platform") 16 | 17 | # Ensure build uses version of setuptools-rust under development 18 | session.install("--no-build-isolation", ".") 19 | # Test Python package 20 | session.run("pytest", *session.posargs) 21 | -------------------------------------------------------------------------------- /examples/rust_with_cffi/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools", "setuptools-rust", "cffi"] 3 | build-backend = "setuptools.build_meta" 4 | -------------------------------------------------------------------------------- /examples/rust_with_cffi/pytest.ini: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyO3/setuptools-rust/f21ef63b03752f98e60f51c754c738166e350d4f/examples/rust_with_cffi/pytest.ini -------------------------------------------------------------------------------- /examples/rust_with_cffi/python/rust_with_cffi/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyO3/setuptools-rust/f21ef63b03752f98e60f51c754c738166e350d4f/examples/rust_with_cffi/python/rust_with_cffi/__init__.py -------------------------------------------------------------------------------- /examples/rust_with_cffi/rust/lib.rs: -------------------------------------------------------------------------------- 1 | use pyo3::prelude::*; 2 | 3 | #[pymodule] 4 | mod rust { 5 | use pyo3::prelude::*; 6 | 7 | #[pyfunction] 8 | fn rust_func() -> PyResult { 9 | return Ok(14); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /examples/rust_with_cffi/setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from setuptools import find_packages, setup 3 | from setuptools_rust import RustExtension 4 | 5 | setup( 6 | name="rust-with-cffi", 7 | version="0.1.0", 8 | classifiers=[ 9 | "License :: OSI Approved :: MIT License", 10 | "Development Status :: 3 - Alpha", 11 | "Intended Audience :: Developers", 12 | "Programming Language :: Python", 13 | "Programming Language :: Rust", 14 | "Operating System :: POSIX", 15 | "Operating System :: MacOS :: MacOS X", 16 | ], 17 | packages=find_packages(where="python"), 18 | package_dir={"": "python"}, 19 | rust_extensions=[ 20 | RustExtension("rust_with_cffi.rust"), 21 | ], 22 | cffi_modules=["cffi_module.py:ffi"], 23 | install_requires=["cffi"], 24 | include_package_data=True, 25 | zip_safe=False, 26 | ) 27 | -------------------------------------------------------------------------------- /examples/rust_with_cffi/tests/test_rust_with_cffi.py: -------------------------------------------------------------------------------- 1 | from rust_with_cffi import rust 2 | from rust_with_cffi.cffi import lib 3 | 4 | 5 | def test_rust(): 6 | assert rust.rust_func() == 14 7 | 8 | 9 | def test_cffi(): 10 | assert lib.cffi_func() == 15 11 | -------------------------------------------------------------------------------- /mypy.ini: -------------------------------------------------------------------------------- 1 | # We have to manually ignore types for some packages. It would be great if 2 | # interested users want to add these types to typeshed! 3 | 4 | [mypy] 5 | show_error_codes = True 6 | 7 | disallow_untyped_defs = True 8 | disallow_any_unimported = True 9 | no_implicit_optional = True 10 | check_untyped_defs = True 11 | 12 | warn_return_any = True 13 | warn_unused_ignores = True 14 | 15 | [mypy-semantic_version.*] 16 | ignore_missing_imports = True 17 | 18 | [mypy-wheel.*] 19 | ignore_missing_imports = True 20 | -------------------------------------------------------------------------------- /noxfile.py: -------------------------------------------------------------------------------- 1 | import os 2 | from inspect import cleandoc as heredoc 3 | from glob import glob 4 | from pathlib import Path 5 | 6 | import nox 7 | import nox.command 8 | 9 | 10 | @nox.session(name="test-examples", venv_backend="none") 11 | def test_examples(session: nox.Session): 12 | for example in glob("examples/*/noxfile.py"): 13 | session.run("nox", "-f", example, external=True) 14 | 15 | 16 | @nox.session(name="test-sdist-vendor") 17 | def test_sdist_vendor(session: nox.Session): 18 | session.install(".", "build") 19 | namespace_package = Path(__file__).parent / "examples" / "namespace_package" 20 | os.chdir(namespace_package) 21 | tmp = Path(session.create_tmp()) 22 | extra_config = tmp / "setup.cfg" 23 | 24 | build_config = """ 25 | [sdist] 26 | vendor_crates = True 27 | """ 28 | extra_config.write_text(heredoc(build_config), encoding="utf-8") 29 | 30 | env = os.environ.copy() 31 | env.update(DIST_EXTRA_CONFIG=str(extra_config)) 32 | cmd = ["python", "-m", "build", "--sdist", "--no-isolation"] 33 | session.run(*cmd, env=env, external=True) 34 | 35 | session.run( 36 | "python", 37 | "-m", 38 | "pip", 39 | "install", 40 | Path("dist") / "namespace_package-0.1.0.tar.gz[dev]", 41 | "--no-build-isolation", 42 | "-v", 43 | # run in offline mode with a blank cargo home to prove the vendored 44 | # dependencies are sufficient to build the package 45 | env={"CARGO_NET_OFFLINE": "true", "CARGO_HOME": str(tmp / ".cargo")}, 46 | ) 47 | session.run("pytest") 48 | 49 | 50 | @nox.session(name="test-crossenv", venv_backend=None) 51 | def test_crossenv(session: nox.Session): 52 | try: 53 | arch = session.posargs[0] 54 | except IndexError: 55 | arch = "aarch64" 56 | print(arch) 57 | 58 | if arch == "aarch64": 59 | rust_target = "aarch64-unknown-linux-gnu" 60 | docker_platform = "aarch64" 61 | elif arch == "armv7": 62 | rust_target = "armv7-unknown-linux-gnueabihf" 63 | docker_platform = "linux/arm/v7" 64 | else: 65 | raise RuntimeError("don't know rust target for arch: " + arch) 66 | 67 | script_build = f"""set -ex 68 | curl -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable 69 | source ~/.cargo/env 70 | rustup target add {rust_target} 71 | 72 | # https://github.com/pypa/setuptools_scm/issues/707 73 | git config --global --add safe.directory /io 74 | 75 | cd examples/rust_with_cffi/ 76 | # Using crossenv master to workaround https://github.com/benfogle/crossenv/issues/108, will need 1.5.0 when released 77 | python3.11 -m pip install https://github.com/benfogle/crossenv/archive/refs/heads/master.zip 78 | python3.11 -m crossenv "/opt/python/cp311-cp311/bin/python3" --cc $TARGET_CC --cxx $TARGET_CXX --sysroot $TARGET_SYSROOT --env LIBRARY_PATH= --manylinux manylinux1 /venv 79 | . /venv/bin/activate 80 | 81 | build-pip install -U 'pip>=23.2.1' 'setuptools>=70.1' 'build>=1' 82 | cross-pip install -U 'pip>=23.2.1' 'setuptools>=70.1' 'build>=1' 83 | build-pip install cffi 84 | cross-expose cffi 85 | cross-pip install -e ../../ 86 | cross-pip list 87 | 88 | export DIST_EXTRA_CONFIG=/tmp/build-opts.cfg 89 | echo -e "[bdist_wheel]\npy_limited_api=cp37" > $DIST_EXTRA_CONFIG 90 | 91 | rm -rf dist/* 92 | cross-python -m build --no-isolation 93 | ls -la dist/ 94 | python -m zipfile -l dist/*.whl # debug all files inside wheel file 95 | """ 96 | 97 | pwd = os.getcwd() 98 | session.run( 99 | "docker", 100 | "run", 101 | "--rm", 102 | "-v", 103 | f"{pwd}:/io", 104 | "-w", 105 | "/io", 106 | f"messense/manylinux2014-cross:{arch}", 107 | "bash", 108 | "-c", 109 | script_build, 110 | external=True, 111 | ) 112 | 113 | script_check = """set -ex 114 | cd /io/examples 115 | python3 --version 116 | pip3 install rust_with_cffi/dist/rust_with_cffi*.whl 117 | python3 -c "from rust_with_cffi import rust; assert rust.rust_func() == 14" 118 | python3 -c "from rust_with_cffi.cffi import lib; assert lib.cffi_func() == 15" 119 | """ 120 | 121 | session.run( 122 | "docker", 123 | "run", 124 | "--rm", 125 | "-v", 126 | f"{pwd}:/io", 127 | "-w", 128 | "/io", 129 | "--platform", 130 | docker_platform, 131 | "python:3.11", 132 | "bash", 133 | "-c", 134 | script_check, 135 | external=True, 136 | ) 137 | 138 | 139 | @nox.session() 140 | def ruff(session: nox.Session): 141 | session.install("ruff") 142 | session.run("ruff", "format", "--diff", ".") 143 | session.run("ruff", "check", ".") 144 | 145 | 146 | @nox.session() 147 | def mypy(session: nox.Session): 148 | session.install("mypy", "fat_macho", "types-setuptools", ".") 149 | session.run("mypy", "setuptools_rust", *session.posargs) 150 | 151 | 152 | @nox.session() 153 | def test(session: nox.Session): 154 | session.install("pytest", ".") 155 | session.run("pytest", "setuptools_rust", "tests", *session.posargs) 156 | 157 | 158 | @nox.session(name="test-examples-emscripten") 159 | def test_examples_emscripten(session: nox.Session): 160 | session.install(".", "build") 161 | emscripten_dir = Path("./emscripten").resolve() 162 | 163 | session.run( 164 | "rustup", 165 | "component", 166 | "add", 167 | "rust-src", 168 | "--toolchain", 169 | "nightly", 170 | external=True, 171 | ) 172 | examples_dir = Path("examples").absolute() 173 | test_crates = [ 174 | examples_dir / "html-py-ever", 175 | examples_dir / "namespace_package", 176 | ] 177 | for example in test_crates: 178 | env = os.environ.copy() 179 | env.update( 180 | RUSTUP_TOOLCHAIN="nightly", 181 | PYTHONPATH=str(emscripten_dir), 182 | _PYTHON_SYSCONFIGDATA_NAME="_sysconfigdata__emscripten_wasm32-emscripten", 183 | _PYTHON_HOST_PLATFORM="emscripten_3_1_14_wasm32", 184 | CARGO_BUILD_TARGET="wasm32-unknown-emscripten", 185 | CARGO_TARGET_WASM32_UNKNOWN_EMSCRIPTEN_LINKER=str( 186 | emscripten_dir / "emcc_wrapper.py" 187 | ), 188 | PYO3_CONFIG_FILE=str(emscripten_dir / "pyo3_config.ini"), 189 | ) 190 | with session.chdir(example): 191 | cmd = ["python", "-m", "build", "--wheel", "--no-isolation"] 192 | session.run(*cmd, env=env, external=True) 193 | 194 | with session.chdir(emscripten_dir): 195 | session.run("node", "runner.js", str(example), external=True) 196 | 197 | 198 | @nox.session(name="bump-version") 199 | def bump_version(session: nox.Session) -> None: 200 | session.install("bump2version") 201 | session.run("bumpversion", *session.posargs) 202 | 203 | 204 | @nox.session() 205 | def docs(session: nox.Session): 206 | session.install(".", "-r", "docs/requirements.txt") 207 | session.run("python", "-m", "sphinx", "docs", "docs/_build", *session.posargs) 208 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "setuptools-rust" 3 | version = "1.11.1" 4 | description = "Setuptools Rust extension plugin" 5 | readme = "README.md" 6 | requires-python = ">=3.9" 7 | keywords = ["distutils", "setuptools", "rust"] 8 | authors = [ 9 | {name = "Nikolay Kim", email = "fafhrd91@gmail.com"}, 10 | ] 11 | classifiers = [ 12 | "Topic :: Software Development :: Version Control", 13 | "License :: OSI Approved :: MIT License", 14 | "Intended Audience :: Developers", 15 | "Programming Language :: Python :: 3", 16 | "Programming Language :: Python :: 3.8", 17 | "Programming Language :: Python :: 3.9", 18 | "Programming Language :: Python :: 3.10", 19 | "Programming Language :: Python :: 3.11", 20 | "Development Status :: 5 - Production/Stable", 21 | "Operating System :: POSIX", 22 | "Operating System :: MacOS :: MacOS X", 23 | "Operating System :: Microsoft :: Windows", 24 | ] 25 | 26 | dependencies = [ 27 | "setuptools>=62.4", 28 | "semantic_version>=2.8.2,<3", 29 | ] 30 | 31 | [project.entry-points."distutils.commands"] 32 | clean_rust = "setuptools_rust:clean_rust" 33 | build_rust = "setuptools_rust:build_rust" 34 | 35 | [project.entry-points."distutils.setup_keywords"] 36 | rust_extensions = "setuptools_rust.setuptools_ext:rust_extensions" 37 | 38 | [project.entry-points."setuptools.finalize_distribution_options"] 39 | setuptools_rust = "setuptools_rust.setuptools_ext:pyprojecttoml_config" 40 | 41 | [project.urls] 42 | Homepage = "https://github.com/PyO3/setuptools-rust" 43 | Repository = "https://github.com/PyO3/setuptools-rust" 44 | Documentation = "https://setuptools-rust.readthedocs.io" 45 | Changelog = "https://github.com/PyO3/setuptools-rust/blob/main/CHANGELOG.md" 46 | 47 | [build-system] 48 | requires = ["setuptools>=62.4", "setuptools_scm"] 49 | build-backend = "setuptools.build_meta" 50 | 51 | [tool.ruff.lint] 52 | extend-select = ["TID251"] 53 | 54 | [tool.ruff.lint.extend-per-file-ignores] 55 | "__init__.py" = ["F403"] 56 | 57 | [tool.ruff.lint.flake8-tidy-imports.banned-api] 58 | "subprocess.run".msg = "Use `_utils.run_subprocess` to ensure `env` is passed" 59 | "subprocess.check_output".msg = "Use `_utils.check_subprocess_output` to ensure `env` is passed" 60 | 61 | [tool.pytest.ini_options] 62 | minversion = "6.0" 63 | addopts = "--doctest-modules" 64 | 65 | [tool.setuptools] 66 | packages = ["setuptools_rust"] 67 | zip-safe = true 68 | 69 | [tool.setuptools.package-data] 70 | setuptools_rust = ["py.typed"] 71 | -------------------------------------------------------------------------------- /setuptools_rust/__init__.py: -------------------------------------------------------------------------------- 1 | from .build import build_rust 2 | from .clean import clean_rust 3 | from .extension import Binding, RustBin, RustExtension, Strip 4 | from .version import version as __version__ # noqa: F401 5 | 6 | __all__ = ("Binding", "RustBin", "RustExtension", "Strip", "build_rust", "clean_rust") 7 | -------------------------------------------------------------------------------- /setuptools_rust/_utils.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | from typing import Any, Optional, Union, cast 3 | 4 | 5 | class Env: 6 | """Allow using ``functools.lru_cache`` with an environment variable dictionary. 7 | 8 | Dictionaries are unhashable, but ``functools.lru_cache`` needs all parameters to 9 | be hashable, which we solve which a custom ``__hash__``.""" 10 | 11 | env: Optional[dict[str, str]] 12 | 13 | def __init__(self, env: Optional[dict[str, str]]): 14 | self.env = env 15 | 16 | def __eq__(self, other: object) -> bool: 17 | if not isinstance(other, Env): 18 | return False 19 | return self.env == other.env 20 | 21 | def __hash__(self) -> int: 22 | if self.env is not None: 23 | return hash(tuple(sorted(self.env.items()))) 24 | else: 25 | return hash(None) 26 | 27 | 28 | def run_subprocess( 29 | *args: Any, env: Union[Env, dict[str, str], None], **kwargs: Any 30 | ) -> subprocess.CompletedProcess: 31 | """Wrapper around subprocess.run that requires a decision to pass env.""" 32 | if isinstance(env, Env): 33 | env = env.env 34 | kwargs["env"] = env 35 | return subprocess.run(*args, **kwargs) # noqa: TID251 # this is a wrapper to implement the rule 36 | 37 | 38 | def check_subprocess_output( 39 | *args: Any, env: Union[Env, dict[str, str], None], **kwargs: Any 40 | ) -> str: 41 | """Wrapper around subprocess.run that requires a decision to pass env.""" 42 | if isinstance(env, Env): 43 | env = env.env 44 | kwargs["env"] = env 45 | return cast(str, subprocess.check_output(*args, **kwargs)) # noqa: TID251 # this is a wrapper to implement the rule 46 | 47 | 48 | def format_called_process_error( 49 | e: subprocess.CalledProcessError, 50 | *, 51 | include_stdout: bool = True, 52 | include_stderr: bool = True, 53 | ) -> str: 54 | """Helper to convert a CalledProcessError to an error message. 55 | 56 | If `include_stdout` or `include_stderr` are True (the default), the 57 | respective output stream will be added to the error message (if 58 | present in the exception). 59 | 60 | >>> format_called_process_error(subprocess.CalledProcessError( 61 | ... 777, ['ls', '-la'], None, None 62 | ... )) 63 | '`ls -la` failed with code 777' 64 | >>> format_called_process_error(subprocess.CalledProcessError( 65 | ... 1, ['cargo', 'foo bar'], 'message', None 66 | ... )) 67 | "`cargo 'foo bar'` failed with code 1\\n-- Output captured from stdout:\\nmessage" 68 | >>> format_called_process_error(subprocess.CalledProcessError( 69 | ... 1, ['cargo', 'foo bar'], 'message', None 70 | ... ), include_stdout=False) 71 | "`cargo 'foo bar'` failed with code 1" 72 | >>> format_called_process_error(subprocess.CalledProcessError( 73 | ... -1, ['cargo'], 'stdout', 'stderr' 74 | ... )) 75 | '`cargo` failed with code -1\\n-- Output captured from stdout:\\nstdout\\n-- Output captured from stderr:\\nstderr' 76 | """ 77 | command = " ".join(_quote_whitespace(arg) for arg in e.cmd) 78 | message = f"`{command}` failed with code {e.returncode}" 79 | if include_stdout and e.stdout is not None: 80 | message += f""" 81 | -- Output captured from stdout: 82 | {e.stdout}""" 83 | 84 | if include_stderr and e.stderr is not None: 85 | message += f""" 86 | -- Output captured from stderr: 87 | {e.stderr}""" 88 | 89 | return message 90 | 91 | 92 | def _quote_whitespace(string: str) -> str: 93 | if " " in string: 94 | return f"'{string}'" 95 | else: 96 | return string 97 | -------------------------------------------------------------------------------- /setuptools_rust/clean.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | from setuptools_rust._utils import check_subprocess_output 4 | 5 | from .command import RustCommand 6 | from .extension import RustExtension 7 | 8 | 9 | class clean_rust(RustCommand): 10 | """Clean Rust extensions.""" 11 | 12 | description = "clean Rust extensions (compile/link to build directory)" 13 | 14 | def initialize_options(self) -> None: 15 | super().initialize_options() 16 | self.inplace = False 17 | 18 | def run_for_extension(self, ext: RustExtension) -> None: 19 | # build cargo command 20 | args = ["cargo", "clean", "--manifest-path", ext.path] 21 | if ext.cargo_manifest_args: 22 | args.extend(ext.cargo_manifest_args) 23 | 24 | if not ext.quiet: 25 | print(" ".join(args), file=sys.stderr) 26 | 27 | # Execute cargo command 28 | try: 29 | check_subprocess_output(args, env=ext.env) 30 | except Exception: 31 | pass 32 | -------------------------------------------------------------------------------- /setuptools_rust/command.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | import logging 3 | from setuptools import Command, Distribution 4 | from setuptools.errors import PlatformError 5 | from typing import List, Optional 6 | 7 | from .extension import RustExtension 8 | from .rustc_info import get_rust_version 9 | 10 | logger = logging.getLogger(__name__) 11 | 12 | 13 | class RustCommand(Command, ABC): 14 | """Abstract base class for commands which interact with Rust Extensions.""" 15 | 16 | # Types for distutils variables which exist on all commands but seem to be 17 | # missing from https://github.com/python/typeshed/blob/master/stdlib/distutils/cmd.pyi 18 | distribution: Distribution 19 | verbose: int 20 | 21 | def initialize_options(self) -> None: 22 | self.extensions: List[RustExtension] = [] 23 | 24 | def finalize_options(self) -> None: 25 | extensions: Optional[List[RustExtension]] = getattr( 26 | self.distribution, "rust_extensions", None 27 | ) 28 | if extensions is None: 29 | # extensions is None if the setup.py file did not contain 30 | # rust_extensions keyword; just no-op if this is the case. 31 | return 32 | 33 | if not isinstance(extensions, list): 34 | ty = type(extensions) 35 | raise ValueError( 36 | "expected list of RustExtension objects for rust_extensions " 37 | f"argument to setup(), got `{ty}`" 38 | ) 39 | for i, extension in enumerate(extensions): 40 | if not isinstance(extension, RustExtension): 41 | ty = type(extension) 42 | raise ValueError( 43 | "expected RustExtension object for rust_extensions " 44 | f"argument to setup(), got `{ty}` at position {i}" 45 | ) 46 | # Extensions have been verified to be at the correct type 47 | self.extensions = extensions 48 | 49 | def run(self) -> None: 50 | if not self.extensions: 51 | logger.info("%s: no rust_extensions defined", self.get_command_name()) 52 | return 53 | 54 | all_optional = all(ext.optional for ext in self.extensions) 55 | # Use the environment of the first non-optional extension, or the first optional 56 | # extension if there is no non-optional extension. 57 | env = None 58 | for ext in self.extensions: 59 | if ext.env: 60 | env = ext.env 61 | if not ext.optional: 62 | break 63 | try: 64 | version = get_rust_version(env) 65 | if version is None: 66 | min_version = max( # type: ignore[type-var] 67 | filter( 68 | lambda version: version is not None, 69 | (ext.get_rust_version() for ext in self.extensions), 70 | ), 71 | default=None, 72 | ) 73 | raise PlatformError( 74 | "can't find Rust compiler\n\n" 75 | "If you are using an outdated pip version, it is possible a " 76 | "prebuilt wheel is available for this package but pip is not able " 77 | "to install from it. Installing from the wheel would avoid the " 78 | "need for a Rust compiler.\n\n" 79 | "To update pip, run:\n\n" 80 | " pip install --upgrade pip\n\n" 81 | "and then retry package installation.\n\n" 82 | "If you did intend to build this package from source, try " 83 | "installing a Rust compiler from your system package manager and " 84 | "ensure it is on the PATH during installation. Alternatively, " 85 | "rustup (available at https://rustup.rs) is the recommended way " 86 | "to download and update the Rust compiler toolchain." 87 | + ( 88 | f"\n\nThis package requires Rust {min_version}." 89 | if min_version is not None 90 | else "" 91 | ) 92 | ) 93 | except PlatformError as e: 94 | if not all_optional: 95 | raise 96 | else: 97 | print(str(e)) 98 | return 99 | 100 | for ext in self.extensions: 101 | try: 102 | rust_version = ext.get_rust_version() 103 | if rust_version is not None and version not in rust_version: 104 | raise PlatformError( 105 | f"Rust {version} does not match extension requirement {rust_version}" 106 | ) 107 | 108 | self.run_for_extension(ext) 109 | except Exception as e: 110 | if not ext.optional: 111 | raise 112 | else: 113 | command_name = self.get_command_name() 114 | print(f"{command_name}: optional Rust extension {ext.name} failed") 115 | print(str(e)) 116 | 117 | @abstractmethod 118 | def run_for_extension(self, extension: RustExtension) -> None: ... 119 | -------------------------------------------------------------------------------- /setuptools_rust/extension.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import json 4 | import os 5 | import re 6 | import subprocess 7 | import warnings 8 | from setuptools.errors import SetupError 9 | from enum import IntEnum, auto 10 | from functools import lru_cache 11 | from typing import ( 12 | Any, 13 | Dict, 14 | List, 15 | Literal, 16 | NewType, 17 | Optional, 18 | Sequence, 19 | TYPE_CHECKING, 20 | Union, 21 | cast, 22 | ) 23 | 24 | if TYPE_CHECKING: 25 | from semantic_version import SimpleSpec 26 | 27 | from ._utils import check_subprocess_output, format_called_process_error, Env 28 | 29 | 30 | class Binding(IntEnum): 31 | """ 32 | Enumeration of possible Rust binding types supported by ``setuptools-rust``. 33 | 34 | Attributes: 35 | PyO3: This is an extension built using 36 | `PyO3 `_. 37 | RustCPython: This is an extension built using 38 | `rust-cpython `_. 39 | NoBinding: Bring your own bindings for the extension. 40 | Exec: Build an executable instead of an extension. 41 | """ 42 | 43 | PyO3 = auto() 44 | RustCPython = auto() 45 | NoBinding = auto() 46 | Exec = auto() 47 | 48 | def __repr__(self) -> str: 49 | return f"{self.__class__.__name__}.{self.name}" 50 | 51 | 52 | class Strip(IntEnum): 53 | """ 54 | Enumeration of modes for stripping symbols from the built extension. 55 | 56 | Attributes: 57 | No: Do not strip symbols. 58 | Debug: Strip debug symbols. 59 | All: Strip all symbols. 60 | """ 61 | 62 | No = auto() 63 | Debug = auto() 64 | All = auto() 65 | 66 | def __repr__(self) -> str: 67 | return f"{self.__class__.__name__}.{self.name}" 68 | 69 | 70 | class RustExtension: 71 | """Used to define a rust extension module and its build configuration. 72 | 73 | Args: 74 | target: The full Python dotted name of the extension, including any 75 | packages, i.e *not* a filename or pathname. It is possible to 76 | specify multiple binaries, if extension uses ``Binding.Exec`` 77 | binding mode. In that case first argument has to be dictionary. 78 | Keys of the dictionary correspond to the rust binary names and 79 | values are the full dotted name to place the executable inside 80 | the python package. To install executables with kebab-case names, 81 | the final part of the dotted name can be in kebab-case. For 82 | example, `hello_world.hello-world` will install an executable 83 | named `hello-world`. 84 | path: Path to the ``Cargo.toml`` manifest file. 85 | args: A list of extra arguments to be passed to Cargo. For example, 86 | ``args=["--no-default-features"]`` will disable the default 87 | features listed in ``Cargo.toml``. 88 | cargo_manifest_args: A list of extra arguments to be passed to Cargo. 89 | These arguments will be passed to every ``cargo`` command, not just 90 | ``cargo build``. For valid options, see 91 | `the Cargo Book `_. 92 | For example, ``cargo_manifest_args=["--locked"]`` will require 93 | ``Cargo.lock`` files are up to date. 94 | features: Cargo `--features` to add to the build. 95 | rustc_flags: A list of additional flags passed to `cargo rustc`. These 96 | only affect the final artifact, usually you should set the 97 | `RUSTFLAGS` environment variable. 98 | rust_version: Minimum Rust compiler version required for this 99 | extension. 100 | quiet: Suppress Cargo's output. 101 | debug: Controls whether ``--debug`` or ``--release`` is passed to 102 | Cargo. If set to `None` (the default) then build type is 103 | automatic: ``inplace`` build will be a debug build, ``install`` 104 | and ``wheel`` builds will be release. 105 | binding: Informs ``setuptools_rust`` which Python binding is in use. 106 | strip: Strip symbols from final file. Does nothing for debug build. 107 | native: Build extension or executable with ``-Ctarget-cpu=native`` 108 | (deprecated, set environment variable RUSTFLAGS=-Ctarget-cpu=native). 109 | script: Generate console script for executable if ``Binding.Exec`` is 110 | used (deprecated, just use ``RustBin`` instead). 111 | optional: If it is true, a build failure in the extension will not 112 | abort the build process, and instead simply not install the failing 113 | extension. 114 | py_limited_api: Deprecated. 115 | env: Environment variables to use when calling cargo or rustc (``env=`` 116 | in ``subprocess.Popen``). setuptools-rust may add additional 117 | variables or modify ``PATH``. 118 | """ 119 | 120 | def __init__( 121 | self, 122 | target: Union[str, Dict[str, str]], 123 | path: str = "Cargo.toml", 124 | args: Optional[Sequence[str]] = (), 125 | cargo_manifest_args: Optional[Sequence[str]] = (), 126 | features: Optional[Sequence[str]] = (), 127 | rustc_flags: Optional[Sequence[str]] = (), 128 | rust_version: Optional[str] = None, 129 | quiet: bool = False, 130 | debug: Optional[bool] = None, 131 | binding: Binding = Binding.PyO3, 132 | strip: Strip = Strip.No, 133 | script: bool = False, 134 | native: bool = False, 135 | optional: bool = False, 136 | py_limited_api: Literal["auto", True, False] = "auto", 137 | env: Optional[Dict[str, str]] = None, 138 | ): 139 | if isinstance(target, dict): 140 | name = "; ".join("%s=%s" % (key, val) for key, val in target.items()) 141 | else: 142 | name = target 143 | target = {"": target} 144 | 145 | self.name = name 146 | self.target = target 147 | self.path = os.path.relpath(path) # relative path to Cargo manifest file 148 | self.args = tuple(args or ()) 149 | self.cargo_manifest_args = tuple(cargo_manifest_args or ()) 150 | self.features = tuple(features or ()) 151 | self.rustc_flags = tuple(rustc_flags or ()) 152 | self.rust_version = rust_version 153 | self.quiet = quiet 154 | self.debug = debug 155 | self.binding = binding 156 | self.strip = strip 157 | self.script = script 158 | self.optional = optional 159 | self.py_limited_api = py_limited_api 160 | self.env = Env(env) 161 | 162 | if native: 163 | warnings.warn( 164 | "`native` is deprecated, set RUSTFLAGS=-Ctarget-cpu=native instead.", 165 | DeprecationWarning, 166 | ) 167 | # match old behaviour of only setting flag for top-level crate; 168 | # setting for `rustflags` is strictly better 169 | self.rustc_flags = (*self.rustc_flags, "-Ctarget-cpu=native") 170 | 171 | if binding == Binding.Exec and script: 172 | warnings.warn( 173 | "`Binding.Exec` with `script=True` is deprecated, use `RustBin` instead.", 174 | DeprecationWarning, 175 | ) 176 | 177 | if self.py_limited_api != "auto": 178 | warnings.warn( 179 | "`RustExtension.py_limited_api` is deprecated, use [bdist_wheel] configuration " 180 | "in `setup.cfg` or `DIST_EXTRA_CONFIG` to build abi3 wheels.", 181 | DeprecationWarning, 182 | ) 183 | 184 | def get_lib_name(self, *, quiet: bool) -> str: 185 | """Parse Cargo.toml to get the name of the shared library.""" 186 | metadata = self.metadata(quiet=quiet) 187 | root_key = metadata["resolve"]["root"] 188 | [pkg] = [p for p in metadata["packages"] if p["id"] == root_key] 189 | name = pkg["targets"][0]["name"] 190 | assert isinstance(name, str) 191 | return re.sub(r"[./\\-]", "_", name) 192 | 193 | def get_rust_version(self) -> Optional[SimpleSpec]: # type: ignore[no-any-unimported] 194 | if self.rust_version is None: 195 | return None 196 | try: 197 | from semantic_version import SimpleSpec 198 | 199 | return SimpleSpec(self.rust_version) 200 | except ValueError: 201 | raise SetupError( 202 | "Can not parse rust compiler version: %s", self.rust_version 203 | ) 204 | 205 | def get_cargo_profile(self) -> Optional[str]: 206 | try: 207 | index = self.args.index("--profile") 208 | return self.args[index + 1] 209 | except ValueError: 210 | pass 211 | except IndexError: 212 | raise SetupError("Can not parse cargo profile from %s", self.args) 213 | 214 | # Handle `--profile=` 215 | profile_args = [p for p in self.args if p.startswith("--profile=")] 216 | if profile_args: 217 | profile = profile_args[0].split("=", 1)[1] 218 | if not profile: 219 | raise SetupError("Can not parse cargo profile from %s", self.args) 220 | return profile 221 | else: 222 | return None 223 | 224 | def entry_points(self) -> List[str]: 225 | entry_points = [] 226 | if self.script and self.binding == Binding.Exec: 227 | for executable, mod in self.target.items(): 228 | base_mod, name = mod.rsplit(".") 229 | script = "%s=%s.%s:run" % (name, base_mod, _script_name(executable)) 230 | entry_points.append(script) 231 | 232 | return entry_points 233 | 234 | def install_script(self, module_name: str, exe_path: str) -> None: 235 | if self.script and self.binding == Binding.Exec: 236 | dirname, executable = os.path.split(exe_path) 237 | script_name = _script_name(module_name) 238 | os.makedirs(dirname, exist_ok=True) 239 | file = os.path.join(dirname, f"{script_name}.py") 240 | with open(file, "w") as f: 241 | f.write(_SCRIPT_TEMPLATE.format(executable=repr(executable))) 242 | 243 | def metadata(self, *, quiet: bool) -> "CargoMetadata": 244 | """Returns cargo metadata for this extension package. 245 | 246 | Cached - will only execute cargo on first invocation. 247 | """ 248 | 249 | return self._metadata(os.environ.get("CARGO", "cargo"), quiet) 250 | 251 | @lru_cache() 252 | def _metadata(self, cargo: str, quiet: bool) -> "CargoMetadata": 253 | metadata_command = [ 254 | cargo, 255 | "metadata", 256 | "--manifest-path", 257 | self.path, 258 | "--format-version", 259 | "1", 260 | ] 261 | if self.cargo_manifest_args: 262 | metadata_command.extend(self.cargo_manifest_args) 263 | 264 | try: 265 | # If quiet, capture stderr and only show it on exceptions 266 | # If not quiet, let stderr be inherited 267 | stderr = subprocess.PIPE if quiet else None 268 | payload = check_subprocess_output( 269 | metadata_command, stderr=stderr, encoding="latin-1", env=self.env.env 270 | ) 271 | except subprocess.CalledProcessError as e: 272 | raise SetupError(format_called_process_error(e)) 273 | try: 274 | return cast(CargoMetadata, json.loads(payload)) 275 | except json.decoder.JSONDecodeError as e: 276 | raise SetupError( 277 | f""" 278 | Error parsing output of cargo metadata as json; received: 279 | {payload} 280 | """ 281 | ) from e 282 | 283 | def _uses_exec_binding(self) -> bool: 284 | return self.binding == Binding.Exec 285 | 286 | 287 | class RustBin(RustExtension): 288 | """Used to define a Rust binary and its build configuration. 289 | 290 | Args: 291 | target: Rust binary target name. 292 | path: Path to the ``Cargo.toml`` manifest file. 293 | args: A list of extra arguments to be passed to Cargo. For example, 294 | ``args=["--no-default-features"]`` will disable the default 295 | features listed in ``Cargo.toml``. 296 | cargo_manifest_args: A list of extra arguments to be passed to Cargo. 297 | These arguments will be passed to every ``cargo`` command, not just 298 | ``cargo build``. For valid options, see 299 | `the Cargo Book `_. 300 | For example, ``cargo_manifest_args=["--locked"]`` will require 301 | ``Cargo.lock`` files are up to date. 302 | features: Cargo `--features` to add to the build. 303 | rust_version: Minimum Rust compiler version required for this bin. 304 | quiet: Suppress Cargo's output. 305 | debug: Controls whether ``--debug`` or ``--release`` is passed to 306 | Cargo. If set to `None` (the default) then build type is 307 | automatic: ``inplace`` build will be a debug build, ``install`` 308 | and ``wheel`` builds will be release. 309 | strip: Strip symbols from final file. Does nothing for debug build. 310 | optional: If it is true, a build failure in the bin will not 311 | abort the build process, and instead simply not install the failing 312 | bin. 313 | """ 314 | 315 | def __init__( 316 | self, 317 | target: Union[str, Dict[str, str]], 318 | path: str = "Cargo.toml", 319 | args: Optional[Sequence[str]] = (), 320 | cargo_manifest_args: Optional[Sequence[str]] = (), 321 | features: Optional[Sequence[str]] = (), 322 | rust_version: Optional[str] = None, 323 | quiet: bool = False, 324 | debug: Optional[bool] = None, 325 | strip: Strip = Strip.No, 326 | optional: bool = False, 327 | env: Optional[dict[str, str]] = None, 328 | ): 329 | super().__init__( 330 | target=target, 331 | path=path, 332 | args=args, 333 | cargo_manifest_args=cargo_manifest_args, 334 | features=features, 335 | rust_version=rust_version, 336 | quiet=quiet, 337 | debug=debug, 338 | binding=Binding.Exec, 339 | optional=optional, 340 | strip=strip, 341 | py_limited_api=False, 342 | env=env, 343 | ) 344 | 345 | def entry_points(self) -> List[str]: 346 | return [] 347 | 348 | 349 | CargoMetadata = NewType("CargoMetadata", Dict[str, Any]) 350 | 351 | 352 | def _script_name(executable: str) -> str: 353 | """Generates the name of the installed Python script for an executable. 354 | 355 | Because Python modules must be snake_case, this generated script name will 356 | replace `-` with `_`. 357 | 358 | >>> _script_name("hello-world") 359 | '_gen_hello_world' 360 | 361 | >>> _script_name("foo_bar") 362 | '_gen_foo_bar' 363 | 364 | >>> _script_name("_gen_foo_bar") 365 | '_gen__gen_foo_bar' 366 | """ 367 | script = executable.replace("-", "_") 368 | return f"_gen_{script}" 369 | 370 | 371 | _SCRIPT_TEMPLATE = """ 372 | import os 373 | import sys 374 | 375 | def run(): 376 | path = os.path.split(__file__)[0] 377 | file = os.path.join(path, {executable}) 378 | if os.path.isfile(file): 379 | os.execv(file, sys.argv) 380 | else: 381 | raise RuntimeError("can't find " + file) 382 | """ 383 | -------------------------------------------------------------------------------- /setuptools_rust/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyO3/setuptools-rust/f21ef63b03752f98e60f51c754c738166e350d4f/setuptools_rust/py.typed -------------------------------------------------------------------------------- /setuptools_rust/rustc_info.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import subprocess 4 | from setuptools.errors import PlatformError 5 | from functools import lru_cache 6 | from typing import Dict, List, NewType, Optional, TYPE_CHECKING 7 | 8 | from ._utils import Env, check_subprocess_output 9 | 10 | if TYPE_CHECKING: 11 | from semantic_version import Version 12 | 13 | 14 | def get_rust_version(env: Optional[Env]) -> Optional[Version]: # type: ignore[no-any-unimported] 15 | try: 16 | # first line of rustc -Vv is something like 17 | # rustc 1.61.0 (fe5b13d68 2022-05-18) 18 | from semantic_version import Version 19 | 20 | return Version(_rust_version(env).split(" ")[1]) 21 | except (subprocess.CalledProcessError, OSError): 22 | return None 23 | 24 | 25 | _HOST_LINE_START = "host: " 26 | 27 | 28 | def get_rust_host(env: Optional[Env]) -> str: 29 | # rustc -Vv has a line denoting the host which cargo uses to decide the 30 | # default target, e.g. 31 | # host: aarch64-apple-darwin 32 | for line in _rust_version_verbose(env).splitlines(): 33 | if line.startswith(_HOST_LINE_START): 34 | return line[len(_HOST_LINE_START) :].strip() 35 | raise PlatformError("Could not determine rust host") 36 | 37 | 38 | RustCfgs = NewType("RustCfgs", Dict[str, Optional[str]]) 39 | 40 | 41 | def get_rustc_cfgs(target_triple: Optional[str], env: Env) -> RustCfgs: 42 | cfgs = RustCfgs({}) 43 | for entry in get_rust_target_info(target_triple, env): 44 | maybe_split = entry.split("=", maxsplit=1) 45 | if len(maybe_split) == 2: 46 | cfgs[maybe_split[0]] = maybe_split[1].strip('"') 47 | else: 48 | assert len(maybe_split) == 1 49 | cfgs[maybe_split[0]] = None 50 | return cfgs 51 | 52 | 53 | @lru_cache() 54 | def get_rust_target_info(target_triple: Optional[str], env: Env) -> List[str]: 55 | cmd = ["rustc", "--print", "cfg"] 56 | if target_triple: 57 | cmd.extend(["--target", target_triple]) 58 | output = check_subprocess_output(cmd, env=env, text=True) 59 | return output.splitlines() 60 | 61 | 62 | @lru_cache() 63 | def get_rust_target_list(env: Env) -> List[str]: 64 | output = check_subprocess_output( 65 | ["rustc", "--print", "target-list"], env=env, text=True 66 | ) 67 | return output.splitlines() 68 | 69 | 70 | @lru_cache() 71 | def _rust_version(env: Env) -> str: 72 | return check_subprocess_output(["rustc", "-V"], env=env, text=True) 73 | 74 | 75 | @lru_cache() 76 | def _rust_version_verbose(env: Env) -> str: 77 | return check_subprocess_output(["rustc", "-Vv"], env=env, text=True) 78 | -------------------------------------------------------------------------------- /setuptools_rust/setuptools_ext.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import sysconfig 4 | import logging 5 | 6 | from typing import List, Literal, Optional, Set, Tuple, Type, TypeVar, cast 7 | from functools import partial 8 | 9 | from setuptools.command.build_ext import build_ext 10 | 11 | from setuptools.command.install import install 12 | from setuptools.command.install_lib import install_lib 13 | from setuptools.command.install_scripts import install_scripts 14 | from setuptools.command.sdist import sdist 15 | from setuptools.dist import Distribution 16 | 17 | from ._utils import Env, run_subprocess 18 | from .build import _get_bdist_wheel_cmd 19 | from .extension import Binding, RustBin, RustExtension, Strip 20 | 21 | try: 22 | from setuptools.command.bdist_wheel import bdist_wheel 23 | except ImportError: 24 | try: # old version of setuptools 25 | from wheel.bdist_wheel import bdist_wheel # type: ignore[no-redef] 26 | except ImportError: 27 | bdist_wheel = None # type: ignore[assignment,misc] 28 | 29 | if sys.version_info[:2] >= (3, 11): 30 | from tomllib import load as toml_load 31 | else: 32 | try: 33 | from tomli import load as toml_load 34 | except ImportError: 35 | from setuptools.extern.tomli import load as toml_load 36 | 37 | 38 | logger = logging.getLogger(__name__) 39 | 40 | T = TypeVar("T", bound=RustExtension) 41 | 42 | 43 | def add_rust_extension(dist: Distribution) -> None: 44 | sdist_base_class = cast(Type[sdist], dist.cmdclass.get("sdist", sdist)) 45 | sdist_options = sdist_base_class.user_options.copy() 46 | sdist_boolean_options = sdist_base_class.boolean_options.copy() 47 | sdist_negative_opt = sdist_base_class.negative_opt.copy() 48 | sdist_options.extend( 49 | [ 50 | ("vendor-crates", None, "vendor Rust crates"), 51 | ( 52 | "no-vendor-crates", 53 | None, 54 | "don't vendor Rust crates. [default; enable with --vendor-crates]", 55 | ), 56 | ] 57 | ) 58 | sdist_boolean_options.append("vendor-crates") 59 | sdist_negative_opt["no-vendor-crates"] = "vendor-crates" 60 | 61 | # Patch dist to include console_scripts for Exec binding 62 | console_scripts = [] 63 | for ext in dist.rust_extensions: # type: ignore[attr-defined] 64 | console_scripts.extend(ext.entry_points()) 65 | 66 | if console_scripts: 67 | if not dist.entry_points: # type: ignore[attr-defined] 68 | dist.entry_points = {"console_scripts": console_scripts} # type: ignore[attr-defined] 69 | else: 70 | ep_scripts = dist.entry_points.get("console_scripts") # type: ignore[attr-defined] 71 | if ep_scripts: 72 | for script in console_scripts: 73 | if script not in ep_scripts: 74 | ep_scripts.append(console_scripts) 75 | else: 76 | ep_scripts = console_scripts 77 | 78 | dist.entry_points["console_scripts"] = ep_scripts # type: ignore[attr-defined] 79 | 80 | class sdist_rust_extension(sdist_base_class): # type: ignore[misc,valid-type] 81 | user_options = sdist_options 82 | boolean_options = sdist_boolean_options 83 | negative_opt = sdist_negative_opt 84 | 85 | def initialize_options(self) -> None: 86 | super().initialize_options() 87 | self.vendor_crates = 0 88 | 89 | def make_distribution(self) -> None: 90 | if self.vendor_crates: 91 | manifest_paths = [] 92 | 93 | # Collate cargo manifest options together. 94 | # We can cheat here, as the only valid options are the simple strings 95 | # --frozen, --locked, or --offline. 96 | # 97 | # https://doc.rust-lang.org/cargo/commands/cargo-build.html#manifest-options 98 | cargo_manifest_args: Set[str] = set() 99 | env: Optional[Env] = None 100 | env_source: Optional[str] = None 101 | for ext in self.distribution.rust_extensions: 102 | if env is not None: 103 | if ext.env != env: 104 | raise ValueError( 105 | f"For vendoring, all extensions must have the same environment variables, " 106 | f"but {env_source} and {ext.name} differ:\n" 107 | f"{env_source}: {env}\n" 108 | f"{ext.name}: {ext.env}" 109 | ) 110 | else: 111 | env = ext.env 112 | env_source = ext.name 113 | manifest_paths.append(ext.path) 114 | if ext.cargo_manifest_args: 115 | cargo_manifest_args.update(ext.cargo_manifest_args) 116 | 117 | if manifest_paths: 118 | base_dir = self.distribution.get_fullname() 119 | dot_cargo_path = os.path.join(base_dir, ".cargo") 120 | self.mkpath(dot_cargo_path) 121 | cargo_config_path = os.path.join(dot_cargo_path, "config.toml") 122 | vendor_path = os.path.join(dot_cargo_path, "vendor") 123 | command = ["cargo", "vendor"] 124 | if cargo_manifest_args: 125 | command.extend(sorted(cargo_manifest_args)) 126 | # additional Cargo.toml for extension 1..n 127 | for extra_path in manifest_paths[1:]: 128 | command.append("--sync") 129 | command.append(extra_path) 130 | # `cargo vendor --sync` accepts multiple values, for example 131 | # `cargo vendor --sync a --sync b --sync c vendor_path` 132 | # but it would also consider vendor_path as --sync value 133 | # set --manifest-path before vendor_path and after --sync to workaround that 134 | # See https://docs.rs/clap/latest/clap/struct.Arg.html#method.multiple for detail 135 | command.extend(["--manifest-path", manifest_paths[0], vendor_path]) 136 | run_subprocess(command, env=env, check=True) 137 | 138 | cargo_config = _CARGO_VENDOR_CONFIG 139 | 140 | # Check whether `.cargo/config`/`.cargo/config.toml` already exists 141 | existing_cargo_config = None 142 | for filename in ( 143 | f".cargo{os.sep}config", 144 | f".cargo{os.sep}config.toml", 145 | ): 146 | if filename in self.filelist.files: 147 | existing_cargo_config = filename 148 | break 149 | 150 | if existing_cargo_config: 151 | cargo_config_path = os.path.join( 152 | base_dir, existing_cargo_config 153 | ) 154 | # Append vendor config to original cargo config 155 | with open(existing_cargo_config, "rb") as f: 156 | cargo_config += f.read() + b"\n" 157 | 158 | with open(cargo_config_path, "wb") as f: 159 | f.write(cargo_config) 160 | 161 | super().make_distribution() 162 | 163 | dist.cmdclass["sdist"] = sdist_rust_extension 164 | 165 | build_ext_base_class = cast( 166 | Type[build_ext], dist.cmdclass.get("build_ext", build_ext) 167 | ) 168 | build_ext_options = build_ext_base_class.user_options.copy() 169 | build_ext_options.append(("target", None, "Build for the target triple")) 170 | 171 | class build_ext_rust_extension(build_ext_base_class): # type: ignore[misc,valid-type] 172 | user_options = build_ext_options 173 | 174 | def initialize_options(self) -> None: 175 | super().initialize_options() 176 | self.target = os.getenv("CARGO_BUILD_TARGET") 177 | 178 | def run(self) -> None: 179 | super().run() 180 | if self.distribution.rust_extensions: 181 | logger.info("running build_rust") 182 | build_rust = self.get_finalized_command("build_rust") 183 | build_rust.inplace = self.inplace 184 | build_rust.target = self.target 185 | build_rust.verbose = self.verbose 186 | build_rust.plat_name = self._get_wheel_plat_name() or self.plat_name 187 | build_rust.run() 188 | 189 | def _get_wheel_plat_name(self) -> Optional[str]: 190 | cmd = _get_bdist_wheel_cmd(self.distribution) 191 | return cast(Optional[str], getattr(cmd, "plat_name", None)) 192 | 193 | dist.cmdclass["build_ext"] = build_ext_rust_extension 194 | 195 | clean_base_class = dist.cmdclass.get("clean") 196 | 197 | if clean_base_class is not None: 198 | 199 | class clean_rust_extension(clean_base_class): # type: ignore[misc,valid-type] 200 | def run(self) -> None: 201 | super().run() 202 | if not self.dry_run: 203 | self.run_command("clean_rust") 204 | 205 | dist.cmdclass["clean"] = clean_rust_extension 206 | 207 | install_base_class = cast(Type[install], dist.cmdclass.get("install", install)) 208 | 209 | # this is required to make install_scripts compatible with RustBin 210 | class install_rust_extension(install_base_class): # type: ignore[misc,valid-type] 211 | def run(self) -> None: 212 | super().run() 213 | install_rustbin = False 214 | if self.distribution.rust_extensions: 215 | install_rustbin = any( 216 | isinstance(ext, RustBin) 217 | for ext in self.distribution.rust_extensions 218 | ) 219 | if install_rustbin: 220 | self.run_command("install_scripts") 221 | 222 | dist.cmdclass["install"] = install_rust_extension 223 | 224 | install_lib_base_class = cast( 225 | Type[install_lib], dist.cmdclass.get("install_lib", install_lib) 226 | ) 227 | 228 | # prevent RustBin from being installed to data_dir 229 | class install_lib_rust_extension(install_lib_base_class): # type: ignore[misc,valid-type] 230 | def get_exclusions(self) -> Set[str]: 231 | exclusions: Set[str] = super().get_exclusions() 232 | install_scripts_obj = cast( 233 | install_scripts, self.get_finalized_command("install_scripts") 234 | ) 235 | scripts_path = install_scripts_obj.build_dir 236 | if self.distribution.rust_extensions: 237 | exe = sysconfig.get_config_var("EXE") 238 | for ext in self.distribution.rust_extensions: 239 | if isinstance(ext, RustBin): 240 | executable_name = ext.name 241 | if exe is not None: 242 | executable_name += exe 243 | exclusions.add(os.path.join(scripts_path, executable_name)) 244 | return exclusions 245 | 246 | dist.cmdclass["install_lib"] = install_lib_rust_extension 247 | 248 | install_scripts_base_class = cast( 249 | Type[install_scripts], dist.cmdclass.get("install_scripts", install_scripts) 250 | ) 251 | 252 | # this is required to make install_scripts compatible with RustBin 253 | class install_scripts_rust_extension(install_scripts_base_class): # type: ignore[misc,valid-type] 254 | def run(self) -> None: 255 | super().run() 256 | install_scripts_obj = cast( 257 | install_scripts, self.get_finalized_command("install_scripts") 258 | ) 259 | scripts_path = install_scripts_obj.build_dir 260 | if os.path.isdir(scripts_path): 261 | for file in os.listdir(scripts_path): 262 | script_path = os.path.join(scripts_path, file) 263 | if os.path.isfile(script_path): 264 | with open(os.path.join(script_path), "rb") as script_reader: 265 | self.write_script(file, script_reader.read(), mode="b") 266 | 267 | dist.cmdclass["install_scripts"] = install_scripts_rust_extension 268 | 269 | if bdist_wheel is not None: 270 | bdist_wheel_base_class = cast( 271 | Type[bdist_wheel], dist.cmdclass.get("bdist_wheel", bdist_wheel) 272 | ) 273 | bdist_wheel_options = bdist_wheel_base_class.user_options.copy() 274 | bdist_wheel_options.append(("target", None, "Build for the target triple")) 275 | 276 | # this is for console entries 277 | class bdist_wheel_rust_extension(bdist_wheel_base_class): # type: ignore[misc,valid-type] 278 | user_options = bdist_wheel_options 279 | 280 | def initialize_options(self) -> None: 281 | super().initialize_options() 282 | self.target = os.getenv("CARGO_BUILD_TARGET") 283 | 284 | def get_tag(self) -> Tuple[str, str, str]: 285 | python, abi, plat = super().get_tag() 286 | arch_flags = os.getenv("ARCHFLAGS") 287 | universal2 = False 288 | if self.plat_name.startswith("macosx-") and arch_flags: 289 | universal2 = "x86_64" in arch_flags and "arm64" in arch_flags 290 | if universal2 and plat.startswith("macosx_"): 291 | from wheel.macosx_libfile import calculate_macosx_platform_tag 292 | 293 | macos_target = os.getenv("MACOSX_DEPLOYMENT_TARGET") 294 | if macos_target is None: 295 | # Example: macosx_11_0_arm64 296 | macos_target = ".".join(plat.split("_")[1:3]) 297 | plat = calculate_macosx_platform_tag( 298 | self.bdist_dir, "macosx-{}-universal2".format(macos_target) 299 | ) 300 | return python, abi, plat 301 | 302 | dist.cmdclass["bdist_wheel"] = bdist_wheel_rust_extension 303 | 304 | 305 | def rust_extensions( 306 | dist: Distribution, attr: Literal["rust_extensions"], value: List[RustExtension] 307 | ) -> None: 308 | assert attr == "rust_extensions" 309 | has_rust_extensions = len(value) > 0 310 | 311 | # Monkey patch has_ext_modules to include Rust extensions. 312 | orig_has_ext_modules = dist.has_ext_modules 313 | dist.has_ext_modules = lambda: (orig_has_ext_modules() or has_rust_extensions) # type: ignore[method-assign] 314 | 315 | if has_rust_extensions: 316 | add_rust_extension(dist) 317 | 318 | 319 | def pyprojecttoml_config(dist: Distribution) -> None: 320 | try: 321 | with open("pyproject.toml", "rb") as f: 322 | cfg = toml_load(f).get("tool", {}).get("setuptools-rust") 323 | except FileNotFoundError: 324 | return None 325 | 326 | if cfg: 327 | modules = map(partial(_create, RustExtension), cfg.get("ext-modules", [])) 328 | binaries = map(partial(_create, RustBin), cfg.get("bins", [])) 329 | dist.rust_extensions = [*modules, *binaries] # type: ignore[attr-defined] 330 | rust_extensions(dist, "rust_extensions", dist.rust_extensions) # type: ignore[attr-defined] 331 | 332 | 333 | def _create(constructor: Type[T], config: dict) -> T: 334 | kwargs = { 335 | # PEP 517/621 convention: pyproject.toml uses dashes 336 | k.replace("-", "_"): v 337 | for k, v in config.items() 338 | } 339 | if "binding" in config: 340 | kwargs["binding"] = Binding[config["binding"]] 341 | if "strip" in config: 342 | kwargs["strip"] = Strip[config["strip"]] 343 | return constructor(**kwargs) 344 | 345 | 346 | _CARGO_VENDOR_CONFIG = b""" 347 | [source.crates-io] 348 | replace-with = "vendored-sources" 349 | 350 | [source.vendored-sources] 351 | directory = ".cargo/vendor" 352 | """ 353 | -------------------------------------------------------------------------------- /setuptools_rust/version.py: -------------------------------------------------------------------------------- 1 | __version__ = version = "1.11.1" 2 | __version_tuple__ = version_tuple = tuple( 3 | map(lambda x: int(x[1]) if x[0] < 3 else x[1], enumerate(__version__.split("."))) 4 | ) 5 | -------------------------------------------------------------------------------- /tests/test_build.py: -------------------------------------------------------------------------------- 1 | from unittest import mock 2 | 3 | from setuptools_rust.build import _adjusted_local_rust_target 4 | 5 | 6 | def test_adjusted_local_rust_target_windows_msvc(): 7 | with mock.patch( 8 | "setuptools_rust.rustc_info.get_rust_target_info", 9 | lambda _plat_name, _env: ["target_env=msvc"], 10 | ): 11 | assert _adjusted_local_rust_target("win32", None) == "i686-pc-windows-msvc" 12 | assert ( 13 | _adjusted_local_rust_target("win-amd64", None) == "x86_64-pc-windows-msvc" 14 | ) 15 | 16 | 17 | def test_adjusted_local_rust_target_windows_gnu(): 18 | with mock.patch( 19 | "setuptools_rust.rustc_info.get_rust_target_info", 20 | lambda _plat_name, _env: ["target_env=gnu"], 21 | ): 22 | assert _adjusted_local_rust_target("win32", None) == "i686-pc-windows-gnu" 23 | assert _adjusted_local_rust_target("win-amd64", None) == "x86_64-pc-windows-gnu" 24 | 25 | 26 | def test_adjusted_local_rust_target_macos(): 27 | with mock.patch("platform.machine", lambda: "x86_64"): 28 | assert _adjusted_local_rust_target("macosx-", None) == "x86_64-apple-darwin" 29 | -------------------------------------------------------------------------------- /tests/test_extension.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | import pytest 4 | from pytest import CaptureFixture, MonkeyPatch 5 | 6 | from setuptools_rust.extension import RustBin, RustExtension 7 | 8 | SETUPTOOLS_RUST_DIR = Path(__file__).parent.parent 9 | 10 | 11 | @pytest.fixture() 12 | def hello_world_bin() -> RustBin: 13 | return RustBin( 14 | "hello-world", 15 | path=( 16 | SETUPTOOLS_RUST_DIR / "examples" / "hello-world" / "Cargo.toml" 17 | ).as_posix(), 18 | ) 19 | 20 | 21 | @pytest.fixture() 22 | def namespace_package_extension() -> RustExtension: 23 | return RustExtension( 24 | "namespace_package.rust", 25 | path=( 26 | SETUPTOOLS_RUST_DIR / "examples" / "namespace_package" / "Cargo.toml" 27 | ).as_posix(), 28 | ) 29 | 30 | 31 | def test_metadata_contents(hello_world_bin: RustBin) -> None: 32 | metadata = hello_world_bin.metadata(quiet=False) 33 | assert "target_directory" in metadata 34 | 35 | 36 | def test_metadata_cargo_log( 37 | capfd: CaptureFixture, monkeypatch: MonkeyPatch, hello_world_bin: RustBin 38 | ) -> None: 39 | monkeypatch.setenv("CARGO_LOG", "trace") 40 | 41 | # With quiet unset, no stdout, plenty of logging stderr 42 | hello_world_bin.metadata(quiet=False) 43 | captured = capfd.readouterr() 44 | assert captured.out == "" 45 | assert "TRACE" in captured.err 46 | 47 | # With quiet set, nothing will be printed 48 | hello_world_bin.metadata(quiet=True) 49 | captured = capfd.readouterr() 50 | assert captured.out == "" 51 | assert captured.err == "" 52 | 53 | 54 | def test_get_lib_name_namespace_package( 55 | namespace_package_extension: RustExtension, 56 | ) -> None: 57 | assert ( 58 | namespace_package_extension.get_lib_name(quiet=True) == "namespace_package_rust" 59 | ) 60 | --------------------------------------------------------------------------------