Документация Python неофициальный перевод
Содержание страницы

Написание скрипта установкиWriting the Setup Script

The setup script is the centre of all activity in building, distributing, and installing modules using the Distutils. The main purpose of the setup script is to describe your module distribution to the Distutils, so that the various commands that operate on your modules do the right thing. As we saw in section A Simple Example above, the setup script consists mainly of a call to setup(), and most information supplied to the Distutils by the module developer is supplied as keyword arguments to 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='http://www.python.org/sigs/distutils-sig/',
      packages=['distutils', 'distutils.command'],
     )

Есть только два отличия от тривиального однофайлового дистрибутива, представленного в разделе A Simple Example: больше метаданных и указание чистых модулей Python по пакетам, а не по модулям. Это важно, поскольку Distutils состоят из нескольких десятков модулей, разбитых (пока) на два пакета; явный список всех модулей было бы утомительно составлять и сложно поддерживать. Дополнительную информацию о метаданных см. в разделе Additional meta-data.

Обратите внимание: любые пути (к файлам или каталогам), указанные в сценарии установки, должны записываться в стиле Unix, то есть с разделителем-слешем. Distutils позаботится о преобразовании этого платформонезависимого представления в то, что подходит для вашей текущей платформы, перед фактическим использованием пути. Это делает ваш сценарий установки переносимым между операционными системами, что, конечно, является одной из главных целей Distutils. В этом духе все пути в данном документе записываются через слеш.

This, of course, only applies to pathnames given to Distutils functions. If you, for example, use standard Python functions such as glob.glob() or os.listdir() to specify files, you should be careful to write portable code instead of hardcoding path separators:

glob.glob(os.path.join('mydir', 'subdir', '*.html'))
os.listdir(os.path.join('mydir', 'subdir'))

Перечисление целых пакетов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 неявно применяется ко всем пакетам, находящимся внутри package, поэтому случай с foo.bar обрабатывается автоматически. В этом примере указание packages = ['foo', 'foo.bar'] сообщает Distutils искать lib/__init__.py и lib/bar/__init__.py. (Следует помнить, что хотя package_dir применяется рекурсивно, необходимо явно перечислить все пакеты в packages: Distutils не будет рекурсивно обходить дерево исходников в поисках любого каталога с файлом __init__.py.)

Перечисление отдельных модулейListing individual modules

Для небольшого дистрибутива модулей может быть удобнее перечислить все модули, а не пакеты – особенно случай одного модуля, который находится в «корневом пакете» (то есть вообще без пакета). Этот простейший случай был показан в разделе Простой пример; вот немного более сложный пример:

py_modules = ['mod1', 'pkg.mod2']

Здесь описаны два модуля: один в «корневом» пакете, другой в пакете pkg. Опять же, стандартное соответствие пакетов и каталогов подразумевает, что эти модули находятся в mod1.py и pkg/mod2.py, а также существует pkg/__init__.py. И снова соответствие пакетов и каталогов можно изменить с помощью параметра package_dir.

Описание модулей расширенияDescribing extension modules

Так же как написание модулей расширения Python немного сложнее, чем написание чистых модулей Python, их описание для Distutils тоже немного сложнее. В отличие от чистых модулей, недостаточно просто перечислить модули или пакеты и ожидать, что Distutils сам найдёт нужные файлы; необходимо указать имя расширения, исходные файлы и любые требования к компиляции/компоновке (каталоги включения, библиотеки для компоновки и т.д.).

Всё это делается через ещё один именованный аргумент setup(), параметр ext_modules. ext_modules – это просто список экземпляров Extension, каждый из которых описывает один модуль-расширение. Предположим, ваш дистрибутив содержит одно расширение с именем foo, реализованное в файле foo.c. Если компилятору/компоновщику не нужны дополнительные указания, описание этого расширения будет совсем простым:

Extension('foo', ['foo.c'])

Класс Extension можно импортировать из distutils.core вместе с setup(). Таким образом, скрипт установки для дистрибутива, содержащего только это одно расширение и ничего больше, может выглядеть так:

from distutils.core import setup, Extension
setup(name='foo',
      version='1.0',
      ext_modules=[Extension('foo', ['foo.c'])],
      )

Класс Extension (фактически, лежащая в его основе механика сборки расширений, реализованная командой build_ext) поддерживает большую гибкость при описании расширений Python, что объясняется в следующих разделах.

Имена расширений и пакеты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.

Исходные файлы расширений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 грубовата и в основном не тестировалась! **

Несмотря на это предупреждение, опции для 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) и подключатся к исполняемому файлу.

Опции препроцессора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, можно воспользоваться тем, что заголовочные файлы устанавливаются единообразно командой install_header из Distutils. Например, заголовочные файлы Numerical Python устанавливаются (в стандартной установке Unix) в /usr/local/include/python1.5/Numerical. (Точное расположение зависит от вашей платформы и установки Python.) Поскольку каталог включения Python – в данном случае /usr/local/include/python1.5 – всегда включён в путь поиска при сборке расширений Python, лучше всего писать код на C так:

#include <Numerical/arrayobject.h>

