Содержание страницы
2. Написание сценария установки¶Writing the Setup Script
Примечание
Этот документ временно сохранён – до тех пор, пока setuptools документация
по адресу https://setuptools.readthedocs.io/en/latest/setuptools.html
не охватит самостоятельно всю соответствующую информацию, представленную здесь.
Сценарий установки является центром всей деятельности по сборке, распространению и установке модулей с помощью Distutils. Основная цель сценария установки – описать ваш дистрибутив модулей для Distutils, чтобы различные команды, работающие с вашими модулями, делали правильные вещи. Как мы видели в разделе A Simple Example выше, сценарий установки состоит в основном из вызова setup(), и большая часть информации, передаваемой Distutils разработчиком модуля, передаётся в виде именованных аргументов setup().
Вот немного более сложный пример, который мы будем рассматривать в следующих нескольких разделах: собственный сценарий установки Distutils. (Имейте в виду, что хотя Distutils входят в состав Python 1.6 и более поздних версий, они также существуют независимо, чтобы пользователи Python 1.5.2 могли использовать их для установки других дистрибутивов модулей. Собственный сценарий установки Distutils, показанный здесь, используется для установки пакета в Python 1.5.2.)
#!/usr/bin/env python
from distutils.core import setup
setup(name='Distutils',
version='1.0',
description='Python Distribution Utilities',
author='Greg Ward',
author_email='gward@python.net',
url='https://www.python.org/sigs/distutils-sig/',
packages=['distutils', 'distutils.command'],
)
Есть только два отличия от тривиального однофайлового дистрибутива, представленного в разделе A Simple Example: больше метаданных и указание чистых модулей Python по пакетам, а не по модулям. Это важно, поскольку Distutils состоят из нескольких десятков модулей, разбитых (пока) на два пакета; явный список всех модулей было бы утомительно составлять и сложно поддерживать. Дополнительную информацию о метаданных см. в разделе Additional meta-data.
Обратите внимание: любые пути (к файлам или каталогам), указанные в сценарии установки, должны записываться в стиле Unix, то есть с разделителем-слешем. Distutils позаботится о преобразовании этого платформонезависимого представления в то, что подходит для вашей текущей платформы, перед фактическим использованием пути. Это делает ваш сценарий установки переносимым между операционными системами, что, конечно, является одной из главных целей Distutils. В этом духе все пути в данном документе записываются через слеш.
Это, конечно, относится только к путям, передаваемым функциям Distutils. Если вы, например, используете стандартные функции Python, такие как glob.glob() или os.listdir() для указания файлов, будьте внимательны и пишите переносимый код вместо жёсткого кодирования разделителей пути:
glob.glob(os.path.join('mydir', 'subdir', '*.html'))
os.listdir(os.path.join('mydir', 'subdir'))
2.1. Перечисление целых пакетов¶Listing whole packages
Опция packages указывает Distutils обрабатывать (собирать, распространять, устанавливать и т. д.) все чистые модули Python, найденные в каждом пакете, упомянутом в списке packages. Для этого, конечно, должно быть соответствие между именами пакетов и каталогами в файловой системе. Соответствие по умолчанию – самое очевидное: пакет distutils находится в каталоге distutils относительно корня дистрибутива. Таким образом, когда вы пишете packages = ['foo'] в своём сценарии установки, вы обещаете, что Distutils найдёт файл foo/__init__.py (название может отличаться в вашей системе, но идея понятна) относительно каталога, где находится ваш сценарий установки. Если вы нарушите это обещание, Distutils выдаст предупреждение, но всё равно обработает проблемный пакет.
Если вы используете другую конвенцию для организации исходного каталога, это не проблема: нужно просто указать опцию package_dir, чтобы сообщить Distutils о вашей конвенции. Например, предположим, что вы храните все исходники Python в lib, так что модули в «корневом пакете» (т. е. не входящие ни в какой пакет) находятся в lib, модули пакета foo – в lib/foo, и так далее. Тогда вы укажете
package_dir = {'': 'lib'}
в своём сценарии установки. Ключами этого словаря являются имена пакетов, а пустое имя пакета означает корневой пакет. Значения – имена каталогов относительно корня вашего дистрибутива. В этом случае, когда вы пишете packages =
['foo'], вы обещаете, что файл lib/foo/__init__.py существует.
Другая возможная конвенция – поместить пакет foo прямо в lib, пакет foo.bar в lib/bar, и т. д. Это можно записать в сценарии установки как
package_dir = {'foo': 'lib'}
Запись package: dir в словаре package_dir неявно применяется ко всем пакетам ниже пакета, поэтому случай foo.bar обрабатывается автоматически. В этом примере наличие packages = ['foo',
'foo.bar'] указывает Distutils искать lib/__init__.py и lib/bar/__init__.py. (Имейте в виду, что хотя package_dir применяется рекурсивно, необходимо явно перечислить все пакеты в packages: Distutils не будет рекурсивно сканировать дерево исходных текстов в поисках каталогов с файлом __init__.py.)
2.2. Перечисление отдельных модулей¶Listing individual modules
Для небольшого дистрибутива модулей может быть удобнее перечислить все модули, а не пакеты – особенно случай одного модуля, который находится в «корневом пакете» (то есть вообще без пакета). Этот простейший случай был показан в разделе Простой пример; вот немного более сложный пример:
py_modules = ['mod1', 'pkg.mod2']
Здесь описываются два модуля: один в «корневом» пакете, другой – в пакете pkg. По умолчанию соответствие пакетов и каталогов подразумевает, что эти два модуля находятся в mod1.py и pkg/mod2.py, а также существует pkg/__init__.py. И снова можно переопределить соответствие пакетов и каталогов с помощью опции package_dir.
2.3. Описание модулей расширения¶Describing extension modules
Так же как написание модулей расширения Python немного сложнее, чем написание чистых модулей Python, их описание для Distutils тоже немного сложнее. В отличие от чистых модулей, недостаточно просто перечислить модули или пакеты и ожидать, что Distutils сам найдёт нужные файлы; необходимо указать имя расширения, исходные файлы и любые требования к компиляции/компоновке (каталоги включения, библиотеки для компоновки и т.д.).
Все это делается через ещё один именованный аргумент для setup() – опцию ext_modules. ext_modules – это просто список экземпляров Extension, каждый из которых описывает один модуль расширения. Предположим, ваш дистрибутив содержит одно расширение с именем foo, реализованное в foo.c. Если не требуется дополнительных указаний компилятору/компоновщику, описание этого расширения довольно просто:
Extension('foo', ['foo.c'])
Класс Extension можно импортировать из distutils.core вместе с setup(). Таким образом, сценарий setup для дистрибутива модуля, содержащего только это одно расширение и ничего больше, может выглядеть так:
from distutils.core import setup, Extension
setup(name='foo',
version='1.0',
ext_modules=[Extension('foo', ['foo.c'])],
)
Класс Extension (точнее, лежащий в основе механизм сборки расширений, реализованный командой build_ext) обеспечивает большую гибкость при описании расширений Python, что объясняется в следующих разделах.
2.3.1. Имена расширений и пакеты¶Extension names and packages
Первым аргументом конструктора Extension всегда является имя расширения, включая любые имена пакетов. Например,
Extension('foo', ['src/foo1.c', 'src/foo2.c'])
описывает расширение, находящееся в корневом пакете, тогда как
Extension('pkg.foo', ['src/foo1.c', 'src/foo2.c'])
описывает то же расширение в пакете pkg. Исходные файлы и результирующий объектный код в обоих случаях идентичны; разница лишь в том, где в файловой системе (а следовательно, где в иерархии пространств имён Python) находится результирующее расширение.
Если у вас есть несколько расширений в одном пакете (или все под одним базовым пакетом), используйте именованный аргумент ext_package для setup(). Например,
setup(...,
ext_package='pkg',
ext_modules=[Extension('foo', ['foo.c']),
Extension('subpkg.bar', ['bar.c'])],
)
скомпилирует foo.c в расширение pkg.foo, а bar.c – в pkg.subpkg.bar.
2.3.2. Исходные файлы расширения¶Extension source files
Второй аргумент конструктора Extension – это список исходных файлов. Поскольку Distutils в настоящее время поддерживает только расширения на C, C++ и Objective-C, это обычно исходные файлы на C/C++/Objective-C. (Не забудьте использовать соответствующие расширения для файлов C++: .cc и .cpp, похоже, распознаются компиляторами как Unix, так и Windows.)
Однако вы также можете включить в список файлы интерфейсов SWIG (.i); команда build_ext умеет работать с расширениями SWIG: она запускает SWIG для файла интерфейса и компилирует полученный C/C++ файл в ваше расширение.
Несмотря на это предупреждение, опции для SWIG в настоящее время можно передавать так:
setup(...,
ext_modules=[Extension('_foo', ['foo.i'],
swig_opts=['-modern', '-I../include'])],
py_modules=['foo'],
)
Или в командной строке так:
> python setup.py build_ext --swig-opts="-modern -I../include"
На некоторых платформах можно включать неисходные файлы, которые обрабатываются компилятором и включаются в ваше расширение. В настоящее время это относится только к файлам текстов сообщений Windows (.mc) и файлам определения ресурсов (.rc) для Visual C++. Они будут скомпилированы в двоичные файлы ресурсов (.res) и слинкованы в исполняемый файл.
2.3.3. Опции препроцессора¶Preprocessor options
Три необязательных аргумента для Extension помогут, если нужно указать каталоги включения для поиска или макросы препроцессора для определения/отмены: include_dirs, define_macros и undef_macros.
Например, если вашему расширению требуются заголовочные файлы в каталоге include в корне вашего дистрибутива, используйте опцию include_dirs:
Extension('foo', ['foo.c'], include_dirs=['include'])
Там можно указать абсолютные пути; если вы знаете, что ваше расширение будет собираться только на Unix-системах с X11R6, установленным в /usr, можно обойтись
Extension('foo', ['foo.c'], include_dirs=['/usr/include/X11'])
Следует избегать такого непереносимого использования, если вы планируете распространять свой код: вероятно, лучше написать код на C так:
#include <X11/Xlib.h>
Если вам нужно включить заголовочные файлы из другого расширения Python, можно воспользоваться тем, что заголовочные файлы устанавливаются согласованным образом командой Distutils install_headers. Например, заголовочные файлы Numerical Python устанавливаются (в стандартной установке Unix) в /usr/local/include/python1.5/Numerical. (Точное расположение будет различаться в зависимости от платформы и установки Python.) Поскольку каталог включения Python – /usr/local/include/python1.5 в данном случае – всегда включён в путь поиска при сборке расширений Python, лучший подход – написать код на C так:
#include <Numerical/arrayobject.h>
Если вам всё же нужно поместить каталог включения Numerical непосредственно в путь поиска заголовков, вы можете найти этот каталог с помощью модуля Distutils distutils.sysconfig:
from distutils.sysconfig import get_python_inc
incdir = os.path.join(get_python_inc(plat_specific=1), 'Numerical')
setup(...,
Extension(..., include_dirs=[incdir]),
)
Несмотря на то, что это вполне переносимо – будет работать в любой установке Python, независимо от платформы, – вероятно, проще просто написать код на C разумным способом.
Можно определять и отменять макросы препроцессора с помощью опций define_macros и undef_macros. define_macros принимает список кортежей (name, value), где name – имя определяемого макроса (строка), а value – его значение: строка или None. (Определение макроса FOO как None эквивалентно простому #define FOO в исходном коде C: в большинстве компиляторов это устанавливает FOO в строку 1.) undef_macros – это просто список макросов для отмены.
Например:
Extension(...,
define_macros=[('NDEBUG', '1'),
('HAVE_STRFTIME', None)],
undef_macros=['HAVE_FOO', 'HAVE_BAR'])
is the equivalent of having this at the top of every C source file:
#define NDEBUG 1
#define HAVE_STRFTIME
#undef HAVE_FOO
#undef HAVE_BAR
2.3.4. Библиотечные опции¶Library options
Также можно указать библиотеки, с которыми нужно компоновать расширение при сборке, и каталоги для поиска этих библиотек. Опция libraries задаёт список библиотек для компоновки, library_dirs – список каталогов для поиска библиотек на этапе компоновки, а runtime_library_dirs – список каталогов для поиска общих (динамически загружаемых) библиотек во время выполнения.
Например, если требуется скомпоновать расширение с библиотеками, которые заведомо находятся в стандартном пути поиска библиотек в целевых системах,
Extension(...,
libraries=['gdbm', 'readline'])
Если нужно скомпоновать расширение с библиотеками в нестандартном расположении, необходимо указать это расположение в library_dirs:
Extension(...,
library_dirs=['/usr/X11R6/lib'],
libraries=['X11', 'Xt'])
(Повторим: подобные непереносимые конструкции следует избегать, если планируется распространять свой код.)
2.3.5. Прочие опции¶Other options
Существуют и другие опции, которые можно использовать для обработки особых случаев.
Опция optional является логической; если она равна true, сбой сборки расширения не прервёт процесс сборки, а просто не приведёт к установке ошибочного расширения.
Опция extra_objects задаёт список объектных файлов, передаваемых компоновщику. Эти файлы не должны иметь расширений, так как используется расширение по умолчанию для компилятора.
extra_compile_args и extra_link_args можно использовать для указания дополнительных параметров командной строки для компилятора и компоновщика соответственно.
export_symbols применим только в Windows. Он может содержать список символов (функций или переменных), подлежащих экспорту. Эта опция не нужна при сборке скомпилированных расширений: Distutils автоматически добавит initmodule в список экспортируемых символов.
Опция depends задаёт список файлов, от которых зависит расширение (например, заголовочные файлы). Команда сборки вызовет компилятор для перестроения расширения, если какой-либо из этих файлов был изменён после предыдущей сборки.
2.4. Взаимосвязи между дистрибутивами и пакетами¶Relationships between Distributions and Packages
Дистрибутив может относиться к пакетам тремя определёнными способами:
Он может требовать пакеты или модули.
Он может предоставлять пакеты или модули.
Он может заменять (делать устаревшими) пакеты или модули.
Эти взаимосвязи можно задать с помощью именованных аргументов функции distutils.core.setup().
Зависимости от других модулей и пакетов Python можно указать, передав именованный аргумент requires функции setup(). Значение должно быть списком строк. Каждая строка задаёт необходимый пакет и, опционально, какие версии достаточны.
Чтобы указать, что требуется любая версия модуля или пакета, строка должна состоять только из имени модуля или пакета. Примеры: 'mymodule' и 'xml.parsers.expat'.
Если требуются конкретные версии, можно указать последовательность квалификаторов в скобках. Каждый квалификатор может состоять из оператора сравнения и номера версии. Допустимые операторы сравнения:
< > ==
<= >= !=
Их можно комбинировать, используя несколько квалификаторов, разделённых запятыми (и необязательными пробелами). В этом случае все квалификаторы должны совпадать; используется логическое И для объединения результатов.
Рассмотрим несколько примеров:
Выражение Requires |
Пояснение |
|---|---|
|
Совместима только версия |
|
Любая версия после |
Теперь, когда мы умеем указывать зависимости, нам также нужно уметь указывать, что мы предоставляем и что могут требовать другие дистрибутивы. Это делается с помощью именованного аргумента provides функции setup(). Значение этого аргумента – список строк, каждая из которых указывает имя модуля или пакета и, опционально, версию. Если версия не указана, считается, что она совпадает с версией дистрибутива.
Несколько примеров:
Выражение Provides |
Пояснение |
|---|---|
|
Предоставить |
|
Предоставить |
Пакет может объявлять, что он заменяет (делает устаревшими) другие пакеты, с помощью именованного аргумента obsoletes. Его значение аналогично значению аргумента requires: список строк, задающих спецификаторы модуля или пакета. Каждый спецификатор состоит из имени модуля или пакета, за которым необязательно следуют один или несколько квалификаторов версии. Версионные квалификаторы указываются в скобках после имени модуля или пакета.
Версии, указанные квалификаторами, считаются заменяемыми (устаревшими) данным дистрибутивом. Если квалификаторы не указаны, считаются устаревшими все версии указанного модуля или пакета.
2.5. Установка скриптов¶Installing Scripts
До сих пор мы имели дело с чистыми и нечистыми модулями Python, которые обычно не запускаются сами по себе, а импортируются скриптами.
Скрипты – это файлы, содержащие исходный код Python, предназначенные для запуска из
командной строки. Скрипты не требуют от Distutils каких-либо сложных действий.
Единственная умная особенность: если первая строка скрипта начинается с
#! и содержит слово «python», Distutils изменит первую строку
так, чтобы она указывала на текущий интерпретатор. По умолчанию она заменяется на
путь к текущему интерпретатору. Опция --executable (или -e)
позволяет явно переопределить путь к интерпретатору.
Опция scripts – это просто список файлов, которые обрабатываются таким
образом. Из скрипта установки PyXML:
setup(...,
scripts=['scripts/xmlproc_parse', 'scripts/xmlproc_val']
)
Изменено в версии 3.1: Все скрипты также будут добавлены в файл MANIFEST, если не предоставлен
шаблон. См. Указание файлов для распространения.
2.6. Установка данных пакета¶Installing Package Data
Часто в пакет нужно установить дополнительные файлы. Эти файлы – обычно данные, тесно связанные с реализацией пакета, или текстовые файлы с документацией, которая может быть полезна программистам, использующим пакет. Такие файлы называются данными пакета.
Данные пакета можно добавить в пакеты с помощью ключевого аргумента
package_data функции setup(). Значением должно быть отображение
имени пакета на список относительных путей, которые нужно скопировать в
пакет. Пути интерпретируются относительно каталога, содержащего
пакет (при необходимости используется информация из отображения package_dir);
то есть ожидается, что файлы являются частью пакета в исходных
каталогах. Они также могут содержать glob-шаблоны.
Имена путей могут содержать части каталогов; все необходимые каталоги будут созданы при установке.
Например, если пакет должен содержать подкаталог с несколькими файлами данных, файлы можно расположить в дереве исходников следующим образом:
setup.py
src/
mypkg/
__init__.py
module.py
data/
tables.dat
spoons.dat
forks.dat
Соответствующий вызов setup() может быть таким:
setup(...,
packages=['mypkg'],
package_dir={'mypkg': 'src/mypkg'},
package_data={'mypkg': ['data/*.dat']},
)
Изменено в версии 3.1: Все файлы, соответствующие package_data, будут добавлены в файл MANIFEST,
если не предоставлен шаблон. См. Указание файлов для распространения.
2.7. Установка дополнительных файлов¶Installing Additional Files
Опция data_files может использоваться для указания дополнительных файлов, необходимых
для распространяемого модуля: файлов конфигурации, каталогов сообщений, файлов данных,
всего, что не подходит под предыдущие категории.
data_files задаёт последовательность пар (каталог, файлы) следующим
образом:
setup(...,
data_files=[('bitmaps', ['bm/b1.gif', 'bm/b2.gif']),
('config', ['cfg/data.cfg'])],
)
Каждая пара (каталог, файлы) в последовательности указывает каталог установки и файлы, которые следует туда установить.
Каждое имя файла в files интерпретируется относительно скрипта setup.py
в корне исходного дистрибутива пакета. Обратите внимание: можно
указать каталог, куда будут установлены файлы данных, но нельзя
переименовать сами файлы данных.
Параметр directory должен быть относительным путём. Он интерпретируется относительно
префикса установки (sys.prefix Python для системных установок;
site.USER_BASE для пользовательских установок). Distutils позволяет directory быть
абсолютным путём установки, но это не рекомендуется, так как несовместимо
с форматом упаковки wheel. Информация о каталогах из files не используется
для определения конечного расположения устанавливаемого файла; используется только
имя файла.
Можно указать опцию data_files как простую последовательность файлов
без указания целевого каталога, но это не рекомендуется, и команда
install выведет предупреждение. Чтобы установить файлы данных
непосредственно в целевой каталог, в качестве каталога нужно задать пустую строку.
Изменено в версии 3.1: Все файлы, соответствующие data_files, будут добавлены в файл MANIFEST,
если не предоставлен шаблон. См. Указание файлов для распространения.
2.8. Дополнительные метаданные¶Additional meta-data
Скрипт setup может включать дополнительные метаданные помимо имени и версии. Эта информация включает:
Метаданные |
Описание |
Значение |
Примечания |
|---|---|---|---|
|
имя пакета |
короткая строка |
(1) |
|
версия этого релиза |
короткая строка |
(1)(2) |
|
имя автора пакета |
короткая строка |
(3) |
|
адрес электронной почты автора пакета |
адрес электронной почты |
(3) |
|
имя сопровождающего пакета |
короткая строка |
(3) |
|
адрес электронной почты сопровождающего пакет |
адрес электронной почты |
(3) |
|
домашняя страница пакета |
URL |
(1) |
|
краткое описание пакета |
короткая строка |
|
|
подробное описание пакета |
длинная строка |
(4) |
|
расположение, откуда можно загрузить пакет |
URL |
|
|
список классификаторов |
список строк |
(6)(7) |
|
список платформ |
список строк |
(6)(8) |
|
список ключевых слов |
список строк |
(6)(8) |
|
лицензия для пакета |
короткая строка |
(5) |
Примечания:
Эти поля обязательны.
Рекомендуется, чтобы версии имели формат major.minor[.patch[.sub]].
Должен быть указан либо автор, либо сопровождающий. Если указан сопровождающий, distutils указывает его как автора в
PKG-INFO.Поле
long_descriptionиспользуется PyPI при публикации пакета для построения его страницы проекта.Поле
license– это текст, указывающий лицензию, покрывающую пакет, если лицензия не выбрана из классификаторов Trove «License». См. полеClassifier. Обратите внимание, что существует опция распространенияlicence, которая устарела, но всё ещё является псевдонимом дляlicense.Это поле должно быть списком.
Допустимые классификаторы перечислены на PyPI.
Для сохранения обратной совместимости это поле также принимает строку. Если передана строка, разделённая запятыми,
'foo, bar', она будет преобразована в['foo', 'bar'], в противном случае будет преобразована в список из одной строки.
- «короткая строка»
Одна строка текста, не более 200 символов.
- «длинная строка»
Несколько строк обычного текста в формате reStructuredText (см. https://docutils.sourceforge.io/).
- «список строк»
См. ниже.
Кодирование информации о версии – само по себе искусство. Пакеты Python обычно придерживаются формата версии major.minor[.patch][sub]. Основной номер (major) равен 0 для начальных экспериментальных выпусков ПО. Он увеличивается для выпусков, представляющих собой важные вехи в пакете. Дополнительный номер (minor) увеличивается, когда в пакет добавляются важные новые функции. Номер патча увеличивается при выпусках с исправлением ошибок. Иногда дополнительная информация о версии в конце используется для обозначения подвыпусков. Это «a1,a2,…,aN» (для альфа-версий, где функциональность и API могут меняться), «b1,b2,…,bN» (для бета-версий, которые только исправляют ошибки) и «pr1,pr2,…,prN» (для финального тестирования перед релизом). Некоторые примеры:
- 0.1.0
первый экспериментальный выпуск пакета
- 1.0.1a2
второй альфа-выпуск первой патч-версии 1.0
classifiers должен быть указан в виде списка:
setup(...,
classifiers=[
'Development Status :: 4 - Beta',
'Environment :: Console',
'Environment :: Web Environment',
'Intended Audience :: End Users/Desktop',
'Intended Audience :: Developers',
'Intended Audience :: System Administrators',
'License :: OSI Approved :: Python Software Foundation License',
'Operating System :: MacOS :: MacOS X',
'Operating System :: Microsoft :: Windows',
'Operating System :: POSIX',
'Programming Language :: Python',
'Topic :: Communications :: Email',
'Topic :: Office/Business',
'Topic :: Software Development :: Bug Tracking',
],
)
Изменено в версии 3.7: setup теперь предупреждает, если поля classifiers, keywords или platforms не указаны в виде списка или строки.
2.9. Отладка скрипта установки¶Debugging the setup script
Иногда что-то идет не так, и скрипт установки делает не то, что хочет разработчик.
Distutils перехватывает любые исключения при запуске скрипта установки и выводит простое сообщение об ошибке перед завершением скрипта. Причина такого поведения – не запутывать администраторов, которые мало знают о Python и пытаются установить пакет. Если они получат длинную трассировку из глубин Distutils, они могут подумать, что пакет или установка Python сломаны, потому что не прочитают все до конца и не увидят, что проблема в правах доступа.
С другой стороны, это не помогает разработчику найти причину сбоя. Для этой цели переменная окружения DISTUTILS_DEBUG может быть установлена в любое значение, кроме пустой строки, и distutils теперь будет выводить подробную информацию о том, что он делает, выводить полную трассировку при возникновении исключения и выводить всю командную строку при сбое внешней программы (например, компилятора C).