APKBUILD examples:Python: Difference between revisions

From Alpine Linux
Jump to navigation Jump to search
(modernise)
Line 1: Line 1:
A lot of Python packages use the ''setuptools'' or ''distutils'' framework. This mean that the build() and the package() section looks a bit different compared to an application which uses ''make''.
A lot of Python packages use the ''setuptools'' or ''distutils'' framework. This mean that the build() and the package() section looks a bit different compared to an application which uses ''make''.


== Considerations ==
== Considerations ==
=== Python 2 vs. Python 3 ===
'''Python 3 should be the default option.''' If the project support both Python 2 and Python 3, then the Alpine package may provide both variants as ''py2-'' and ''py3-'' subpackages.
However, add ''py2'' variant only when you really need it.
Python 2 is legacy and will be deprecated soon.
You may omit ''py2'' variant, but never omit ''py3'' variant if it's supported by the project.


=== pkgname ===
=== pkgname ===


Package name for a Python ''library'' must be prefixed with ''py-'', ''py2-'', or ''py3-''.
Package name for a Python ''library'' must be prefixed with ''py3-''.


; py- : Use if the project supports both Python 2 and Python 3, even if it does not support Python 3 yet, but may in future.
For an 'executable' (for example, <code>black</code>, <code>binwalk</code>), you generally don't need to prefix it.
; py2- : TODO
; py3- : Use if the project does not support Python 2.


There’s no exact rule if the prefix should be used for tools and applications written in Python, it varies.
There’s no exact rule if the prefix should be used for tools and applications written in Python, it varies.
Line 23: Line 13:
=== arch ===
=== arch ===


