Can I exclude files in sdist when using build frontend with setuptools backend?

I am using the “build” package as a build frontend, with setuptools as the build backend. Before that change (when still using setup.py), I was used to specifying file inclusions and exclusions to the sdist archive via a MANIFEST.in file. That does not work anymore, and the sdist seems to contain all files in the source repo. I’d like to reduce that to only the files that are needed to build and test from source.

Note that Controlling files in the distribution - setuptools 75.8.2.post20250228 documentation still describes that MANIFEST.in is obeyed by the setuptools backend.

Is there a way to specify which files to exclude from an sdist archive when using the build frontend with the setuptools backend?

As far as I know, this should work as before. Maybe you found a bug.

It’d probably be best for anyone to be able to help you if you could provide here a full, standalone, minimal reproducible example.

Thanks for the response. With that confirmation, let me try further.
In a stripped down version of a package, I could not reproduce it. I’m now trying to add stuff from my original package until it reproduces. I’ll post an update.

It started reproducing when I added dynamic version retrieval using setuptools-scm.

pyproject.toml:

# pyproject.toml file that tests exclusion of files using MANIFEST.in

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

[tool.setuptools]
platforms = ["any"]
zip-safe = true
script-files = ["build.sh"]

[tool.setuptools.packages.find]
include = ["test_manifest", "test_manifest.*"]

[project]
name = "test-manifest"
description = "Test exclusion of files using MANIFEST.in"
requires-python = ">=3.8"
dynamic = ["version", "dependencies"]

[tool.setuptools.dynamic]
dependencies = {file = ["requirements.txt"]}

[tool.setuptools_scm]
version_file = "test_manifest/_version_scm.py"

MANIFEST.in file:

include pyproject.toml
include dev-requirements.txt
include MANIFEST.in
recursive-include test_manifest *

Repo tree:

├── MANIFEST.in
├── build.sh
├── dev-requirements.txt
├── dist
│   └── test_manifest-1.0.1.dev0+ge4fd9be.d20250302.tar.gz
├── excluded
│   └── file1.py
├── excluded_file1.txt
├── pyproject.toml
├── requirements.txt
├── test_manifest
│   ├── __init__.py
│   ├── _version_scm.py
│   └── main.py
└── test_manifest.egg-info
    ├── PKG-INFO
    ├── SOURCES.txt
    ├── dependency_links.txt
    ├── requires.txt
    ├── top_level.txt
    └── zip-safe

Build commands (in build.sh):

