Сборка и пакетирование
Состав рабочего каталога в процессе сборки
Исходные файлы (в репозитории хранятся только они)
Локальные файлы (прописаны в .gitignore)
Целевой генерат (не кладётся в репозиторий) — то, что мы собираем, тестируем и т. п.
- бинарники, библиотеки
- документация
дистрибуции (.zip, .tar.gz, .whl и т. п.)
- …
Промежуточный генерат, отходы (не кладутся в репозиторий): .o файлы, отчёты, кеш, индексы, …
Задачи сборочной среды
- Оперативное развёртывание сборочного окружения (модули, инструменты)
Сборка целевых файлов
Makefile генерируется, например, sphinx-ом.
- Неоправданно мощный инструмент для Python, зато годится для всего и часто есть в готовых проектах.
- Очень простой синтаксис и логика
- Достаточен для небольших проектов
Т. н. проекты в различных IDE (WingIDE, Spyder, PyCharm и т. п.)
- …
- Удаление из окружения лишних файлов (отходов / всех генератов)
git clean -fd
Специальные цели clean и distclean в подсистемах сборки
- Python: сборка дистрибутивов
Setuptools и вокруг него
Формирование дистрибутива с помощью Setuptools
Создадим проект.
~/src $ mkdir pkg_propj ~/src $ cd pkg_propj ~/src/pkg_propj $ mkdir dumblorem ~/src/pkg_propj $ python3 -m venv venv ~/src/pkg_propj $ . ./venv/bin/activate (venv) ~/src/pkg_propj $ pip install pylorem Collecting pylorem Using cached https://files.pythonhosted.org/packages/67/4d/90f5631847467a1d39e96271534379de8221ff79bcaa85203242524c9b23/pylorem-1.2-py3-none-any.whl Installing collected packages: pylorem Successfully installed pylorem-1.2
Python-окружение в отдельном каталоге venv
Для работы модуля потребуется модуль pylorem
dumblorem/__init__.py:
1 #!/usr/bin/env python3
2 '''
3 Very dummy pylorem test
4 '''
5
6 import pylorem
7
8 L = pylorem.LoremIpsum() # Main pylorem object
9
10 def once():
11 '''Just a sentence
12 >>> once().count('.')
13 1
14 '''
15 return L.sentence()
16
17 def many():
18 '''Full parageaph
19 >>> many().count('.')>1
20 True
21 '''
22 return L.paragraph()
Из этого уже можно сделать устанавливаемый модуль! См. учебник
setup.py:
Это просто программа на python, содержащая вызов функции setup()
Возможно, понадобится ещё pip install setuptools
«Чистый» модуль в build/lib
(venv) ~/src/pkg_propj $ python setup.py build running build running build_py creating build creating build/lib creating build/lib/dumblorem copying dumblorem/__init__.py -> build/lib/dumblorem
Архив с исходниками
Команда:python setup.py sdist
- Недостаточно метаинформации: нет README, url и author_email
- Ок,
setup.py
from setuptools import setup, find_packages setup( name="dumblorem", version="0.1", author="Me", author_email="me@example.com", description="This is an dummy lorem package`1", url="http://example.com/DumbLorem/", packages=find_packages(), )
Теперь:
(venv) ~/src/pkg_propj $ python setup.py sdist running sdist running egg_info writing dumblorem.egg-info/PKG-INFO writing dependency_links to dumblorem.egg-info/dependency_links.txt writing top-level names to dumblorem.egg-info/top_level.txt reading manifest file 'dumblorem.egg-info/SOURCES.txt' writing manifest file 'dumblorem.egg-info/SOURCES.txt' running check creating dumblorem-0.1 creating dumblorem-0.1/dumblorem creating dumblorem-0.1/dumblorem.egg-info copying files to dumblorem-0.1... copying README -> dumblorem-0.1 copying setup.py -> dumblorem-0.1 copying dumblorem/__init__.py -> dumblorem-0.1/dumblorem copying dumblorem.egg-info/PKG-INFO -> dumblorem-0.1/dumblorem.egg-info copying dumblorem.egg-info/SOURCES.txt -> dumblorem-0.1/dumblorem.egg-info copying dumblorem.egg-info/dependency_links.txt -> dumblorem-0.1/dumblorem.egg-info copying dumblorem.egg-info/top_level.txt -> dumblorem-0.1/dumblorem.egg-info Writing dumblorem-0.1/setup.cfg Creating tar archive removing 'dumblorem-0.1' (and everything under it) (venv) ~/src/pkg_propj $ tar tvf dist/dumblorem-0.1.tar.gz drwxr-xr-x george/george 0 2020-04-28 21:56 dumblorem-0.1/ -rw-r--r-- george/george 230 2020-04-28 21:56 dumblorem-0.1/PKG-INFO -rw-r--r-- george/george 0 2020-04-28 21:56 dumblorem-0.1/README drwxr-xr-x george/george 0 2020-04-28 21:56 dumblorem-0.1/dumblorem/ -rw-r--r-- george/george 325 2020-04-28 21:37 dumblorem-0.1/dumblorem/__init__.py drwxr-xr-x george/george 0 2020-04-28 21:56 dumblorem-0.1/dumblorem.egg-info/ -rw-r--r-- george/george 230 2020-04-28 21:56 dumblorem-0.1/dumblorem.egg-info/PKG-INFO -rw-r--r-- george/george 169 2020-04-28 21:56 dumblorem-0.1/dumblorem.egg-info/SOURCES.txt -rw-r--r-- george/george 1 2020-04-28 21:56 dumblorem-0.1/dumblorem.egg-info/dependency_links.txt -rw-r--r-- george/george 10 2020-04-28 21:56 dumblorem-0.1/dumblorem.egg-info/top_level.txt -rw-r--r-- george/george 38 2020-04-28 21:56 dumblorem-0.1/setup.cfg -rw-r--r-- george/george 269 2020-04-28 21:55 dumblorem-0.1/setup.py
Архив с модулем: python setup.py bdist
Дистрибутив Wheel
Нужно дополнение wheel для setuptools:
(venv) ~/src/pkg_propj $ python setup.py --help-commands … Extra commands: alias define a shortcut to invoke one or more commands bdist_egg create an "egg" distribution develop install package in 'development mo … (venv) ~/src/pkg_propj $ pip install wheel Collecting wheel Using cached wheel-0.34.2-py2.py3-none-any.whl (26 kB) Installing collected packages: wheel Successfully installed wheel-0.34.2 (venv) ~/src/pkg_propj $ python setup.py --help-commands … Extra commands: bdist_wheel create a wheel distribution alias define a shortcut to invoke one or more commands bdist_egg create an "egg" distribution …
Сделаем этот bdist_wheel:
(venv) ~/src/pkg_propj $ python setup.py bdist_wheel running bdist_wheel running build running build_py … removing build/bdist.linux-x86_64/wheel (venv) ~/src/pkg_propj $ zipinfo dist/dumblorem-0.1-py3-none-any.whl Archive: dist/dumblorem-0.1-py3-none-any.whl Zip file size: 1404 bytes, number of entries: 5 -rw-r--r-- 2.0 unx 325 b- defN 20-Apr-28 18:37 dumblorem/__init__.py -rw-r--r-- 2.0 unx 220 b- defN 20-Apr-28 19:01 dumblorem-0.1.dist-info/METADATA -rw-r--r-- 2.0 unx 92 b- defN 20-Apr-28 19:01 dumblorem-0.1.dist-info/WHEEL -rw-r--r-- 2.0 unx 10 b- defN 20-Apr-28 19:01 dumblorem-0.1.dist-info/top_level.txt ?rw-rw-r-- 2.0 unx 374 b- defN 20-Apr-28 19:01 dumblorem-0.1.dist-info/RECORD 5 files, 1021 bytes uncompressed, 704 bytes compressed: 31.0%
Этот модуль по-человечески не устанавливается, потому что для него нужен ещё pylorem. Этот pylorem надо добавить в setup.py:
Снова делаем bdist_wheel и пытаемся его установить в чистом окружении:
(testenv) george@inspiron:~/src/testenv> python3 -c 'import pylorem' Traceback (most recent call last): File "<string>", line 1, in <module> ModuleNotFoundError: No module named 'pylorem' (testenv) george@inspiron:~/src/testenv> pip install ../pkg_propj/dist/dumblorem-0.1-py3-none-any.whl Processing /home/george/src/pkg_propj/dist/dumblorem-0.1-py3-none-any.whl Collecting pylorem (from dumblorem==0.1) Using cached https://files.pythonhosted.org/packages/67/4d/90f5631847467a1d39e96271534379de8221ff79bcaa85203242524c9b23/pylorem-1.2-py3-none-any.whl Installing collected packages: pylorem, dumblorem Successfully installed dumblorem-0.1 pylorem-1.2 (testenv) george@inspiron:~/src/testenv> python3 -c 'import dumblorem; print(dumblorem.once())' Oris paciscor inceptum subseco nitor servus scriptor illius congregatio nimium paro summopere approbo monstrum scaphiumsciphus.
Дополнительная сборка и очистка
Изготовим другой словарь для pylorem, вот так (mklorem.py):
См. учебник по ninja
Напишем Ninja-файл build.ninja:
(venv) ~/src/pkg_propj $ cat build.ninja rule newlorem command = python3 mklorem.py > $out build dumblorem/lorem.json: newlorem (venv) ~/src/pkg_propj $ ninja dumblorem/lorem.json [1/1] python3 mklorem.py > dumblorem/lorem.json (venv) ~/src/pkg_propj $ ls dumblorem __init__.py lorem.json (venv) ~/src/pkg_propj $ ninja -t clean Cleaning... 1 files. (venv) ~/src/pkg_propj $ ls dumblorem __init__.py
Как видно, ninja знает, что dumblorem/lorem.json — генерат, и умеет сам его удалять по команде clean
Научим dumblorem читать этот файл:
1 #!/usr/bin/env python3
2 '''
3 Very dummy pylorem test
4 '''
5
6 import pylorem
7 import pathlib
8 import os
9
10 L = pylorem.LoremIpsum() # Main pylorem object
11
12 def rev():
13 '''Use reversed vocabular
14 >>> many().count(" o")
15 True
16 '''
17 pd = pathlib.Path(__file__).parent.absolute()
18 pylorem.lorem.loremfile = os.path.join(pd, "lorem.json")
19
20 def once():
21 '''Just a sentence
22 >>> once().count('.')
23 1
24 '''
25 return L.sentence()
26
27 def many():
28 '''Full parageaph
29 >>> many().count('.')>1
30 True
31 '''
32 return L.paragraph()
Надо ещё научить setup.py распространять этот .json вместе с пакетом. Для этого надо добавить package_data={ "": ["*.json"], }, в параметры вызова setup().
Научим ninja удалять все гененраты, проводить тесты и даже делать колесо!
(venv) ~/src/pkg_propj $ cat build.ninja generated = build dist dumblorem.egg-info rule newlorem command = python3 mklorem.py > $out rule distclean command = rm -rf $generated; ninja -t clean rule wheelbuild command = python setup.py bdist_wheel build dumblorem/lorem.json: newlorem build distclean: distclean build wheel: wheelbuild || dumblorem/lorem.json default wheel
Запись wheelbuild || dumblorem/lorem.json задаёт зависимость по сборке
(venv) ~/src/pkg_propj $ ninja distclean [1/1] rm -rf build dist dumblorem.egg-info; ninja -t clean Cleaning... 0 files. (venv) ~/src/pkg_propj $ ninja [2/2] python setup.py bdist_wheel running bdist_wheel running build running build_py …
Сборочные зависимости
Поставим ещё один инструмент: анализатор import-ов:
(venv) ~/src/pkg_propj $ pip install pipreqs Collecting pipreqs Using cached pipreqs-0.4.10-py2.py3-none-any.whl (25 kB) Collecting yarg Using cached yarg-0.1.9-py2.py3-none-any.whl (19 kB) Processing /home/george/.cache/pip/wheels/56/ea/58/ead137b087d9e326852a851351d1debf4ada529b6ac0ec4e8c/docopt-0.6.2-py2.py3-none-any.whl Collecting requests Using cached requests-2.23.0-py2.py3-none-any.whl (58 kB) Collecting chardet<4,>=3.0.2 Using cached chardet-3.0.4-py2.py3-none-any.whl (133 kB) Collecting certifi>=2017.4.17 Using cached certifi-2020.4.5.1-py2.py3-none-any.whl (157 kB) Collecting idna<3,>=2.5 Using cached idna-2.9-py2.py3-none-any.whl (58 kB) Collecting urllib3!=1.25.0,!=1.25.1,<1.26,>=1.21.1 Using cached urllib3-1.25.9-py2.py3-none-any.whl (126 kB) Installing collected packages: chardet, certifi, idna, urllib3, requests, yarg, docopt, pipreqs Successfully installed certifi-2020.4.5.1 chardet-3.0.4 docopt-0.6.2 idna-2.9 pipreqs-0.4.10 requests-2.23.0 urllib3-1.25.9 yarg-0.1.9
Сколько теперь модулей в окружении?
(venv) ~/src/pkg_propj $ pip freeze certifi==2020.4.5.1 chardet==3.0.4 docopt==0.6.2 idna==2.9 ninja==1.9.0.post1 pipreqs==0.4.10 pylorem==1.2 requests==2.23.0 urllib3==1.25.9 yarg==0.1.9
Какие из них действительно нужны при сборке? А кто ж вас знает!
Какие из них нужно добавлять в setup.py? Далеко не все! Можно попробовать воспользоваться анализатором pipreqs`
(venv) ~/src/pkg_propj $ pipreqs --print pylorem==1.2
- Как и следовало ожидать, один только pylorem