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 будет содержать ровно то, что нужно, — и ничего лишнего.