rm -rf test_manifest.egg-info
rm -rf build
rm dist/*
python -m build --sdist --outdir dist .

Content of created source archive:

test_manifest-1.0.1.dev0+ge4fd9be.d20250302/MANIFEST.in
test_manifest-1.0.1.dev0+ge4fd9be.d20250302/PKG-INFO
test_manifest-1.0.1.dev0+ge4fd9be.d20250302/build.sh
test_manifest-1.0.1.dev0+ge4fd9be.d20250302/dev-requirements.txt
test_manifest-1.0.1.dev0+ge4fd9be.d20250302/excluded/file1.py
test_manifest-1.0.1.dev0+ge4fd9be.d20250302/excluded_file1.txt
test_manifest-1.0.1.dev0+ge4fd9be.d20250302/pyproject.toml
test_manifest-1.0.1.dev0+ge4fd9be.d20250302/requirements.txt
test_manifest-1.0.1.dev0+ge4fd9be.d20250302/setup.cfg
test_manifest-1.0.1.dev0+ge4fd9be.d20250302/test_manifest/__init__.py
test_manifest-1.0.1.dev0+ge4fd9be.d20250302/test_manifest/_version_scm.py
test_manifest-1.0.1.dev0+ge4fd9be.d20250302/test_manifest/main.py
test_manifest-1.0.1.dev0+ge4fd9be.d20250302/test_manifest.egg-info/PKG-INFO
test_manifest-1.0.1.dev0+ge4fd9be.d20250302/test_manifest.egg-info/SOURCES.txt
test_manifest-1.0.1.dev0+ge4fd9be.d20250302/test_manifest.egg-info/dependency_links.txt
test_manifest-1.0.1.dev0+ge4fd9be.d20250302/test_manifest.egg-info/requires.txt
test_manifest-1.0.1.dev0+ge4fd9be.d20250302/test_manifest.egg-info/top_level.txt
test_manifest-1.0.1.dev0+ge4fd9be.d20250302/test_manifest.egg-info/zip-safe

The unexpected files in the source archive are:

excluded/file1.py
excluded_file1.txt

The setup.cfg file has been auto-created, but that is not an issue, I guess.

When removing the dynamic versioning from the pyproject.toml file, these two files are not in the source archive.

I guess this sounds like a bug in setuptools or setuptools-scm?

Keep in mind that modern versions of SetupTools change their file inclusion behaviors based on the presence or absence of a pyproject.toml file, so if you’re adding one you may need to override its newer automated module scanning features.

fungi, that’s what I try to to by providing a MANIFEST.in file. My understanding is that when present, the default behavior is completely replaced with what the MANIFEST.in file specifies. And that is what I indeed see when dynamic versioning is not used.

Just to be complete, when not using dynamic versioning, the repo tree looks like above, but the pyproject.toml file is:

# pyproject.toml file that tests exclusion of files using MANIFEST.in

[build-system]
requires = [
    "setuptools>=70.0.0",
#    "setuptools-scm>=8.1.0",
    "wheel>=0.38.1",
]
build-backend = "setuptools.build_meta"

[tool.setuptools]
platforms = ["any"]
zip-safe = true
script-files = ["build.sh"]

[tool.setuptools.packages.find]
include = ["test_manifest", "test_manifest.*"]

[project]
name = "test-manifest"
version = "1.0.0"
description = "Test exclusion of files using MANIFEST.in"
requires-python = ">=3.8"

# dynamic = ["version", "dependencies"]
dynamic = ["dependencies"]

[tool.setuptools.dynamic]
dependencies = {file = ["requirements.txt"]}

# [tool.setuptools_scm]
# version_file = "test_manifest/_version_scm.py"

and the content of the source archive follows what the MANIFEST.in file specifies:

test_manifest-1.0.0/MANIFEST.in
test_manifest-1.0.0/PKG-INFO
test_manifest-1.0.0/build.sh
test_manifest-1.0.0/dev-requirements.txt
test_manifest-1.0.0/pyproject.toml
test_manifest-1.0.0/requirements.txt
test_manifest-1.0.0/setup.cfg
test_manifest-1.0.0/test_manifest/__init__.py
test_manifest-1.0.0/test_manifest/main.py
test_manifest-1.0.0/test_manifest.egg-info/PKG-INFO
test_manifest-1.0.0/test_manifest.egg-info/SOURCES.txt
test_manifest-1.0.0/test_manifest.egg-info/dependency_links.txt
test_manifest-1.0.0/test_manifest.egg-info/top_level.txt
test_manifest-1.0.0/test_manifest.egg-info/zip-safe

In both cases, I used the following package versions on Python 3.12:

setuptools                    75.8.2
setuptools-scm                8.2.0
wheel                         0.45.1

I have created Use of dynamic versioning with setuptools-scm causes MANIFEST.in to be ignored · Issue #1115 · pypa/setuptools-scm · GitHub for this. The issue has a zip file attached with all the files, which makes it easier to reproduce the issue compared to the descriptions in this thread.
Let’s see how this develops.

There is a solution to the issue: The files need to be explicitly excluded in MANIFEST.in. See the comments on the issue I opened.

A long time ago I wrote this:

https://sinoroc.gitlab.io/kb/python/package_data.html

Many things have changed in setuptools since then, but maybe some of it still applies today. On the other hand, I have strictly no idea what happens when setuptools-scm is involved.