Сборка и пакетирование

Состав рабочего каталога в процессе сборки

  1. Исходные файлы (в репозитории хранятся только они)

  2. Локальные файлы (прописаны в .gitignore)

  3. Целевой генерат (не кладётся в репозиторий) — то, что мы собираем, тестируем и т. п.

    • бинарники, библиотеки
    • документация
    • дистрибуции (.zip, .tar.gz, .whl и т. п.)

  4. Промежуточный генерат, отходы (не кладутся в репозиторий): .o файлы, отчёты, кеш, индексы, …

Задачи сборочной среды

Формирование дистрибутива с помощью 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

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:

   1 from setuptools import setup, find_packages
   2 
   3 setup(
   4     name="dumblorem",
   5     version="0.1",
   6     packages=find_packages(),
   7 )

«Чистый» модуль в 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

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:

   1 from setuptools import setup, find_packages
   2 
   3 setup(
   4     name="dumblorem",
   5     version="0.1",
   6     author="Me",
   7     author_email="me@example.com",
   8     description="This is an dummy lorem package`1",
   9     url="http://example.com/DumbLorem/",
  10     install_requires=["pylorem"],
  11     packages=find_packages(),
  12 )

Снова делаем 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):

   1 #!/usr/bin/env python3
   2 
   3 import pylorem
   4 import json
   5 import sys
   6 
   7 with open(pylorem.lorem.loremfile, "r") as fp:
   8     J = json.load(fp)
   9     JJ = ["".join(reversed(w)) for w in J]
  10     json.dump(JJ, sys.stdout)

См. учебник по 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

Научим 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

(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

LecturesCMC/PythonDevelopment2020/10_Packaging (последним исправлял пользователь ArsenyMaslennikov 2021-03-09 18:26:04)