Если же необходимо поместить каталог include 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

Опции библиотекLibrary options

Также можно указать библиотеки, с которыми нужно линковать расширение, и каталоги для поиска этих библиотек. Опция libraries – это список библиотек для линковки, library_dirs – список каталогов для поиска библиотек на этапе линковки, а runtime_library_dirs – список каталогов для поиска разделяемых (динамически загружаемых) библиотек во время выполнения.

Например, если требуется скомпоновать расширение с библиотеками, которые заведомо находятся в стандартном пути поиска библиотек в целевых системах,

Extension(...,
          libraries=['_gdbm', 'readline'])

Если нужно линковаться с библиотеками в нестандартном расположении, придётся указать это расположение в library_dirs:

Extension(...,
          library_dirs=['/usr/X11R6/lib'],
          libraries=['X11', 'Xt'])

(Повторим: подобные непереносимые конструкции следует избегать, если планируется распространять свой код.)

** Следует упомянуть библиотеки clib здесь или где-нибудь ещё! **

Другие вариантыOther options

Существуют и другие опции, которые можно использовать для обработки особых случаев.

Параметр extra_objects – это список объектных файлов, передаваемых компоновщику. Эти файлы не должны иметь расширений, так как используется расширение по умолчанию для компилятора.

Параметры extra_compile_args и extra_link_args можно использовать для указания дополнительных параметров командной строки для компилятора и компоновщика соответственно.

Параметр export_symbols полезен только в Windows. Он может содержать список символов (функций или переменных), которые нужно экспортировать. Этот параметр не требуется при сборке скомпилированных расширений: Distutils автоматически добавит initmodule в список экспортируемых символов.

Отношения между дистрибутивами и пакетамиRelationships between Distributions and Packages

Дистрибутив может относиться к пакетам тремя определёнными способами:

  1. Он может требовать пакеты или модули.
  2. Он может предоставлять пакеты или модули.
  3. Он может заменять (делать устаревшими) пакеты или модули.

Эти зависимости можно указать с помощью именованных аргументов функции distutils.core.setup().

Зависимости от других модулей и пакетов Python можно указать, передав requires в качестве именованного аргумента функции setup(). Значением должен быть список строк. Каждая строка задает требуемый пакет и, опционально, подходящие версии.

Чтобы указать, что требуется любая версия модуля или пакета, строка должна состоять только из имени модуля или пакета. Примеры: 'mymodule' и 'xml.parsers.expat'.

Если требуются конкретные версии, можно указать последовательность квалификаторов в скобках. Каждый квалификатор может состоять из оператора сравнения и номера версии. Допустимые операторы сравнения:

<    >    ==
<=   >=   !=

Их можно комбинировать, используя несколько квалификаторов, разделённых запятыми (и необязательными пробелами). В этом случае все квалификаторы должны совпадать; используется логическое И для объединения результатов.

Рассмотрим несколько примеров:

Выражение Requires Пояснение
==1.0 Совместима только версия 1.0
>1.0, !=1.5.1, <2.0 Любая версия после 1.0 и до 2.0 совместима, за исключением 1.5.1

Теперь, когда мы можем указывать зависимости, нужно также уметь указывать, что мы предоставляем, чтобы другие дистрибутивы могли на это ссылаться. Для этого используется именованный аргумент provides функции setup(). Значением этого аргумента является список строк, каждая из которых задает имя модуля или пакета Python и, опционально, версию. Если версия не указана, считается, что она соответствует версии дистрибутива.

Несколько примеров:

Выражение Provides Пояснение
mypkg Предоставляет mypkg, используя версию дистрибутива
mypkg (1.1) Предоставляет mypkg версии 1.1, независимо от версии дистрибутива

Пакет может объявлять, что он заменяет (делает устаревшими) другие пакеты, с помощью именованного аргумента obsoletes. Его значение аналогично значению аргумента requires: список строк, задающих спецификаторы модуля или пакета. Каждый спецификатор состоит из имени модуля или пакета, за которым необязательно следуют один или несколько квалификаторов версии. Версионные квалификаторы указываются в скобках после имени модуля или пакета.

Версии, указанные квалификаторами, считаются заменяемыми (устаревшими) данным дистрибутивом. Если квалификаторы не указаны, считаются устаревшими все версии указанного модуля или пакета.

Установка скриптовInstalling Scripts

До сих пор мы имели дело с чистыми и нечистыми модулями Python, которые обычно не запускаются сами по себе, а импортируются скриптами.

Скрипты – это файлы, содержащие исходный код Python, предназначенные для запуска из командной строки. Скрипты не требуют от Distutils ничего очень сложного. Единственная интересная особенность: если первая строка скрипта начинается с #! и содержит слово «python», Distutils скорректирует первую строку, чтобы она указывала на текущее расположение интерпретатора. По умолчанию она заменяется на путь к текущему интерпретатору. Параметр --executable (или -e) позволяет явно переопределить путь к интерпретатору.

Параметр scripts – это просто список файлов, которые будут обработаны таким образом. Из сценария установки PyXML:

