Jump to content
From ArchWiki

Этот документ охватывает стандарты и рекомендации по написанию PKGBUILDов для программного обеспечения Python.

## Именование пакета

Для библиотечных модулей Python 3 используйте `python-modulename`. Также используйте префикс, если пакет предоставляет программу, тесно связанную с экосистемой Python (например, pip или tox). Для других приложений используйте только имя программы.

Note: Название пакета должно быть полностью в нижнем регистре.

## Архитектура

Смотрите PKGBUILD#arch.

Пакет Python, содержащий расширения C, является архитектурно-зависимым. В противном случае он, скорее всего, является архитектурно-независимым.

Пакеты, собранные с помощью setuptools, определяют свои расширения C с помощью ключевого слова `ext_modules` в `setup.py`.

## Исходники

URL загрузки, связанные с сайтом PyPI, содержат непредсказуемый хэш, который необходимо получать с сайта PyPI каждый раз, когда пакет должен быть обновлен. Это делает их непригодными для использования в PKGBUILD. PyPI предоставляет альтернативную стабильную схему: массив `source=()` PKGBUILD#source должен использовать следующие шаблоны URL:

Исходный пакет
  `https://files.pythonhosted.org/packages/source/${_name::1}/$_name/$_name-$pkgver.tar.gz`
рулевой пакет Pure Python
  `https://files.pythonhosted.org/packages/py2.py3/${_name::1}/$_name/${_name//-/_}-$pkgver-py2.py3-none-any.whl` (Bilingual – Python 2 and Python 3 compatible)
  `https://files.pythonhosted.org/packages/py3/${_name::1}/$_name/${_name//-/_}-$pkgver-py3-none-any.whl` (Python 3 only)
  Обратите внимание, что имя дистрибутива может содержать тире, а его представление в имени файла wheel - нет (они преобразуются в символы подчеркивания).
Архитектурно-специфический рулевой пакет
  Дополнительные массивы, специфичные для архитектуры, могут быть добавлены путем добавления подчеркивания и имени архитектуры, например, `source_x86_64=('...')`. Также `_py=cp310` можно использовать, чтобы не повторять версию Python:
  `https://files.pythonhosted.org/packages/$_py/${_name::1}/$_name/${_name//-/_}-$pkgver-$_py-${_py}m-manylinux1_x86_64.whl`