; noarch : Use for pure Python packages (i.e. without compiled code). Also add python2, or python3 to <tt>depends=</tt>.
; noarch : Use for pure Python packages (i.e. without compiled code). Also add python3 to <tt>depends=</tt>.
; all (and others) :  Use for packages with native extensions (i.e. with compiled code). '''Do not''' add python2/python3 to <tt>depends=</tt> (it's auto-detected via dynamic linking to python library).
; all (and others) :  Use for packages with native extensions (i.e. with compiled code). '''Do not''' add python3 to <tt>depends=</tt> (it's auto-detected via dynamic linking to python library).


=== source ===
=== source ===


Basically all Python are published in [https://pypi.python.org/pypi PyPI] (the Python Package Index).
Most Python packages are published in [https://pypi.python.org/pypi PyPI] (the Python Package Index).
If the package has a source tarball available in PyPI (that’s true for most packages), you should reference it in <tt>source=</tt> as:
If the package has a source tarball available in PyPI (that’s true for most packages), and it contains tests (some explicitly remove them from PyPI), you should reference it in <tt>source=</tt> as:


<pre>
<pre>
Line 35: Line 25:
</pre>
</pre>


where <tt>_pyname</tt> is a real name of the Python package.
where <tt>_pyname</tt> is the real name of the Python package.


Otherwise, use the normal upstream git tarballs.


== Examples ==
== Examples ==


Comments marked with <tt>##</tt> should not be copied into final APKBUILD!
=== Package for setuptools/setup.py build ===
 
 
=== Package for single Python version ===


If the project supports only Python 3 (or 2), then the APKBUILD is very simple:
If the project has a setup.py, then the APKBUILD is very simple:


<pre>
<pre>
Line 53: Line 41:
depends="python3"
depends="python3"
makedepends="python3-dev"
makedepends="python3-dev"
checkdepends="py3-pytest"
...
...


build() {
build() {
cd "$builddir"
python3 setup.py build
python3 setup.py build
}
}


check() {
check() {
cd "$builddir"
pytest
python3 setup.py test
}
}


package() {
package() {
cd "$builddir"
python3 setup.py install --skip-build --root="$pkgdir"
python3 setup.py install --prefix=/usr --root="$pkgdir"
}
}
</pre>
</pre>


Note that PyPI tarballs can contain a generated setup.py that does not exist in the upstream repo - this means you must use the pyproject.toml method if you change sources.


=== Multiversion package without executables ===
=== Package for pyproject.toml build ===
 
Use this variant if the package does not install any files outside of <tt>/usr/lib/pythonX.Y/site-packages</tt>.
If the build modifies source code, e.g. using 2to3, read the last section!


<pre>
<pre>
Line 81: Line 65:
_pyname="PyFoo"
_pyname="PyFoo"
...
...
_py2deps="python2 py2-bar py2-baz"
depends="python3 py3-bar py3-baz"
_py3deps="python3 py3-bar py3-baz"
makedepends="py3-build py3-installer python3-dev py3-wheel"
makedepends="python2-dev python3-dev"
subpackages="py2-${pkgname#py-}:_py2 py3-${pkgname#py-}:_py3"
...
...


build() {
build() {
cd "$builddir"
python3 -m build --no-isolation --wheel
python2 setup.py build
python3 setup.py build
}
}


check() {
check() {
cd "$builddir"
pytest
python2 setup.py test
python3 setup.py test
}
}


package() {
package() {
cd "$builddir"
python3 -m installer -d "$pkgdir" \
python2 setup.py install --prefix=/usr --root="$pkgdir"
dist/PyFoo-$pkgver-py3-none-any.whl
python3 setup.py install --prefix=/usr --root="$pkgdir"
}
 
_py() {
local _python="$1"
pkgdesc="$pkgdesc (for $_python)"
install_if="$pkgname=$pkgver-r$pkgrel $_python"
 
mkdir -p "$subpkgdir"/usr/lib/
mv "$pkgdir"/usr/lib/$_python* "$subpkgdir"/usr/lib/
}
 
_py2() {
depends="$_py2deps"
_py python2
}
 
_py3() {
depends="$_py3deps"
_py python3
}
}
</pre>
</pre>


=== Multiversion package with executables ===
Depending on the <code>build-backend</code> in the pyproject.toml, you might need py3-setuptools or py3-flit-core or py3-poetry-core or py3-hatchling at build time.


Use this variant if the packages installs some executables into <tt>/usr/bin</tt>.
Sometimes you might have to pass the path to the built package to run tests:
If the build modifies source code, e.g. using 2to3, read the last section!
 
'''Note:''' This is just mere workaround that will not be needed in future once we add needed features to apk.


<pre>
<pre>
pkgname="py-foo"
check() {
_pyname="PyFoo"
python3 -m installer -d testenv \
...
dist/PyFoo-$pkgver-py3-none-any.whl
depends="py-bar py-baz"
local sitedir="$(python3 -c 'import site;print(site.getsitepackages()[0])')"
makedepends="python2-dev python3-dev py-setuptools"
PYTHONPATH="$PWD/testenv/$sitedir" pytest
subpackages="py2-${pkgname#py-}:_py2 py3-${pkgname#py-}:_py3"
...
 
build() {
cd "$builddir"
python2 setup.py build
python3 setup.py build
}
 
package() {
mkdir -p "$pkgdir"/usr/bin
 
# Create unsuffixed symlinks for executables from py3 subpackage.
local name; for name in foo bar baz; do
ln -s $name-3 "$pkgdir"/usr/bin/$name
done
}
 
_py2() {
replaces="$pkgname"            ## remove if you're writing a new aport (and always remove this comment!)
depends="${depends//py-/py2-}"  ## remove if there are no common Python dependencies, i.e. above depends= is empty/undefined (and always remove this comment!)
_py python2
}
 
_py3() {
depends="${depends//py-/py3-}"  ## remove if there are no common Python dependencies, i.e. above depends= is empty/undefined (and always remove this comment!)
_py python3
}
 
_py() {
local python="$1"
local pyver="${1:6:1}"
pkgdesc="$pkgdesc (for $python)"
depends="$depends $python"  ## remove if arch isn't noarch (and always remove this comment!)
install_if="$pkgname=$pkgver-r$pkgrel $python"
 
cd "$builddir"
$python setup.py install --prefix=/usr --root="$subpkgdir"
 
# Add version suffix to executable files.
local path; for path in "$subpkgdir"/usr/bin/*; do
mv "$path" "$path-$pyver"
done
}
</pre>
 
 
=== Multiversion package for project using 2to3 ===
 
Some Python projects use 2to3 (or similar tools) during build phase to make the code-base compatible with Python 3.
Since it modifies sources during build, you cannot run <tt>setup.py build</tt> with python2 and python3 one after another with the same source files.
You must duplicate the buildir and run them separately.
 
<pre>
prepare() {
default_prepare
 
# Soure files are modified during build with 2to3 tool, so we cannot
# build it for both Python versions in the same location.
local python; for python in python2 python3; do
cp -r "$builddir" "$builddir-$python"
done
}
 
build() {
local python; for python in python2 python3; do
cd "$builddir"-$python
$python setup.py build
done
}
 
_py() {
...
 
cd "$builddir"-$python
$python setup.py install --prefix=/usr --root="$subpkgdir"
}
</pre>
 
=== Generating documentation ===
 
To generate documentation:
 
<pre>
makedepends="py-sphinx"
subpackages="$pkgname-doc"
 
package() {
...
        cd "$builddir"/doc
        install -d "$pkgdir"/usr/share/html/$pkgname "$pkgdir"/usr/share/man/man1
        PYTHONPATH="$builddir/src:$PYTHONPATH" make html man
        install -t "$pkgdir"/usr/share/man/man1 build/man/*
        mv build/html/* "$pkgdir"/usr/share/html/$pkgname
}
}
</pre>
</pre>


[[Category:Development]] [[Category:Python]]
[[Category:Development]] [[Category:Python]]

Revision as of 12:39, 30 June 2022

A lot of Python packages use the setuptools or distutils framework. This mean that the build() and the package() section looks a bit different compared to an application which uses make.

Considerations

pkgname

Package name for a Python library must be prefixed with py3-.

For an 'executable' (for example, black, binwalk), you generally don't need to prefix it.

There’s no exact rule if the prefix should be used for tools and applications written in Python, it varies.

arch

noarch
Use for pure Python packages (i.e. without compiled code). Also add python3 to depends=.
all (and others)
Use for packages with native extensions (i.e. with compiled code). Do not add python3 to depends= (it's auto-detected via dynamic linking to python library).

source

Most Python packages are published in PyPI (the Python Package Index). If the package has a source tarball available in PyPI (that’s true for most packages), and it contains tests (some explicitly remove them from PyPI), you should reference it in source= as:

https://files.pythonhosted.org/packages/source/${_pyname%${_pyname#?}}/$_pyname/$_pyname-$pkgver.tar.gz

where _pyname is the real name of the Python package.

Otherwise, use the normal upstream git tarballs.

Examples

Package for setuptools/setup.py build

If the project has a setup.py, then the APKBUILD is very simple:

pkgname="py3-foo"
_pyname="foo"
...
depends="python3"
makedepends="python3-dev"
checkdepends="py3-pytest"
...

build() {
	python3 setup.py build
}

check() {
	pytest
}

package() {
	python3 setup.py install --skip-build --root="$pkgdir"
}

Note that PyPI tarballs can contain a generated setup.py that does not exist in the upstream repo - this means you must use the pyproject.toml method if you change sources.

Package for pyproject.toml build

pkgname="py-foo"
_pyname="PyFoo"
...
depends="python3 py3-bar py3-baz"
makedepends="py3-build py3-installer python3-dev py3-wheel"
...

build() {
	python3 -m build --no-isolation --wheel
}

check() {
	pytest
}

package() {
	python3 -m installer -d "$pkgdir" \
		dist/PyFoo-$pkgver-py3-none-any.whl
}

Depending on the build-backend in the pyproject.toml, you might need py3-setuptools or py3-flit-core or py3-poetry-core or py3-hatchling at build time.

Sometimes you might have to pass the path to the built package to run tests:

check() {
	python3 -m installer -d testenv \
		dist/PyFoo-$pkgver-py3-none-any.whl
	local sitedir="$(python3 -c 'import site;print(site.getsitepackages()[0])')"
	PYTHONPATH="$PWD/testenv/$sitedir" pytest
}