Сборка и пакетирование
Состав рабочего каталога в процессе сборки
- Исходные файлы (в репозитории хранятся только они) 
- Локальные файлы (прописаны в .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
