Skip to content

Build configurations#

All build configurations are stored under tool.pdm.build table.

Specify the package files#

The PDM backend supports conventional project layouts, eliminating the need for configuration in most projects. Additionally, you can customize which files to include or exclude using various configurations. Let's take a look at some examples.

The common package layout#

1
2
3
4
5
6
7
.
├── pyproject.toml
├── mypackage
│   ├── __init__.py
│   └── lib.py
└── tests
    └── test_lib.py

Build this project and you will get the distributions with the following contents:

1
2
3
4
5
pyproject.toml
PKG-INFO
mypackage/__init__.py
mypackage/lib.py
tests/test_lib.py
1
2
3
mypackage/__init__.py
mypackage/lib.py
mypackage-0.1.0.dist-info/...

Basically, a source distribution should contain all the files required to build or test the package. Typically, a wheel distribution should also be built from that. On the contrary, the files in a wheel distribution will be copied to the site-packages directory as-is. So you can notice the tests/ are include by a source distribution but not by a wheel.

When the wheel is installed, there will be the following files on your local disk:

1
2
3
.../site-packages/mypackage/__init__.py
.../site-packages/mypackage/lib.py
.../site-packages/mypackage-0.1.0.dist-info/...

So that you can directly do import mypackage in your code.

For a wheel, there are some special handlings for the console scripts and data files, read the details in Python Packaging User Guide.

More than one top packages

If there are more than one top packages in your project, they will be included in the distribution as well. A directory is considered as a top package if it contains an __init__.py file. All this can be achieved without any build configuration.

The module files layout#

If there is no package in your project but instead only module files, these files will be included in the distribution. The project layout can be like the following:

1
2
3
4
5
.
├── pyproject.toml
├── mymodule.py
└── tests
    └── test_mymodule.py

Include or exclude files#

By default, python files under the project root will be included only when there is no package found. You can change this by specifying the includes and excludes settings.

1
2
[tool.pdm.build]
includes = ["mypackage/", "script.py"]

Note that this is uncommon because it will place both mypackage/ and script.py under site-packages directory when installed. However, the build scripts are required by a source distribution to build the package, and you don't want them to be installed. In this case, use the source-includes setting instead:

1
2
[tool.pdm.build]
source-includes = ["scripts/", "tests/"]

The patterns in source-includes will be automatically excluded by the wheel distribution.

Similarly, the excludes config also accepts a list of relative paths or glob patterns, and the matched files will not be packaged in to the distribution.

Include files in a namespace package#

A PEP 420 namespace package is a package that is split across multiple directories on the filesystem. It is a directory without an __init__.py file. For example, this is a Python package with nested namespace packages:

1
2
3
4
5
6
basepackage/
  somenamespace/
    my_namespace_package/
      __init__.py
      ...
pyproject.toml

PDM-Backend doesn't collect the files in the namespace packages automatically. Instead, you have to specify it in the includes setting manually:

1
2
[tool.pdm.build]
includes = ["basepackage/"]

The src layout#

The src layout is also a common project layout which places all the python source files under a src directoy. PDM-backend also detects this automatically. Given the following project layout:

1
2
3
4
5
6
7
.
├── pyproject.toml
├── src
│   └── mypackage
│       ├── __init__.py
│       └── lib.py
└── tests

The built distribution will be:

1
2
3
4
pyproject.toml
src/mypackage/__init__.py
src/mypackage/lib.py
tests/test_lib.py
1
2
3
mypackage/__init__.py
mypackage/lib.py
mypackage-0.1.0.dist-info/...

You can see the files are remapped in the wheel distribution but keep the same in the source distribution.

PDM-backend enables the src layout when it finds a src/ directory. But you can also specify the directory name by package-dir configuration:

1
2
[tool.pdm.build]
package-dir = "mysrc"

The default value is src if src/ is found and . otherwise.

Priority of includes and excludes#

If a file is covered by both includes and excludes, the one with the more path parts and less wildcards in the pattern wins, otherwise excludes takes precedence if the length is the same.

For example, given the following configuration:

1
2
includes = ["src"]
excludes = ["**/*.json"]

src/foo/data.json will be excluded since the pattern in excludes has more path parts, however, if we change the configuration to:

1
2
includes = ["src", "src/foo/data.json"]
excludes = ["**/*.json"]

the same file will be included since it is covered by includes with a more specific path.

Default includes and excludes#

If neither includes and excludes is specified, the backend can determine them following the rules as below:

  • If top-level packages are found under package-dir, they will be included, together with any data files inside.
  • Otherwise, all top-level *.py files under package-dir will be included.
  • Test files under tests, if found, are included by sdist and excluded by other formats.

Note

Specifying includes and excludes will override the default values, so you need to include the package directories manually. *.pyc, __pycache__/ and build/ are always excluded.

Wheel data files#

You can include additional files that are not normally installed inside site-packages directory, with tool.pdm.build.wheel-data table:

