Дата и время публикации:
Проблема и решение
1. Суть проблемы
Во время разворачивания Python-3.8 из исходных кодов [3.1], поимел показанную в дампе 1.1 проблему установки пакетов, которые позволяют расширать возможности бинарного интерпретатора CPython
Основная проблема использования toolsetup является вроде бы в невинном предупреждении во время python3.8 setup.py install, показанным в дампе 1.1
Дамп 1.1
$ python3.8 setup.py install ... /home/user/.sources/Python-3.8.13/build/python-setuptools/setuptools/command/easy_install.py:144: EasyInstallDeprecationWarning: easy_install command is deprecated. Use build and pip and other standards-based tools. ...
Так давайте по порядку — где находится суть загвоздки?
Для этого, сначала мне потребовалось развернуть Python-3.8 следующим образом, как показано в дампе 1.2
Дамп 1.2
~$ mkdir .sources && cd .sources ~/.sources$ sudo apt update … ~/.sources$ sudo apt build-dep python3.8 ~/.sources$ sudo apt upgrade ~/.sources$ wget -c \ https://www.python.org/ftp/python/3.8.13/Python-3.8.13.tgz && \ tar xzf Python-3.8.13.tgz && \ cd Python-3.8.13 ~/.sources/Python-3.8.13$ ./configure —prefix=$HOME/.local/share/python-3.8 ~/.sources/Python-3.8.13$ make -j8 ~/.sources/Python-3.8.13$ make install … Installing collected packages: setuptools, pip WARNING: The scripts pip3 and pip3.8 are installed in '/home/user/.local/share/python-3.8/bin' which is not on PATH. Consider adding this directory to PATH or, if you prefer to suppress this warning, use --no-warn-script-location. Successfully installed pip-22.0.4 setuptools-56.0.0
При этом, как показано в дампе 1.2 обращаем внимание на вроде бы уже установленной версии setuptools-56.0.0, как показано в дампе 1.4
Дамп 1.3
$ python3.8 -m pip list Package Version ---------- ------- build 0.8.0 packaging 21.3 pep517 0.12.0 pip 22.0.4 pyparsing 3.0.9 setuptools 56.0.0 tomli 2.0.1
добавить путь $HOME/.sources/Python-3.8.13/.local/share/python-3.8/bin к PATH файла ~/.profile, как показано в дампе 1.4
Дамп 1.4
... # set PATH so it includes user's private .local/share/python-3.8/bin if it exists if [ -d "$HOME/.local/share/python-3.8/bin" ] ; then PATH="$HOME/.local/share/python-3.8/bin:$PATH" fi ...
или выполнить просто экспорт переменной окружении PATH на время выполнения сеанса пользователя, как показано в дампе 1.5
Дамп 1.5
~$ export PATH=$HOME/.local/share/python-3.8/bin:$PATH
После чего нужно стянуть последнюю версию пакетов python-build и python-setuptools, как показано в дампе 1.6
Дамп 1.5
~/.sources/Python-3.8.13/build$ git clone https://github.com/pypa/build.git python-build && cd python-build ... ~/.sources/Python-3.8.13/build/python-build$ cd .. ~/.sources/Python-3.8.13/build$ git clone https://github.com/pypa/setuptools.git python-setuptools && cd python-setuptool ...
И была выполнена успешна команда облегченной установки, как показано в дампе 1.6
Дамп 1.6
python3.8 setup.py install ... /home/user/.sources/Python-3.8.13/build/python-setuptools/setuptools/command/easy_install.py:144: EasyInstallDeprecationWarning: easy_install command is deprecated. Use build and pip and other standards-based tools. ...
из которого видно, что установка сопровождалась предупреждением easy_install command is deprecated..., что станет в будущем несомненно проблемой.
Потому что начиная с версии setuptools-42.0 скрипта easy_install запрещена, а формат дистрибьюции egg больше не поддерживается, вместо последнего стоит воспользоватся wheel (файлы с расширением .whl) или обычной дистрибьюцей (файлы с расширением .tar.gz). Соответственно, что они устанавливаются с помощью модуля pip, который требует чтобы эти пакеты были собраны в определенных форматах . Поэтому нужно понимать как и что использовать вместо python setup.py install, а именно:
- формат wheel — встроенный формат в Python, называемый "wheel", который обеспечивает свойственный Python, гибкий пакетный формат, позволяющий быстро устанавливать программное обеспечение вместе пересборки каждый раз из исходников (PEP-491)[3.9]
- distutils/setuptools — управление независимым форматом сборки для дерева исходников (PEP-517)[3.10],
- минимальные требования к проекту Python (PEP-518)[3.11], которые показаны в дампе 1.7
Дамп 1.7
[build-system] # Minimum requirements for the build system to execute. requires = ["setuptools", "wheel"] # PEP 508 specifications.
Кроме того, нужно затвердить, что проект вашего программного обеспечения в Python не всегда равно пакету, потому что последний — это голая дистрибьюция, которая прежде всего содержит модули и порождает скрипты запуска этих модулей, если они были заданы в проекте до начала сборки.
Также в конце коснусь вопроса почему была запрещена read_configuration из setuptools.config и что использовать вместо неё.
2. Решение
2.1 Использование формата wheel
Формат собранного пакета Wheel, обеспечивающий удобный формат раздачи пакетов с модулями Python с использованием команды, формат которой показан в дампе 2.1.1
Дамп 2.1.1
python -m pip install {proto://}path/to/location/zip-archieve-file.whl
Где
- proto:// — один из применяемых протколов http://, https:// или локальный file://
- zip-archieve-file.whl — Wheel является по сути файлом архива zip с определенным форматом файла, который показан в дампе 2.1.2
Дамп 2.1.2
{distribution}-{version}(-{build tag})?-{python tag}-{abi tag}-{platform tag}.whl
Где
- {distribution} — имя дистрибутива с раздаваемыми пакетами, содержащий модули Python, например virtualenv, django и тому подобного,
- {version} — версия дистрибутива, например 1.0;
- {build tag} — дополнительный номер сборки, который должен начинаться с цифры, а затем оставшиеся лексико-графические символы. Тем самым, это позволяет разделить два дистрибутива, которые имеют незначительные, не дотягивающие различая, например, с разными условиями сборки, тестирования и состава пакета;
- {python tag} — тэг совместимости версии Python — обычно указывают для какого поколения Python предназначен данный дистрибутив, например, py27 — Python-2.7, py2 — Python2, py3 — Python3, py37 — Python3.7 и т.д.
- {abi tag} — двоичный интерфейс приложений (ABI), например, ‘cp33m’, ‘abi3’, ‘none’;
- {platform tag} — тэг (программно-)технической платформы, например, ‘linux_x86_64’, ‘any’. Так, ‘linux_x86_64’ устанавливает ограничения на использование ОС — только linux, x86_64 — аппаратная 64-разрядная архитектура процессора x86 ; соответственно, ‘any’ таких ограничений не имеет и может использоваться на любых технических платформах.
2.2 Создание дистрибутива в формате Wheel
Производится с использованием setup.py командой, формат которой показан в дампе 2.2.1
Дамп 2.2.1
python-shellenv-1.6$ python setup.py bdist_wheel running bdist_wheel running build running build_py package init file 'src/__init__.py' not found (or not a regular file) running egg_info writing shellenv.egg-info/PKG-INFO writing dependency_links to shellenv.egg-info/dependency_links.txt writing entry points to shellenv.egg-info/entry_points.txt writing requirements to shellenv.egg-info/requires.txt writing top-level names to shellenv.egg-info/top_level.txt adding license file 'LICENSE' (matched pattern 'LICEN[CS]E*') reading manifest file 'shellenv.egg-info/SOURCES.txt' writing manifest file 'shellenv.egg-info/SOURCES.txt' installing to build/bdist.linux-x86_64/wheel running install running install_lib warning: install_lib: 'build/lib' does not exist -- no Python modules to install running install_egg_info Copying shellenv.egg-info to build/bdist.linux-x86_64/wheel/shellenv-1.7-py3.8.egg-info running install_scripts adding license file "LICENSE" (matched pattern "LICEN[CS]E*") creating build/bdist.linux-x86_64/wheel/shellenv-1.7.dist-info/WHEEL creating 'dist/shellenv-1.7-py2.py3-none-any.whl' and adding 'build/bdist.linux-x86_64/wheel' to it adding 'shellenv-1.7.dist-info/LICENSE' adding 'shellenv-1.7.dist-info/METADATA' adding 'shellenv-1.7.dist-info/WHEEL' adding 'shellenv-1.7.dist-info/entry_points.txt' adding 'shellenv-1.7.dist-info/top_level.txt' adding 'shellenv-1.7.dist-info/zip-safe' adding 'shellenv-1.7.dist-info/RECORD' removing build/bdist.linux-x86_64/wheel
При этом, как показано в дампе 2.2.2, в директории dist/ будет находится соответствующий формату собранный пакет Wheel файл с расширением .whl
Дамп 2.2.2
python-shellenv-1.6$ ls dist shellenv-1.6-py2.py3-none-any.whl python-shellenv-1.6$ python3 -m pip install dist/shellenv-1.7-py2.py3-none-any.whl Processing ./dist/shellenv-1.7-py2.py3-none-any.whl Requirement already satisfied: setuptools>=42 in /home/user/.local/share/python-3.8/lib/python3.8/site-packages (from shellenv==1.7) (56.0.0) Requirement already satisfied: python-dotenv>=0.19 in /home/user/.local/share/python-3.8/lib/python3.8/site-packages (from shellenv==1.7) (0.20.0) Installing collected packages: shellenv Attempting uninstall: shellenv Found existing installation: shellenv 1.6 Uninstalling shellenv-1.6: Successfully uninstalled shellenv-1.6 Successfully installed shellenv-1.7 python-shellenv-1.6$ python3 -m pip list Package Version ------------- ------- ... python-dotenv 0.20.0 setuptools 56.0.0 shellenv 1.7 ... wheel 0.37.1
Эти атрибуты были рассмотрены мной более подробно в использовании setup.py
Поэтому быстро, атрибут bdist_wheel устанавливает универсальный формат wheel c помощью {"bdist_wheel": {"universal": True}} и используется вместе с комнадой python setup.py bdist_wheel
Атрибут zip_safe установлен к True, чтобы устанавливать пакет как zip-файл. В то время как более популярным является установка zip_safe к False, чтобы устанавливать все в отдельную директорию.
При выполнении команды python3 setup.py bdist_wheel может возникать ошибка, которая показана в дампе 2.2.3
Дамп 2.2.3
python-shellenv$ python3 setup.py bdist_wheel usage: setup.py [global_opts] cmd1 [cmd1_opts] [cmd2 [cmd2_opts] ...] or: setup.py --help [cmd1 cmd2 ...] or: setup.py --help-commands or: setup.py cmd --help error: invalid command 'bdist_wheel'
Что решается установкой пакета wheel командой python3 -m pip install
2.3 Унифицированное решение для получение версии и содержимого пакета
Функция read_configuration из setuptools.config запрещена для использования, поэтому начиная с апреля 2020 года рекомендуется использовать pyproject.toml взамен setup.cfg . На самом деле, причиной этого стало желание избежать проблем связанных с использованием формата .ini-файлов в setup.cfg, которые вносят некоторые проблемы, связанные в первую очередь с лексическим разбором, а также возможность разночтения, в виду дублирования информации в setup.py [3.3]
Поэтому чтение версии берется из глобальной переменной модуля __version__, прописанной в файле __init__.py вместе с __copyright__, как показано в дампе 2.3.1
Дамп 2.3.1
... __author__ = "Author: Andrew Rzhaskov" __copyright__ = "Copyright: 2022, Russia Moscow" __version__ = "1.7" ...
Поэтому в setup.py нужно прописать пару функций, которые читают заключенное в двойных/одинарных скобках значение версии из __init__.py, как показано в дампе 2.3.2
Дамп 2.3.2
... def read(filename, encoding='utf-8'): """read file contents""" full_path = os.path.join(os.path.dirname(__file__), filename) with io.open(full_path, encoding=encoding) as fh: contents = fh.read().strip() return contents def get_version(rel_path): for line in read().splitlines(): if line.startswith('__version__'): delim = '"' if '"' in line else "'" return line.split(delim)[1] else: raise RuntimeError("Unable to find version string.") ... setup( ... version=get_version('src/shellenv/__init__.py'), ... include_package_data = False, packages=find_packages(where='src', exclude=["shellenv-tools"]), package_dir={ '': 'src', }, ... )
В данном случае, явно прописывается на каком уровне файловой системы, директории src/ находится пакета 'shellenv' в дереве проекта python-shellenv, в который содержат дополнительный пакет именем shellenv-tools, как показано в дереве дампа 2.3.3
Дамп 2.3.3
. ├── buildout.cfg ├── LICENSE ├── pyproject.toml ├── README.md ├── setup.cfg ├── setup.py └── src ├── shellenv-tools │ ├── __init__.py │ └── __main__.py └── shellenv ├── __init__.py ├── __main__.py ├── cmdlineargs.py ├── shellenv.py └── testrun.py
Которое имеет файлы __init__.py, но пока в настоящей версии, начиная с shellenv-1.7, раздаваемый файл пакета в формате wheel будет собираться только для shellenv
Поэтому в find_packages() используется дополнительный атрибут exclude=, чтобы исключить возникновение ошибки, как показано в дампе 2.3.4
Дамп 2.3.4
$ python3 setup.py bdist_wheel running bdist_wheel running build running build_py error: package directory 'shellenv-tools' does not exist
При этом include_package_data = False, так как данные отдельно не подключаются в данном проекте при сборки дистрибутива, включающего модуль shellenv [3.6]
Кроме того, можно еще попробовать использовать файл MANIFEST.in [3.8], который позволяет игнорировать один или несколько файлов, но также указать какие файлы нужно включать в проект, например, как показано в дампе 2.3.5
Дамп 2.3.4
include AUTHORS include LICENSE include README.md exclude .git exclude .travis.yml recursive include doc * recursive exclude test * global exclude __pycache__ *.pyc
3. Библиография
3.1 Setuptools is removing easy_install, deprecation warning in 42.0.0 #493
3.2 PEP 491 – The Wheel Binary Package Format 1.9
3.3 StackOverflow.Com – Is `setup.cfg` deprecated?
3.4 Single-sourcing the package version
3.5 StackOverflow.Com – What is "where" argument for in setuptools.find_packages?
3.6 Setuptools – Data Files Support
3.7 Setuptools – Understanding the zip_safe flag
3.8 StackOverflow.Com – Is it possible to exclude certain files when building a wheel with setup.py?
3.9 PEP 491
3.10 PEP 517
3.11 PEP 518