2. Написание сценария настройки

Примечание

Этот документ сохраняется исключительно до тех пор, пока документация setuptools на сайте https://setuptools.readthedocs.io/en/latest/setuptools.html не будет независимо охватывать всю соответствующую информацию, включенную сюда в настоящее время.

Сценарий установки является центром всей деятельности по созданию, распространению и установке модулей с помощью Distutils. Основная цель скрипта установки - описать дистрибутив вашего модуля в Distutils, чтобы различные команды, работающие с вашими модулями, выполняли правильные действия. Как мы видели в разделе Простой пример выше, сценарий установки состоит в основном из вызова команды 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'],
     )

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

Обратите внимание, что любые имена путей (файлы или каталоги), указанные в сценарии установки, должны быть записаны с использованием конвенции 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. Перечисление целых пакетов

Опция 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).

2.2. Перечисление отдельных модулей

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

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

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

2.3. Описание модулей расширения

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

Все это делается через другой аргумент ключевого слова в 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, что объясняется в следующих разделах.

2.3.1. Имена и пакеты расширений

Первым аргументом конструктора 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 является список исходных файлов. Поскольку 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. Параметры препроцессора

Три дополнительных аргумента к Extension помогут, если вам нужно указать каталоги include для поиска или макросы препроцессора для определения/неопределения: 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). Поскольку каталог include 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 в вашем исходном тексте Си: в большинстве компиляторов это устанавливает FOO в строку 1). undef_macros - это просто список макросов для неопределения.

Например:

Extension(...,
          define_macros=[('NDEBUG', '1'),
                         ('HAVE_STRFTIME', None)],
          undef_macros=['HAVE_FOO', 'HAVE_BAR'])

это эквивалентно тому, чтобы в начале каждого исходного файла на языке C было написано:

#define NDEBUG 1
#define HAVE_STRFTIME
#undef HAVE_FOO
#undef HAVE_BAR

2.3.4. Варианты библиотек

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

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

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

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

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

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

2.3.5. Другие варианты

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

Опция optional представляет собой булево значение; если оно истинно, сбой сборки расширения не прервет процесс сборки, а просто не установит сбойное расширение.

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

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

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

Опция depends представляет собой список файлов, от которых зависит расширение (например, заголовочные файлы). Команда сборки вызовет компилятор исходных текстов для пересборки расширения, если какой-либо из этих файлов был изменен с момента предыдущей сборки.

2.4. Отношения между дистрибутивами и пакетами

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

  1. Для этого могут потребоваться пакеты или модули.

  2. Он может предоставлять пакеты или модули.

  3. Это может привести к устареванию пакетов или модулей.

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

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

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

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

<    >    ==
<=   >=   !=

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

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

Требуется экспрессия

Пояснение

==1.0

Совместима только версия 1.0

>1.0, !=1.5.1, <2.0

Любая версия после 1.0 и до 2.0 является совместимой, за исключением 1.5.1

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

Некоторые примеры:

Обеспечивает экспрессию

Пояснение

mypkg

Предоставьте mypkg, используя версию дистрибутива

mypkg (1.1)

Предоставьте mypkg версию 1.1, независимо от версии дистрибутива

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

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

2.5. Установка сценариев

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

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

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

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

Изменено в версии 3.1: Все скрипты также будут добавлены в файл MANIFEST, если шаблон не предоставлен. См. Указание файлов для распространения.

2.6. Установка данных пакета

Часто в пакет необходимо установить дополнительные файлы. Эти файлы часто представляют собой данные, тесно связанные с реализацией пакета, или текстовые файлы, содержащие документацию, которая может быть интересна программистам, использующим пакет. Эти файлы называются 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. Установка дополнительных файлов

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

data_files задает последовательность пар (каталог, файлы) следующим образом:

setup(...,
      data_files=[('bitmaps', ['bm/b1.gif', 'bm/b2.gif']),
                  ('config', ['cfg/data.cfg'])],
     )

Каждая пара (каталог, файлы) в последовательности задает каталог установки и файлы для установки в нем.

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

Каталог directory должен быть относительным путем. Он интерпретируется относительно префикса установки (Python’s sys.prefix для системных установок; site.USER_BASE для пользовательских установок). Distutils позволяет directory быть абсолютным путем установки, но это не рекомендуется, поскольку несовместимо с форматом упаковки wheel. Информация о каталоге из files не используется для определения конечного местоположения устанавливаемого файла; используется только имя файла.

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

Изменено в версии 3.1: Все файлы, соответствующие data_files, будут добавлены в файл MANIFEST, если шаблон не предоставлен. См. Указание файлов для распространения.

2.8. Дополнительные мета-данные

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

Мета-данные

Описание

Значение

Примечания

name

название пакета

короткая строка

(1)

version

версия данного релиза

короткая строка

(1)(2)

author

имя автора пакета

короткая строка

(3)

author_email

адрес электронной почты автора пакета

адрес электронной почты

(3)

maintainer

имя сопровождающего пакета

короткая строка

(3)

maintainer_email

адрес электронной почты сопровождающего пакета

адрес электронной почты

(3)

url

домашняя страница для пакета

URL

(1)

description

краткое, обобщенное описание пакета

короткая строка

long_description

более подробное описание пакета

длинная строка

(4)

download_url

место, где можно загрузить пакет

URL

classifiers

список классификаторов

список строк

(6)(7)

platforms

список платформ

список строк

(6)(8)

keywords

список ключевых слов

список строк

(6)(8)

license

лицензия на пакет

короткая строка

(5)

Примечания:

  1. Эти поля обязательны для заполнения.

  2. Рекомендуется, чтобы версии имели форму major.minor[.patch[.sub]].

  3. Должен быть указан либо автор, либо сопровождающий. Если указан сопровождающий, distutils перечисляет его как автора в PKG-INFO.

  4. Поле long_description используется PyPI, когда вы публикуете пакет, для создания его страницы проекта.

  5. Поле license представляет собой текст, указывающий на лицензию, покрывающую пакет, если лицензия не является выбором из классификаторов Trove «Лицензия». См. поле Classifier. Обратите внимание, что существует опция распространения licence, которая устарела, но все еще действует как псевдоним для license.

  6. Это поле должно быть списком.

  7. Действительные классификаторы перечислены в PyPI.

  8. Для сохранения обратной совместимости это поле также принимает строку. Если вы передадите разделенную запятыми строку 'foo, bar', она будет преобразована в ['foo', 'bar'], В противном случае она будет преобразована в список из одной строки.

„короткая строка“

Одна строка текста, не более 200 знаков.

„длинная строка“

Несколько строк обычного текста в формате reStructuredText (см. http://docutils.sourceforge.net/).

„список строк“

См. ниже.

Кодирование информации о версии - это целое искусство. Пакеты 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. Отладка сценария установки

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

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

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

Вернуться на верх