1
2
3
4
5
6
7
8
[tool.pdm.build.wheel-data]
# Install all files under scripts/ to the $prefix/bin directory.
scripts = ["scripts/*"]
# Install all *.h files under headers/ (recursively) to the $prefix/include directory,
# flattening all files into one directory:
# headers/folder1/file1.h -> $prefix/include/file1.h
# headers/folder2/file2.h -> $prefix/include/file2.h
include = [{path = "headers/**/*.h"}]

The keys are the name of the install scheme, and should be amongst scripts, purelib, platlib, include, platinclude and data. The values should be lists of items, which may contain the following attributes:

  • path: The path pattern to match the files to be included.
  • relative-to: if specified, the relative paths of the matched files will be calculated based on this directory, otherwise the files will be flattened and installed directly under the scheme directory.

In both attributes, you can use ${BUILD_DIR} to refer to the build directory.

These files will be packaged into the {name}-{version}.data/{scheme} directory in the wheel distribution.

Advanced examples:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
[tool.pdm.build.wheel-data]
# Install all *.h files under headers/ (recursively) to the $prefix/include directory,
# keeping the directory structure (thanks to relative-to).
# We make the destination paths relative to "headers"
# so that "headers" does not appear in the destination paths:
# headers/folder1/file1.h -> $prefix/include/folder1/file1.h
include = [{path = "headers/**/*.h", relative-to = "headers/"}]
# Install all files under share/ (recursively) to the $prefix/data directory,
# keeping the directory structure (thanks to relative-to).
# We make the destination paths relative to "."
# to preserve the exact same directory structure in the destination:
# share/man/man1/project.1 -> $prefix/data/share/man/man1/project.1
data = [{path = "share/**/*", relative-to = "."}]

Warning

If you use pyproject-build to build your distributions, note that its default behavior is to build the wheel from the contents of the sdist. It means that if you don't include your data files to the sdist, they won't be included in the wheel. To disable this default mode of pyproject-build, explicitely pass it the -w, --wheel flag. But to be safe, and allow others to build your project with pyproject-build or python -m build, you should include your data files to the sdist, for example with:

1
2
3
4
5
6
7
8
[tool.pdm.build]
package-dir = "src"
source-includes = ["share"]

[tool.pdm.build.wheel-data]
data = [
    {path = "share/**/*", relative-to = "."},
]

Local build hooks#

You can specify a custom script to be executed before the build process, which can be used to generate files or modify the metadata. A file named pdm_build.py under the project root will be detected as custom hook script automatically, or you can specifiy the name via custom-hook setting:

1
2
[tool.pdm.build]
custom-hook = "build.py"

Default value: pdm_build.py

Run setuptools#

Due the lack of ability to build C extensions, pdm-backend allow users to call setuptools in an auto-generated setup.py from the project. This is enabled by setting run-setuptools to true:

1
2
[tool.pdm.build]
run-setuptools = true

Default value: false

The hook function pdm_build_update_setup_kwargs will be called to modify the arguments passed to setup() function as you desire.

Read the hooks document about the details of the hook functions.

is-purelib#

By default, pdm-backend produces non platform-specific wheels, with tag py3-none-any. But if run-setuptools is true, the built wheel will be platform-specific. You can override this behavior by setting is-purelib to true or false explicitly:

1
2
[tool.pdm.build]
is-purelib = true

Choose the editable build format#

pdm-backend supports two ways to build an editable wheel, path and editables, with the former being the default. It can be changed with editable-backend setting:

1
2
[tool.pdm.build]
editable-backend = "editables"

Default value: path

path#

In this approach, the editable build will be very similar to the legacy format generated by setuptools. A .pth file containing the parent path of the packages will be installed into the site-packages directory.

editables#

If you choose this approach, the backend will install a proxy module which redirects the import statements to the real location of the package, which is powered by the editables package.

Build config settings#

Some build frontends such as build and pdm supports passing options from command-line to the backend. pdm-backend supports the following config settings:

  • --python-tag=<tag> Override the python implementation compatibility tag(e.g. cp37, py3, pp3)
  • --py-limited-api=<abi> Python tag (cp32|cp33|cpNN) for abi3 wheel tag
  • --plat-name=<plat> Override the platform name(e.g. win_amd64, manylinux2010_x86_64)
  • --build-number=<build-number> Build number for this particular version. As specified in PEP-0427, this must start with a digit.
  • no-clean-build Don't clean the build directory before the build starts, this can also work by setting env var PDM_BUILD_NO_CLEAN to 1.

For example, you can supply these options with build:

1
python -m build --sdist --wheel --outdir dist/ --config-setting="--python-tag=cp37" --config-setting="--plat-name=win_amd64"

Environment variables#

  • SOURCE_DATE_EPOCH: Set the timestamp(seconds) of the zipinfo in the wheel for reproducible builds. The default date is 2016/01/01.