This utility provides the capability to dynamically determine the version of a python distribution package at build-time based on the current version (as determined by the most recent git tag) and a "version bumping" policy. It extends functionality in the setuptools
library and as such requires it as the configured build-backend.
In order to properly hook setuptools
, Dynamic Versioning needs both a setup.py
file and a pyproject.toml
file. Advocates of the use of setup.py
tend to not want to use pyproject.toml
, and vice versa. And most would prefer to not use both. However, setuptools
does not provide a way in pyproject.toml
to dictate "overrides" of Command
classes, thus the override of egg_info
must be defined in setup.py
. A minimal setup.py
could be:
from setuptools import setup
import dynamic_versioning
setup(cmdclass={"egg_info": dynamic_versioning.DynamicVersioningEggInfo})
... and all other configuration values could be defined in pyproject.toml
.
Meanwhile, PEP 517 front-ends, such as pip
expect the build-backend to be defined in pyproject.toml
, so to force the use of setuptools
as the build-backend, a pyproject.toml
file is also necessary. A minimal pyproject.toml
file could be:
[build-system]
requires = ["setuptools", "dynamic-versioning"]
build-backend = "setuptools.build_meta"
... and all other configuration value could be defined in setup.py
.
Note: A Future version of Dynamic Versioning will provide a command line utility for generating these minimal configuration files. This may be helpful to initialize a new project.
The software provides four configuration values that define how it will dynamically determine the version at build-time. No change is needed to the command to build - both python setup.py bdist_wheel|install|...
(soon to be deprecated by setuptools
) and the more modern pip wheel .
will trigger the version determination.
The first configuration value is "new version" and can be set in one of three ways:
- In a configuration file named
dynamic_versioning.ini
:
[dynamic_versioning]
new-version = 1.2.3
- If
dynamic_versioning.ini
is not found, inpyproject.toml
:
[tool.dynamic_versioning]
new-version = "1.2.3"
- Or, in the environment variable
DV_NEW_VERSION
If this configuration value is set, the version will be set to the value - simple as that. This overrides any version set in setup.py
and/or pyproject.toml
. If this configuration value is set, all other configuration values will be ignored.
The second configuration value is "version bump" and is the heart of Dynamic Versioning. This configuration value can be set using version-bump
in either dynamic_versioning.ini
or pyproject.toml
, or the environment variable DV_VERSION_BUMP
. The value provided must be one of: PATCH
(UPDATE
is also accepted), MINOR
, or MAJOR
(using any case). When provided, Dynamic Versioning will grab the most recent annotated git tag and parse it as the current version. It is assumed that tags will follow semantic versioning format, with an optional prepended "V" (ex. [v|V]4.2.7
). The portion of the current version designated by the value of "version bump" is then incremented by 1 ("bumped") and the lesser portions are zeroed out.
For example:
- 1.2.3 bumped by PATCH (or UPDATE) -> 1.2.4
- 1.2.3 bumped by MINOR -> 1.3.0
- 1.2.3 bumped by MAJOR -> 2.0.0
If an annotated git tag is not available, the version will be set to 0.0.1
by default. Since the system depends on annotated git tags, it is expected that a tag will be created after an official version has been built and published. This default allows a "bootstrap" to that process. Of course, new-version
can always be used to bootstrap the process at a different version.
The third configuration value is "current version" and can be set in the same three ways: current-version
in either dynamic_versioning.ini
or pyproject.toml
, or the environment variable DV_CURRENT_VERSION
. This value is used as a "fall-back" for when the version cannot be found by parsing the most recent annotated git tag. This is useful in circumstances when git is not available (or cannot be counted on to be available) on the system building the distribution package. This value is required when generating a source distribution, as the .git
directory will almost certainly not be packed inside the source distribution.
The last configuration value provided is "dev version" and can be set in the same three ways: dev-version
in either dynamic_versioning.ini
or pyproject.toml
, or the environment variable DV_DEV_VERSION
. This allows the developers to denote the build as a development version. This will result in a version that adheres to the PEP 440 section on development versioning. While determining the current version through the most recent git tag, the number of commits since that tag is also captured. The current version is then "bumped" using the value set by "version bump," defaulting to MAJOR
when absent. The version is then assembled using the format {major}.{minor}.{patch}.dev{commit_count}
. For instance, if there have been 12 commits since 4.2.7
, the development version could become: 5.0.0.dev12
(Major), 4.3.0.dev12
(Minor), or 4.2.8.dev12
(Patch).
git
is being used- tags are annotated, or at least the ones that provide version numbers are
- the annoted tags follow (simple) semantic versioning (
[v|V]{major}.{minor}.{patch}
)
A note about logging. If you use python setup.py ...
(using setuptools
as both a build-frontend and build-backend), and define a logger in your setup.py
file, this library will log a few things along the way. However, if you use pip wheel .
, pip
silences all logging from the build-backend. That is, unless there is an error. pip
is then quick to tell you it's not pip
's fault - check for the error in the build-backend or your build configuration. And when it is doing that, it will allow the logs from setuptools
and Dynamic Versioning to be seen.
In case you want to develop against this code base, or just run the unit tests, here's how you do it:
- Optionally (but suggested), create a virtual environment (I like
virtualenvwrapper
) - Run the tests:
tests/run_tests.sh
. This process will install the software, along with the testing dependencies, run several linters, and then the unit tests.
In most places I have developed software, developers forget to update the version field in the setup.py
file. And then one is faced with making a commit to SCM just to change it, and that commit doesn't represent a change in the actual code. In an environment which adopts the CI/CD attitude of: "every commit has the potential to be a release," that doesn't fly so well. Further, when the build server automatically publishes packages, the build process needs to determine the version (dynamically or manually) in order to publish.
Some project managers like to have a human-in-the-loop (usually themselves) and manually kick off the build that actually publishes the package. In this case, a build server configuration that allows providing parameters can be helpful. This software adds a configuration ("new version") so a version can easily be provided, for instance from the build config parameters.
But in other cases, "versions" are cheap - especially when the distribution packages are published to an internal artifact server so "plugin" developers, possibly from a different department, the customer, or even a partnered company, have access. But, because of the distinct API lines drawn between these players, "development" versions might be discouraged. The versions published are official, but are often-updated and a prime use of the "continuous" part of CI/CD. Either of "version bump" or "dev version" can help accomplish this in a dynamic way.