1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301
|
# Usage
## At build time
The preferred way to configure `setuptools-scm` is to author
settings in the `tool.setuptools_scm` section of `pyproject.toml`.
It's necessary to use a setuptools version released after 2022.
```toml title="pyproject.toml"
[build-system]
requires = ["setuptools>=64", "setuptools-scm>=8"]
build-backend = "setuptools.build_meta"
[project]
# version = "0.0.1" # Remove any existing version parameter.
dynamic = ["version"]
[tool.setuptools_scm]
# can be empty if no extra settings are needed, presence enables setuptools-scm
```
That will be sufficient to require `setuptools-scm` for projects
that 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
### 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__)
```
### Usage from Sphinx
``` {.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.
### 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_${NORMALIZED_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}`.
!!! 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 a few changes to your repository are required.
Ensure the content of the following files:
```{ .text file=".git_archival.txt"}
node: $Format:%H$
node-date: $Format:%cI$
describe-name: $Format:%(describe:tags=true,match=*[0-9]*)$
```
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, don't forget to commit the two files:
```commandline
$ git add .git_archival.txt .gitattributes && git commit -m "add export config"
```
Note that if you are creating a `_version.py` file, note that it should not
be kept in version control. It's strongly recommended to be put into gitignore.
[git-archive-issue]: https://github.com/pypa/setuptools-scm/issues/806
### File finders hook makes most of `MANIFEST.in` unnecessary
`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`, namely:
* To ensure all relevant files are packaged when running the `sdist` command.
* When using [include_package_data] to include package data as part of the `build` or `bdist_wheel`.
`MANIFEST.in` may still be used: anything defined there overrides the hook.
This is mostly useful to exclude files tracked in your SCM from packages,
although in principle it can be used to explicitly include non-tracked files too.
[file_finders]: https://setuptools.pypa.io/en/latest/userguide/extension.html#adding-support-for-revision-control-systems
[include_package_data]: https://setuptools.readthedocs.io/en/latest/setuptools.html#including-data-files
|