Обратите внимание, что вместо `_name` используется пользовательская переменная `pkgname`, поскольку пакеты Python обычно имеют префикс `python-`. Эта переменная может быть определена следующим образом:

    _name=${pkgname#python-}

## Методы установки

Python packages are generally installed using language-specific package manager such as pip, which fetches packages from an online repository (usually PyPI, the Python Package Index) and tracks the relevant files.

However, for managing Python packages from within `PKGBUILD`s, one needs to "install" the Python package to the temporary location `$pkgdir/usr/lib/python<Python version>/site-packages/$pkgname`.

For Python packages using standard metadata to specify their build backend in `pyproject.toml`, this can most easily achieved using python-build and python-installer. Old packages might fail to specify that they use setuptools, and only offer a `setup.py` that has to be invoked manually.

Note: Dependencies from the package's metadata must be defined in the `depends` array otherwise they will not be installed.

### Основные стандарты (PEP 517)

Рабочий процесс, основанный на стандартах, прост: Создайте колесо с помощью python-build и установите его в `$pkgdir` с помощью python-installer:

    makedepends=(python-build python-installer python-wheel)

    build() {
        cd "$_name-$pkgver"
        python -m build --wheel --no-isolation
    }

    package() {
        cd "$_name-$pkgver"
        python -m installer --destdir="$pkgdir" dist/*.whl
    }

где

- `--wheel` results in only a wheel file to be built, no source distribution.
- `--no-isolation` means that the package is built using what is installed on your system (which includes packages you specified in `depends`), by default the tool creates an isolated virtual environment and performs the build there.
- `--destdir="$pkgdir"` prevents trying to directly install in the host system instead of inside the package file, which would result in a permission error
- `--compile-bytecode=...` or `--no-compile-bytecode` can be passed to `installer`, but the default is sensibly picked, so this should not be necessary.

Warning: Skipping `build` and putting the `.whl` file in your `source` array is discouraged in favor of building from source, and should only be used when the latter is not a viable option (for example, packages which only come with wheel sources, and therefore cannot be built from source).
Warning: If your package is a VCS package (`python-…-git`), include the command `git -C "${srcdir}/${pkgname}" clean -dfx` in your `prepare` function. This removes stale wheels along with other build artifacts, and helps prevent issues further down the road. See also upstream issues for setuptools and Poetry.

### setuptools или distutils

Если `pyproject.toml` не найден или он не содержит таблицы `[build-system]`, это означает, что проект использует старый унаследованный формат, использующий файл setup.py, который вызывает setuptools или distutils. Обратите внимание, что хотя distutils включен в стандартную библиотеку Python - наличие установленного setuptools означает, что вы используете исправленную версию distutils.

    makedepends=('python-setuptools')  # если только он не требует именно distutils

    build() {
        cd "$_name-$pkgver"
        python setup.py build
    }

    package() {
        cd "$_name-$pkgver"
        python setup.py install --root="$pkgdir" --optimize=1
    }

где:

- `--root="$pkgdir"` работает как `--destdir` выше
- `--optimize=1` компилирует оптимизированные файлы байткода (.opt-1.pyc), чтобы они могли отслеживаться pacman вместо того, чтобы создаваться на хостовой системе по требованию.
- Добавление `--skip-build` оптимизирует ненужную попытку повторного выполнения шагов сборки, уже запущенных в функции `build()`, если это имеет место.

Если полученный пакет включает исполняемые файлы, которые импортируют устаревший модуль pkg_resources, то setuptools должен быть дополнительно указан как `depends` в функции разделения `package_*()`; альтернативно, если PKGBUILD устанавливает пакет Python только для одной версии Python, setuptools должен быть перемещен из `makedepends` в `depends`.

Некоторые пакеты пытаются использовать setuptools и возвращаются к distutils, если setuptools не может быть импортирован. В этом случае setuptools должен быть добавлен как `makedepends`, чтобы результирующие метаданные Python были лучше.

Если пакет требует сборки setuptools из-за включения исполняемых файлов (что не поддерживается distutils), но импортирует только distutils, то при сборке будет выдано предупреждение, а полученный пакет будет поврежден (он не будет содержать исполняемых файлов):

    /usr/lib/python3.8/distutils/dist.py:274: UserWarning: Unknown distribution option: 'entry_points'
      warnings.warn(msg)

Необходимо сообщить об ошибке в upstream. Для обхода проблемы можно использовать недокументированную функцию setuptools:

    # не удается из-за distutils
    python setup.py build

    # работает, используя setuptools shim
    python -m setuptools.launch setup.py build

Если пакет использует python-setuptools-scm, пакет, скорее всего, не будет собран с ошибкой, такой как:

    LookupError: setuptools-scm was unable to detect version for /build/python-jsonschema/src/jsonschema-3.2.0.

    Make sure you're either building from a fully intact git repository or PyPI tarballs. Most other sources (such as GitHub's tarballs, a git checkout without the .git folder) don't contain the necessary metadata and will not work.

Для получения сборки `SETUPTOOLS_SCM_PRETEND_VERSION` необходимо экспортировать как переменную окружения с `$pkgver` в качестве значения:

    export SETUPTOOLS_SCM_PRETEND_VERSION=$pkgver

## Проверка

Warning: Избегайте использования `tox` для запуска testsuites, поскольку она явно предназначена для проверки повторяющихся конфигураций, загруженных из PyPI во время работы `tox`, и не проверяет версию, которая будет установлена пакетом. Это противоречит цели наличия функции check вообще.

Большинство проектов Python, предоставляющих набор тестов, используют nosetests или pytest для запуска тестов с `test` в имени файла или каталога, содержащего набор тестов. В общем случае для запуска набора тестов достаточно просто запустить `nosetests` или `pytest`.

    check(){
        cd "$srcdir/foo-$pkgver"

        # Для nosetests
        nosetests

        # Для pytest
        pytest
    }

Если есть скомпилированное расширение C, тесты необходимо запускать, используя `$PYTHONPATH`, отражающий текущую мажорную и минорную версию Python, чтобы найти и загрузить его.

    check(){
      cd "$pkgname-$pkgver"
      local python_version=$(python -c 'import sys; print("".join(map(str, sys.version_info[:2])))')
      # Для nosetests
      PYTHONPATH="$PWD/build/lib.linux-$CARCH-cpython-${python_version}" nosetests

      # Для pytest
      PYTHONPATH="$PWD/build/lib.linux-$CARCH-cpython-${python_version}" pytest
    }

Некоторые проекты предоставляют `setup.py` точки входа для запуска теста. Это работает как для `pytest`, так и для `nosetests`.

    check(){
        cd "$srcdir/foo-$pkgver"

        # For nosetests
        python setup.py nosetests

        # For pytest - needs python-pytest-runner
        python setup.py pytest
    }

## Советы и рекомендации

### Обнаружение удаленных подписей PGP на PyPI

Если для данного тарбола Python sdist существуют отдельные PGP-подписи - их следует использовать для проверки тарбола. Однако файлы подписей не отображаются непосредственно в разделе загрузки файлов какого-либо проекта на pypi.org. Чтобы обнаружить тарбалы sdist и их потенциальные файлы подписей, можно воспользоваться этим сервисом для получения обзора по каждому проекту: https://pypi.debian.net/.

Для python-requests, это будет https://pypi.debian.net/requests.

### Использование версии python

Иногда во время подготовки, сборки, тестирования или установки требуется указать основную и вторую версию Python для системы. Не записывайте это в жестком коде (например, `3.9` или `3.10`), а используйте вызов интерпретатора Python для получения информации и сохранения ее в локальной переменной:

    check(){
      local python_version=$(python -c 'import sys; print(".".join(map(str, sys.version_info[:2])))')
      ...
    }

### Использование site-packages

Иногда во время сборки, тестирования или установки требуется обратиться к директории `site-packages` системы. Не следует жестко кодировать этот каталог, вместо этого используйте вызов интерпретатора Python для получения пути и сохранения его в локальной переменной:

    check(){
      local site_packages=$(python -c "import site; print(site.getsitepackages()[0])")
      ...
    }

### Тестовый каталог в site-packages

Убедитесь, что вы не установили каталог с именем `tests` в `site-packages`. (т.е. `/usr/lib/pythonX.Y/site-packages/tests/`). Проекты пакетов Python, использующие setuptools, иногда неправильно конфигурируются для включения каталога, содержащего тесты, в качестве Python-пакета верхнего уровня. Если вы столкнулись с этим, то можете помочь, подав заявку в проект пакета с просьбой исправить это, например, вот так.

Category:

- Arch package guidelines (Русский)