setup(...,
      scripts=['scripts/xmlproc_parse', 'scripts/xmlproc_val']
      )

Установка данных пакета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']},
      )

Установка дополнительных файловInstalling Additional Files

Параметр data_files можно использовать для указания дополнительных файлов, необходимых дистрибутиву модуля: конфигурационные файлы, файлы сообщений, файлы данных – всё, что не подходит под предыдущие категории.

Параметр data_files задаёт последовательность пар (directory, files) следующим образом:

setup(...,
      data_files=[('bitmaps', ['bm/b1.gif', 'bm/b2.gif']),
                  ('config', ['cfg/data.cfg']),
                  ('/etc/init.d', ['init-script'])]
     )

Обратите внимание: можно указать имена каталогов, в которые будут установлены файлы данных, но сами файлы переименовать нельзя.

Каждая пара (directory, files) в последовательности задает каталог установки и файлы для установки в нем. Если directory – относительный путь, он интерпретируется относительно префикса установки (sys.prefix для чисто Python-пакетов, sys.exec_prefix для пакетов, содержащих расширения). Каждый файл в files интерпретируется относительно скрипта setup.py из корня пакета исходного кода. Информация о каталоге из files не используется для определения конечного расположения устанавливаемого файла; используется только имя файла.

Параметр data_files можно задать как простую последовательность файлов без указания целевого каталога, но это не рекомендуется; в этом случае команда install выведет предупреждение. Чтобы установить файлы данных непосредственно в целевой каталог, в качестве каталога следует указать пустую строку.

Дополнительные метаданныеAdditional meta-data

Скрипт setup может включать дополнительные метаданные помимо имени и версии. Эта информация включает:

Метаданные Описание Значение Примечания
name имя пакета короткая строка (1)
version версия этого релиза короткая строка (1)(2)
author имя автора пакета короткая строка (3)
author_email адрес электронной почты автора пакета адрес электронной почты (3)
maintainer имя сопровождающего пакета короткая строка (3)
maintainer_email адрес электронной почты сопровождающего пакет адрес электронной почты (3)
url домашняя страница пакета URL (1)
description краткое описание пакета короткая строка  
long_description подробное описание пакета длинная строка  
download_url расположение, откуда можно загрузить пакет URL (4)
classifiers список классификаторов список строк (4)
платформы список платформ список строк  

Примечания:

  1. Эти поля обязательны.
  2. Рекомендуется, чтобы версии имели формат major.minor[.patch[.sub]].
  3. Должен быть указан либо автор, либо сопровождающий.
  4. Эти поля не следует использовать, если пакет должен быть совместим с версиями Python до 2.2.3 или 2.3. Список доступен на сайте PyPI.
«короткая строка»
Одна строка текста, не более 200 символов.
«длинная строка»
Многострочный простой текст в формате reStructuredText (см. http://docutils.sf.net/).
«список строк»
См. ниже.

Указание версии – это искусство само по себе. Пакеты Python обычно следуют формату версии major.minor[.patch][sub]. Основной номер равен 0 для начальных экспериментальных выпусков ПО. Он увеличивается для выпусков, представляющих важные вехи в пакете. Второстепенный номер увеличивается, когда добавляются важные новые функции. Номер патча увеличивается при выпусках с исправлениями ошибок. Дополнительная завершающая информация о версии иногда используется для обозначения подвыпусков. Это «a1,a2,...,aN» (для альфа-выпусков, где функциональность и API могут измениться), «b1,b2,...,bN» (для бета-выпусков, которые только исправляют ошибки) и «pr1,pr2,...,prN» (для финального предрелизного тестирования). Некоторые примеры:

0.1.0
первый экспериментальный выпуск пакета
1.0.1a2
второй альфа-выпуск первой патч-версии 1.0

Классификаторы указываются в виде списка Python:

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',
          ],
      )

Если требуется включить классификаторы в файл setup.py и при этом сохранить обратную совместимость с версиями Python до 2.2.3, то можно добавить следующий фрагмент кода в setup.py перед вызовом setup().

# исправить distutils, если он не справляется с ключевыми словами "classifiers" или
# ключевые слова "download_url"
from sys import version
if version < '2.2.3':
    from distutils.dist import DistributionMetadata
    DistributionMetadata.classifiers = None
    DistributionMetadata.download_url = None

Отладка сценария установкиDebugging the setup script

Иногда что-то идет не так, и скрипт установки делает не то, что хочет разработчик.

Distutils перехватывает любые исключения при запуске скрипта установки и выводит простое сообщение об ошибке перед завершением скрипта. Причина такого поведения – не запутывать администраторов, которые мало знают о Python и пытаются установить пакет. Если они получат длинную трассировку из глубин Distutils, они могут подумать, что пакет или установка Python сломаны, потому что не прочитают все до конца и не увидят, что проблема в правах доступа.

С другой стороны, это не помогает разработчику найти причину сбоя. Для этого можно установить переменную окружения DISTUTILS_DEBUG в любое непустое значение, и distutils будет выводить подробную информацию о том, что делает, а также полный стек вызовов в случае возникновения исключения.