Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/_sidebar.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@
"maintainer/example_recipes/go",
"maintainer/example_recipes/rust",
"maintainer/example_recipes/pure-python",
"maintainer/example_recipes/abi3"
"maintainer/example_recipes/maturin-abi3"
]
}
]
Expand Down
10 changes: 0 additions & 10 deletions docs/maintainer/example_recipes/abi3.md

This file was deleted.

151 changes: 151 additions & 0 deletions docs/maintainer/example_recipes/maturin-abi3.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
---
title: 'maturin abi3 packages'
---

This guide shows you how to create a conda-forge recipe for a Python package with a
compiled Rust extension built with [maturin](https://maturin.rs/) using CPython's
stable [`abi3` ABI](https://docs.python.org/3/c-api/stable.html).

Building against the stable ABI means a single compiled artifact works across many Python
versions, so the package can be shipped as a
[Python version-independent](https://docs.conda.io/projects/conda-build/en/stable/resources/define-metadata.html#python-version-independent-packages)
package. Instead of one build per Python version, you build once against the minimum
supported Python and reuse it everywhere.

For more general information on abi3 packages, see the
[knowledge base](../knowledge_base.mdx#packages-with-abi3-extensions).

conda-forge also keeps full example recipes in the
[`python-abi3-feedstock`](https://github.com/conda-forge/python-abi3-feedstock) repository:

- [`example-recipe.yaml`](https://github.com/conda-forge/python-abi3-feedstock/blob/main/recipe/example-recipe.yaml) (v1 format)
- [`example-meta.yaml`](https://github.com/conda-forge/python-abi3-feedstock/blob/main/recipe/example-meta.yaml) (v0 format)

## Recipe template

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this recipe example here adds anything more useful than the two listed above. Having this duplicated here will make things go out of sync fast.


```yaml title="recipe.yaml"
Comment thread
pavelzw marked this conversation as resolved.
context:
version: "1.2.3"

package:
name: example-package
version: ${{ version }}

source:
url: https://pypi.org/packages/source/e/example-package/example_package-${{ version }}.tar.gz
sha256: 9899b001e26a6d9c0930cd4138a7c7c9d23629dc949b39ae42c25e2d05c2145d

build:
number: 1
skip: is_abi3 and not is_python_min
python:
version_independent: ${{ is_abi3 }}
script:
env:
CARGO_PROFILE_RELEASE_STRIP: symbols
CARGO_PROFILE_RELEASE_LTO: fat
content:
# Remove this wrapper once https://github.com/conda-forge/rust-activation-feedstock/pull/79 is merged

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

depends on conda-forge/cfep#63. i would also be fine with waiting until the CFEP is accepted... we could alternatively also for now just remove the cargo-auditable-wrapper from the example and add it once this is ready.

- if: unix
then:
- mkdir -p "${BUILD_PREFIX}/bin"
- cp "${RECIPE_DIR}/cargo-auditable-wrapper.sh" "${BUILD_PREFIX}/bin/cargo-auditable-wrapper"
- export CARGO="cargo-auditable-wrapper"
else:
- copy "%RECIPE_DIR%\cargo-auditable-wrapper.bat" "%BUILD_PREFIX%\Library\bin\cargo-auditable-wrapper.bat" || exit 1
- set CARGO=cargo-auditable-wrapper.bat
- cargo-bundle-licenses --format yaml --output THIRDPARTY.yml
- python -m pip install . --no-deps --ignore-installed -vv --no-build-isolation --disable-pip-version-check

requirements:
build:
- ${{ compiler('rust') }}
- ${{ stdlib('c') }}
- cargo-bundle-licenses
- cargo-auditable
host:
- python
- if: is_abi3
then: python-abi3
- pip
- maturin
run:
- python

tests:
- python:
python_version:
- ${{ python_min }}.*
- '*'
imports:
- example_package
pip_check: true
- if: is_abi3
then:
script:
- if: win
then: abi3audit %PREFIX%/Lib/site-packages/example_package/_native.pyd -s -v --assume-minimum-abi3 ${{ python_min }}
else: abi3audit $SP_DIR/example_package/_native.abi3.so -s -v --assume-minimum-abi3 ${{ python_min }}
requirements:
run:
- abi3audit

about:
homepage: https://github.com/example/example-package
summary: Single-line summary of the package.
license: MIT
license_file:
- LICENSE
- THIRDPARTY.yml
documentation: https://example.com/example-package-docs/
repository: https://github.com/example/example-package

extra:
recipe-maintainers:
- LandoCalrissian
```

Both `is_abi3` and `is_python_min` are provided by conda-forge's build matrix.

`is_abi3` is not really about the package itself but about the Python _variant_ being built
against, and whether that variant supports the stable ABI. Regular CPython does, so
`is_abi3` is `true` there and the extension is built once against the minimum supported
Python. Other variants — such as free-threading CPython or PyPy — don't provide a stable
ABI, so `is_abi3` is `false` and the recipe falls back to a normal per-Python build. The
`if: is_abi3` selectors are what let the same recipe handle both cases without further
changes.

`python-abi3` (when `is_abi3`): Ensures the extension is built and linked against the stable ABI.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not true. The extension itself has to do it. See "Support in the upstream package" section at https://labs.quansight.org/blog/conda-abi3

This package pins the abi3 toolchain so the resulting artifact is compatible across Python versions.

:::note

If your package doesn't support abi3, remove all abi3-related things from the recipe to build it.

:::

### The `cargo-auditable-wrapper`

`cargo-auditable` embeds a dependency manifest into the compiled artifact so it can later be
audited. The plain `cargo auditable install` used for standalone binaries does not fit the
maturin flow, because maturin invokes `cargo` itself. Instead, point the `CARGO` environment
variable at a small wrapper that transparently forwards every invocation through
`cargo auditable`. Add both wrapper scripts next to `recipe.yaml`:

```sh title="cargo-auditable-wrapper.sh"
#!/bin/sh
exec cargo auditable $*
```

```bat title="cargo-auditable-wrapper.bat"
@echo off
cargo auditable %*
```

:::note

These wrappers are a temporary workaround. Once
[rust-activation-feedstock#79](https://github.com/conda-forge/rust-activation-feedstock/pull/79)
is merged, `cargo auditable` will be wired up automatically and the wrappers can be removed.

:::