2025, Dec 06 12:01

Как убрать .gitignore и .github из sdist: setuptools-scm и MANIFEST.in

Как собрать чистый sdist в Python: настроить setuptools и setuptools-scm, использовать MANIFEST.in с exclude и prune, исключить .gitignore и каталог .github.

При сборке пакета Python с помощью setuptools и setuptools-scm нетрудно получить в исходном дистрибутиве больше файлов, чем вы рассчитывали. Часто туда попадают артефакты, предназначенные только для репозитория, например .gitignore и каталог .github. Если вы хотите получить аккуратный sdist, где остаются лишь код пакета и необходимый минимум, есть точный способ прийти к этому без поломки сборки.

Как воспроизвести проблему

Возьмём небольшой проект, где сборка запускается командой python -m build. Структура репозитория проста: сам пакет, файлы документации и немного служебных материалов, связанных с системой контроля версий.

repo_root
├── .gitignore
├── .github
├── pyproject.toml 
├── README.rst
├── LICENSE
└── toolkit
    ├── __init__.py
    └── module.py

Конфигурация упаковки указывает использовать setuptools, setuptools-scm и wheel и просит setuptools обнаруживать пакеты по шаблону.

[build-system]
requires = [
    "setuptools >= 65.5.1",
    "setuptools-scm",
    "wheel"
]
build-backend = "setuptools.build_meta"

[tool.setuptools.package-data]
"*" = [
    "README.rst",
    "LICENSE"
]

[tool.setuptools.packages.find]
include = [
    "toolkit.*"
]

После сборки сгенерированный SOURCES.txt подтягивает .gitignore и каталог .github, и они оказываются в артефактах в dist.

.gitignore
LICENSE
README.rst
pyproject.toml
.github/dependabot.yml
.github/pull_request_template.md
.github/ISSUE_TEMPLATE/99_any.md
.github/workflows/testing.yml
toolkit.egg-info/PKG-INFO
toolkit.egg-info/SOURCES.txt
toolkit.egg-info/dependency_links.txt
toolkit.egg-info/requires.txt
toolkit.egg-info/top_level.txt
toolkit/__init__.py
toolkit/module.py

Почему так происходит

При включённом setuptools-scm по умолчанию добавляется каждый файл под управлением системы контроля версий. Именно это и происходит: все файлы, отслеживаемые VCS, считаются частью исходного дистрибутива, если вы явно не скажете обратное. Такое поведение — ожидаемое. Если нужен более узкий набор, полагаться на неявные фильтры нельзя: исключения нужно задать явно.

Есть и другая тонкость, которая иногда сбивает обнаружение пакетов. Если шаблон include выглядит как toolkit.* — он совпадает только с подпакетами и не захватывает верхнеуровневый пакет toolkit. Если кажется, что discovery «сломался» и базовый пакет не распознаётся, шаблон должен быть toolkit* вместо toolkit.*. Это отдельный вопрос от политики выбора файлов и не объясняет, почему .gitignore или .github попадают в ваш sdist.

Исправление: MANIFEST.in с exclude и prune

Когда вы должны использовать setuptools-scm, управлять тем, что не должно попадать в дистрибутив, можно через файл MANIFEST.in в корне репозитория. Применяйте exclude для отдельных файлов и prune — для целых каталогов или деревьев каталогов. Аргументы поддерживают глоб‑шаблоны, так что можно настроить как точечные, так и широкие исключения.

exclude .gitignore
prune .github

Поместите этот MANIFEST.in в корень репозитория. В шаблонах можно использовать как точные имена, как выше, так и паттерны вроде exclude foo/*.py[co]. Официальные директивы и параметры описаны на странице https://setuptools.pypa.io/en/latest/userguide/miscellaneous.html.

Как должен выглядеть результат

С MANIFEST.in sdist перестаёт упаковывать содержимое, нужное только самому репозиторию, и оставляет лишь то, что важно для упаковки. Имеет смысл оставить в архиве pyproject.toml: он сообщает сборочным инструментам, как собирать и устанавливать пакет; без него ваш sdist не будет устанавливаемым.

LICENSE
README.rst
pyproject.toml
toolkit.egg-info/PKG-INFO
toolkit.egg-info/SOURCES.txt
toolkit.egg-info/dependency_links.txt
toolkit.egg-info/requires.txt
toolkit.egg-info/top_level.txt
toolkit/__init__.py
toolkit/module.py

Замечание об упрощении конфигурации

Если на самом деле вам не нужен setuptools-scm, более минимальная конфигурация без него не будет «захватывать» в sdist все отслеживаемые файлы. В таком случае можно опустить и секции tool.setuptools.*, поскольку README и LICENSE включаются по умолчанию, а в архив попадёт pyproject.toml и не затянутся .github или другие случайные файлы из репозитория.

Почему это важно

Сдержанные и продуманные исходные дистрибутивы не отправляют пользователям CI‑воркфлоу, шаблоны и прочие метаданные репозитория. Это даёт более чистые артефакты и снижает путаницу у потребителей вашего пакета. Наличие pyproject.toml также гарантирует возможность установки в инструментах, которые опираются на него для организации сборки.

Выводы

Если в вашей цепочке инструментов есть setuptools-scm, исходите из того, что всё под управлением VCS будет включено, пока вы не исключите это явно. Разместите MANIFEST.in в корне репозитория, используйте exclude для одиночных файлов и prune для каталогов, а pyproject.toml оставляйте в архиве. Если обнаружение пакетов выглядит некорректно, проверьте, что шаблон include покрывает базовый пакет, например toolkit* вместо toolkit.*. С этими правками ваш sdist будет содержать ровно то, что нужно, — и ничего лишнего.