# Usage

## At build time

!!! note "Setuptools Version Requirements"
    setuptools-scm requires setuptools 61 or later (minimum), but recommends >=80 for best compatibility.
    Support for setuptools <80 is deprecated and will be removed in a future release.
    The examples below use `setuptools>=80` as the recommended version.

There are three ways to enable `setuptools-scm` at build time:

### Simplified Activation (new)

For basic usage without custom configuration, use the `simple` extra:

```toml title="pyproject.toml"
[build-system]
requires = ["setuptools>=80", "setuptools-scm[simple]>=8"]
build-backend = "setuptools.build_meta"

[project]
# version = "0.0.1"  # Remove any existing version parameter.
dynamic = ["version"]

# No [tool.setuptools_scm] section needed for basic usage!
```

This streamlined approach automatically enables version inference when:
- `setuptools-scm[simple]` is listed in `build-system.requires`
- `version` is included in `project.dynamic`

!!! tip "When to use simplified activation"

    Use simplified activation when you:
    - Want basic SCM version inference with default settings
    - Don't need custom version schemes or file writing
    - Prefer minimal configuration

    Upgrade to explicit configuration if you need customization.

### Explicit Configuration (full control)

Add a `tool.setuptools_scm` section for custom configuration:

```toml title="pyproject.toml"
[build-system]
requires = ["setuptools>=80", "setuptools-scm>=8"]
build-backend = "setuptools.build_meta"

[project]
# version = "0.0.1"  # Remove any existing version parameter.
dynamic = ["version"]

[tool.setuptools_scm]
# Configure custom options here (version schemes, file writing, etc.)
version_file = "src/mypackage/_version.py"

# Example: Git-specific configuration
[tool.setuptools_scm.scm.git]
pre_parse = "fail_on_missing_submodules"  # Fail if submodules are not initialized
describe_command = "git describe --dirty --tags --long --exclude *js*"  # Custom describe command
```

Projects must support PEP 518 ([pip](https://pypi.org/project/pip) and
[pep517](https://pypi.org/project/pep517/)). Tools that still invoke `setup.py`
must ensure build requirements are installed.

### Using the setup keyword

Alternatively, enable `setuptools-scm` via the `use_scm_version` keyword in `setup.py`.
This also counts as an explicit opt-in and does not require a tool section.

!!! note "Legacy simplified activation"

    Previous versions had a "simplified" activation where listing `setuptools_scm`
    in `build-system.requires` together with `project.dynamic = ["version"]` would
    auto-enable version inference. This behavior was removed due to regressions and
    ambiguous activation.

    The new simplified activation using the `[simple]` extra provides the same
    convenience but with explicit opt-in, making it clear when version inference
    should be enabled.

### Version files

Version files can be created with the ``version_file`` directive.

```toml title="pyproject.toml"
...
[tool.setuptools_scm]
version_file = "pkg/_version.py"
```
Where ``pkg`` is the name of your package.

Unless the small overhead of introspecting the version at runtime via
`importlib.metadata` is a concern or you need a version file in an
alternative format such as plain-text (see ``version_file_template``)
you most likely do _not_ need to write a separate version file; see
the runtime discussion below for more details.

## As cli tool

If you need to confirm which version string is being generated
or debug the configuration, you can install
[setuptools-scm](https://github.com/pypa/setuptools-scm)
directly in your working environment and run:

```commandline
$ python -m setuptools_scm # example from running local after changes
7.1.1.dev149+g5197d0f.d20230727
```

 and to list all tracked by the scm:

```commandline
$ python -m setuptools_scm ls # output trimmed for brevity
./LICENSE
...
./src/setuptools_scm/__init__.py
./src/...
...
```

!!! note "Committed files only"

    currently only committed files are listed, this might change in the future

!!! warning "sdists/archives don't provide file lists"

    Currently there is no builtin mechanism
    to safely transfer the file lists to sdists or obtaining them from archives.
    Coordination for setuptools and hatch is ongoing.

To explore other options, try

```commandline
$ python -m setuptools_scm --help
```

## At runtime

### Python Metadata

The standard method to retrieve the version number at runtime is via
[PEP-0566](https://www.python.org/dev/peps/pep-0566/) metadata using
``importlib.metadata`` from the standard library (added in Python 3.8)
or the
[`importlib_metadata`](https://pypi.org/project/importlib-metadata/)
backport for earlier versions:

```python title="package_name/__init__.py"
from importlib.metadata import version, PackageNotFoundError

try:
    __version__ = version("package-name")
except PackageNotFoundError:
    # package is not installed
    pass
```

### Via your version file

If you have opted to create a Python version file via the standard
template, you can import that file, where you will have a ``version``
string and a ``version_tuple`` tuple with elements corresponding to
the version tags.

```python title="Using package_name/_version.py"
import package_name._version as v

print(v.version)
print(v.version_tuple)
```

### Via setuptools_scm (strongly discouraged)

While the most simple **looking** way to use `setuptools_scm` at
runtime is:

```python
from setuptools_scm import get_version
version = get_version()
```

it is strongly discouraged to call directly into `setuptools_scm` over
the standard Python `importlib.metadata`.

In order to use `setuptools_scm` from code that is one directory deeper
than the project's root, you can use:

```python
from setuptools_scm import get_version
version = get_version(root='..', relative_to=__file__)
```

For legacy configurations or when working with extracted archives (like PyPI tarballs),
you may need to specify a `fallback_root` parameter. This is particularly useful
for legacy Sphinx configurations that use `get_version()` instead of getting the
version from the installed package:

```python
from setuptools_scm import get_version
# For legacy Sphinx conf.py that needs to work both in development and from archives
version = get_version(root='..', fallback_root='..', relative_to=__file__)
```

The `fallback_root` parameter specifies the directory to use when the SCM metadata
is not available (e.g., in extracted tarballs), while `root` is used when SCM
metadata is present.

### Usage from Sphinx

The recommended approach for Sphinx configurations is to use the installed package metadata:

``` {.python file=docs/.entangled/sphinx_conf.py}
from importlib.metadata import version as get_version
release: str = get_version("package-name")
# for example take major/minor
version: str = ".".join(release.split('.')[:2])
```

The underlying reason is that services like *Read the Docs* sometimes change
the working directory for good reasons and using the installed metadata
prevents using needless volatile data there.

!!! note "Legacy Sphinx configurations"

    If you have a legacy Sphinx configuration that still uses `setuptools_scm.get_version()`
    directly (instead of `importlib.metadata`), you may need to use the `fallback_root`
    parameter to ensure it works both in development and when building from archives:

    ```python
    from setuptools_scm import get_version
    # Legacy approach - use fallback_root for archive compatibility
    release = get_version(root='..', fallback_root='..', relative_to=__file__)
    version = ".".join(release.split('.')[:2])
    ```

    However, it's strongly recommended to migrate to the `importlib.metadata` approach above.


### With Docker/Podman


In some situations, Docker may not copy the `.git`  into the container when
building images. Because of this, builds with version inference may fail.

The following snippet exposes the external `.git` directory without copying.
This allows the version to be inferred properly form inside the container
without copying the entire `.git` folder into the container image.

```dockerfile
RUN --mount=source=.git,target=.git,type=bind \
    pip install --no-cache-dir -e .
```
However, this build step introduces a dependency to the state of your local
`.git` folder the build cache and triggers the long-running pip install process on every build.
To optimize build caching, one can use an environment variable to pretend a pseudo
version that is used to cache the results of the pip install process:


```dockerfile
FROM python
COPY pyproject.toml
ARG PSEUDO_VERSION=1 # strongly recommended to update based on git describe
RUN SETUPTOOLS_SCM_PRETEND_VERSION_FOR_MY_PACKAGE=${PSEUDO_VERSION} pip install -e .[test]
RUN --mount=source=.git,target=.git,type=bind pip install -e .
```

Note that running this Dockerfile requires docker with BuildKit enabled
[docs](https://github.com/moby/buildkit/blob/v0.8.3/frontend/dockerfile/docs/syntax.md).

To avoid BuildKit and mounting of the .git folder altogether, one can also pass the desired
version as a build argument.
Note that `SETUPTOOLS_SCM_PRETEND_VERSION_FOR_${DIST_NAME}`
is preferred over `SETUPTOOLS_SCM_PRETEND_VERSION`.



## Default versioning scheme

In the standard configuration `setuptools-scm` takes a look at three things:

1. latest tag (with a version number)
2. the distance to this tag (e.g. number of revisions since latest tag)
3. workdir state (e.g. uncommitted changes since latest tag)

and uses roughly the following logic to render the version:


| distance | state     | format                                                               |
|----------|-----------|----------------------------------------------------------------------|
| no       | unchanged | `{tag}`                                                              |
| yes      | unchanged | `{next_version}.dev{distance}+{scm letter}{revision hash}`           |
| no       | changed   | `{tag}+dYYYYMMDD`                                                    |
| yes      | changed   | `{next_version}.dev{distance}+{scm letter}{revision hash}.dYYYYMMDD` |

where `{next_version}` is the next version number after the latest tag

The next version is calculated by adding `1` to the last numeric component of
the tag.

For Git projects, the version relies on  [git describe](https://git-scm.com/docs/git-describe),
so you will see an additional `g` prepended to the `{revision hash}`.


## Version Tag Formats

setuptools-scm automatically detects version information from SCM tags. The default tag regex
supports a wide variety of tag formats, with the **"v" prefix being recommended** for clarity
and consistency.

### Recommended Tag Format

**Use the "v" prefix for version tags:**

```bash
git tag v1.0.0        # Recommended
git tag v2.1.3
git tag v1.0.0-alpha1
git tag v1.0.0-rc1
```

### Supported Tag Formats

setuptools-scm's default tag regex supports:

- **Version prefix**: `v` or `V` (optional, but recommended)
- **Project prefix**: Optional project name followed by dashes (e.g., `myproject-v1.0.0`)
- **Version number**: Standard semantic versioning patterns
- **Pre-release suffixes**: Alpha, beta, RC versions
- **Build metadata**: Anything after `+` is ignored

**Examples of valid tags:**
```bash
# Recommended formats (with v prefix)
v1.0.0
v2.1.3
v1.0.0-alpha1
v1.0.0-beta2
v1.0.0-rc1
v1.2.3-dev
V1.0.0              # Capital V also works

# Project-prefixed formats
myproject-v1.0.0
my-lib-v2.1.0

# Without v prefix (supported but not recommended)
1.0.0
2.1.3
1.0.0-alpha1

# With build metadata (metadata after + is ignored)
v1.0.0+build.123
v1.0.0+20240115
```

### Why Use the "v" Prefix?

1. **Clarity**: Makes it immediately obvious that the tag represents a version
2. **Convention**: Widely adopted standard across the software industry
3. **Git compatibility**: Works well with git's tag sorting and filtering
4. **Tool compatibility**: Many other tools expect version tags to have a "v" prefix

### Custom Tag Patterns

If you need different tag patterns, you can customize the tag regex:

```toml title="pyproject.toml"
[tool.setuptools_scm]
tag_regex = "^release-(?P<version>[0-9]+\\.[0-9]+\\.[0-9]+)$"
```

## Node ID Prefixes

setuptools-scm automatically prepends identifying characters to node IDs (commit/revision hashes)
to distinguish between different SCM systems:

- **Git repositories**: Node IDs are prefixed with `g` (e.g., `g1a2b3c4d5`)
- **Mercurial repositories**: Node IDs are prefixed with `h` (e.g., `h1a2b3c4d5`)

This prefixing serves several purposes:

1. **SCM identification**: Makes it clear which version control system was used
2. **Consistency**: Ensures predictable node ID format across different SCM backends
3. **Debugging**: Helps identify the source SCM when troubleshooting version issues

The prefixes are automatically added by setuptools-scm and should be included when manually
specifying node IDs in environment variables like `SETUPTOOLS_SCM_PRETEND_METADATA`.

**Examples:**
```bash
# Git node ID
1.0.0.dev5+g1a2b3c4d5

# Mercurial node ID
1.0.0.dev5+h1a2b3c4d5
```

!!! note

    According to [PEP 440](https://peps.python.org/pep-0440/#local-version-identifiers>),
    if a version includes a local component, the package cannot be published to public
    package indexes like PyPI or TestPyPI. The disallowed version segments may
    be seen in auto-publishing workflows or when a configuration mistake is made.

    However, some package indexes such as devpi or other alternatives allow local
    versions. Local version identifiers must comply with [PEP 440](https://peps.python.org/pep-0440/#local-version-identifiers>).

## Semantic Versioning (SemVer)

Due to the default behavior it's necessary to always include a
patch version (the `3` in `1.2.3`), or else the automatic guessing
will increment the wrong part of the SemVer (e.g. tag `2.0` results in
`2.1.devX` instead of `2.0.1.devX`). So please make sure to tag
accordingly.


## Builtin mechanisms for obtaining version numbers

1. the SCM itself (Git/Mercurial)
2. `.hg_archival` files (Mercurial archives)
3. `.git_archival.txt` files (Git archives, see subsection below)
4. `PKG-INFO`


### Git archives

Git archives are supported, but require specific setup and understanding of how they work with package building.

#### Overview

When you create a `.git_archival.txt` file in your repository, it enables setuptools-scm to extract version information from git archives (e.g., GitHub's source downloads). However, this file contains template placeholders that must be expanded by `git archive` - they won't work when building directly from your working directory.

#### Setting up git archival support

You can generate a `.git_archival.txt` file using the setuptools-scm CLI:

```commandline
# Generate a stable archival file (recommended for releases)
$ python -m setuptools_scm create-archival-file --stable

# Generate a full archival file with all metadata (use with caution)
$ python -m setuptools_scm create-archival-file --full
```

Alternatively, you can create the file manually:

**Stable version (recommended):**
```{ .text file=".git_archival.txt"}
node: $Format:%H$
node-date: $Format:%cI$
describe-name: $Format:%(describe:tags=true,match=*[0-9]*)$
```

**Full version (with branch information - can cause instability):**
```{ .text file=".git_archival.txt"}
# WARNING: Including ref-names can make archive checksums unstable
# after commits are added post-release. Use only if describe-name is insufficient.
node: $Format:%H$
node-date: $Format:%cI$
describe-name: $Format:%(describe:tags=true,match=*[0-9]*)$
ref-names: $Format:%D$
```

Feel free to alter the `match` field in `describe-name` to match your project's
tagging style.

!!! note

    If your git host provider does not properly expand `describe-name`, you may
    need to include `ref-names: $Format:%D$`. But **beware**, this can often
    lead to the git archive's checksum changing after a commit is added
    post-release. See [this issue][git-archive-issue] for more details.


``` {.text file=".gitattributes"}
.git_archival.txt  export-subst
```

Finally, commit both files:
```commandline
$ git add .git_archival.txt .gitattributes && git commit -m "add git archive support"
```

#### Understanding the warnings

If you see warnings like these when building your package:

```
UserWarning: git archive did not support describe output
UserWarning: unprocessed git archival found (no export subst applied)
```

This typically happens when:

1. **Building from working directory**: You're running `python -m build` directly in your repository
2. **Sdist extraction**: A build tool extracts your sdist to build wheels, but the extracted directory isn't a git repository

#### Recommended build workflows

**For development builds:**
Exclude `.git_archival.txt` from your package to avoid warnings:

```{ .text file="MANIFEST.in"}
# Exclude archival file from development builds
exclude .git_archival.txt
```

**For release builds from archives:**
Build from an actual git archive to ensure proper template expansion:

```commandline
# Create archive from a specific tag/commit
$ git archive --output=../source_archive.tar v1.2.3
$ cd ..
$ tar -xf source_archive.tar
$ cd extracted_directory/
$ python -m build .
```

**For automated releases:**
Many CI systems and package repositories (like GitHub Actions) automatically handle this correctly when building from git archives.

#### Integration with package managers

**MANIFEST.in exclusions:**
```{ .text file="MANIFEST.in"}
# Exclude development files from packages
exclude .git_archival.txt
exclude .gitattributes
```


```{ .text file=".gitattributes"}
# Archive configuration
.git_archival.txt  export-subst
.gitignore         export-ignore
```

#### Troubleshooting

**Problem: "unprocessed git archival found" warnings**
- ✅ **Solution**: Add `exclude .git_archival.txt` to `MANIFEST.in` for development builds
- ✅ **Alternative**: Build from actual git archives for releases

**Problem: "git archive did not support describe output" warnings**
- ℹ️ **Information**: This is expected when `.git_archival.txt` contains unexpanded templates
- ✅ **Solution**: Same as above - exclude file or build from git archives

**Problem: Version detection fails in git archives**
- ✅ **Check**: Is `.gitattributes` configured with `export-subst`?
- ✅ **Check**: Are you building from a properly created git archive?
- ✅ **Check**: Does your git hosting provider support archive template expansion?

!!! warning "Branch Names and Archive Stability"

    Including `ref-names: $Format:%D$` in your `.git_archival.txt` can make archive checksums change when new commits are added to branches referenced in the archive. This primarily affects GitHub's automatic source archives. Use the stable format (without `ref-names`) unless you specifically need branch information and understand the stability implications.

!!! note "Version Files"

    If you are creating a `_version.py` file, it should not be kept in version control. Add it to `.gitignore`:
    ```
    # Generated version file
    src/mypackage/_version.py
    ```

[git-archive-issue]: https://github.com/pypa/setuptools-scm/issues/806

### File finders hook makes most of `MANIFEST.in` unnecessary

!!! warning "Automatic File Inclusion"

    **`setuptools-scm` automatically provides a setuptools file finder by default.** This means that when you install setuptools-scm, it will automatically include **all SCM-tracked files** in your source distributions (sdist) without requiring a `MANIFEST.in` file.

    This automatic behavior can be surprising if you're not expecting it. The file finder is active as soon as setuptools-scm is installed in your build environment.

`setuptools-scm` implements a [file_finders] entry point
which returns all files tracked by your SCM.
This eliminates the need for a manually constructed `MANIFEST.in` in most cases where this
would be required when not using `setuptools-scm`.

[file_finders]: https://setuptools.pypa.io/en/stable/userguide/extension.html

#### How it works

1. **Automatic Discovery**: When building source distributions (`python -m build --sdist`), setuptools automatically calls the `setuptools-scm` file finder
2. **SCM Integration**: The file finder queries your SCM (Git/Mercurial) for all tracked files
3. **Inclusion**: All tracked files are automatically included in the sdist

#### Controlling file inclusion

**To exclude unwanted files:**

1. **Use `MANIFEST.in`** to exclude specific files/patterns:
   ```
   exclude development.txt
   recursive-exclude tests *.pyc
   ```

2. **Configure Git archive** (for Git repositories):
   ```bash
   # Add to .gitattributes
   tests/ export-ignore
   *.md export-ignore
   ```

3. **Use `.hgignore`** or **Mercurial archive configuration** (for Mercurial repositories)

#### Troubleshooting

**Problem: Unwanted files in my package**
- ✅ **Solution**: Add exclusions to `MANIFEST.in`
- ✅ **Alternative**: Use Git/Mercurial archive configuration

**Problem: Missing files in package**
- ✅ **Check**: Are the files tracked in your SCM?
- ✅ **Solution**: `git add` missing files or override with `MANIFEST.in`

**Problem: File finder not working**
- ✅ **Check**: Is setuptools-scm installed in your build environment?
- ✅ **Check**: Are you in a valid SCM repository?

### Timestamps for Local Development Versions

!!! info "Improved Timestamp Behavior"

    When your working directory has uncommitted changes (dirty), setuptools-scm now uses the **actual modification time of changed files** instead of the current time for local version schemes like `node-and-date`.

    **Before**: Dirty working directories always used current time (`now`)
    **Now**: Uses the latest modification time of changed files, falling back to current time only if no changed files are found

    This provides more stable and meaningful timestamps that reflect when you actually made changes to your code.

**How it works:**

1. **Clean repository**: Uses commit timestamp from SCM
2. **Dirty repository**: Uses latest modification time of changed files
3. **Fallback**: Uses current time if no modification times can be determined

**Benefits:**

- More stable builds during development
- Timestamps reflect actual change times
- Better for reproducible development workflows
