Process Mozilla's version numbers. Support main products like Firefox desktop, Fennec, Fenix, and Thunderbird. Tell whether version numbers are valid or not, whether they are nightlies or regular releases, whether this version precedes that other.
Mozilla's build and release pipelines deal with version numbers. This has naturally grown into many scripts living at various places. Most of them reimplemented the same logic that determined whether a version was of which type. In theory, it's a simple problem that could (and was) solved with a regular expression. Although, as a human group, Mozilla hasn't been 100% consistent in giving version numbers to its products. Thus, the regular expression had to grow and we had to update each place that dealt with such exceptions.
Hence, mozilla-version
is an attempt to become the source of truth when a piece of software has to deal with version numbers. It was build to take existing exceptions into account while enforcing new versions to comply with known schemes.
Want to use mozilla-version
? Here's how: https://mozilla-version.readthedocs.io/en/latest/
Just install it from pip:
pip install mozilla-version
virtualenv venv # create the virtualenv in ./venv
. venv/bin/activate # activate it
git clone https://github.com/mozilla-releng/mozilla-version
cd mozilla-version
pip install --require-hashes -r requirements/local.txt
mozilla-version
uses classes to represent version numbers. For readers wondering: the original author is not a big fan of OOP and usually tries to not use this paradigm. Although, version handling turns out to be simpler when consumers of mozilla-version
get objects. For instance:
# Functional programming
version_string = "84.0b3"
if is_version_beta(version_string) and get_beta_number(version_string) >= 2:
do_something()
# OOP
version = FirefoxVersion.parse("84.0b3")
if version.is_beta and version.beta_number >= 2:
do_something()
In the latter case, data gets parsed once and the if
statement is closer to a regular English sentence. Another example:
# Functional programming
from functools import cmp_to_key
version_strings = ["84.0b3", "100.0.1", "84.0a1", "84.0"]
sorted_versions = sorted(version_strings, key=cmp_to_key(compare_versions))
# compare_version() would be a function provided by the mozilla-version library.
# Signature would look like this: compare_versions(version_a, version_b)
#
# cmp_to_key() comes from https://docs.python.org/3/howto/sorting.html#the-old-way-using-the-cmp-parameter
# OOP
version_strings = ["84.0b3", "100.0.1", "84.0a1", "84.0"]
versions = [FirefoxVersion.parse(string) for string in version_strings]
sorted_version = sorted(versions)
Once again, compare_versions()
would have to parse both versions every time it's called. We could memoize intermediary results but it doesn't help in writing a straightforward sorted()
call.
One major drawback of OOP is the complexity of the class hierarchy. Remembering side effects of a parent's function call can be hard to remember and debug. Luckily, the problem around version numbers is restricted enough to likely not be a problem in the future.
Like mentioned in the rationale, this problem cannot be solved with a regular expression anymore. mozilla-version
checks whether a version number is valid thanks to 2 passes.
The first one is a regular expression. Its purpose to take out strings that are obviously neither version numbers, nor roughly matching the expected scheme. It's not meant to be strict.
The second pass ensures data is 100% valid. This is done at the end of the __init__()
call. Therefore, a consumer of say FirefoxVersion()
is guaranteed to deal with a valid version number the constructor succeeds (which is the implicit rule of a constructor, by the way). Speaking of __init__()
, mozilla-version relies on attrs
which implicitly defines constructors for us. Hence, the second pass is processed in __attrs_post_init__()
.
Edge cases are handled in the second pass. This means: the first pass needs to be broad enough to let edge cases pass through.
They are explicitly called out in each class as _RELEASED_EDGE_CASES
. They were found on:
Version numbers are a very simple problem to unit test. This library has 100% coverage and unit tests are small enough to be extended. Cases are usually written in a simple way where version under tests are plain strings. Then, the test itself parses each string. It's really meant to favor case addition. Feel free to add any edge cases you have in mind.
Run maintenance/pin.sh
mozilla-version follows semver. Essentially, increment the
- MAJOR version when you make incompatible API changes,
- MINOR version when you add API functionality in a backwards-compatible manner, and
- PATCH version when you make backwards-compatible bug fixes.
Update the changelog and version.txt to the new version before making a new release.
If you add change the list of files that need to be packaged (either adding new files, or removing previous packaged files), modify MANIFEST.in
.
To enable gpg signing in git,
- you need a gpg keypair
- you need to set your
user.signingkey
in your~/.gitconfig
orscriptworker/.git/config
- If you want to specify a specific gpg executable, specify your
gpg.program
in your~/.gitconfig
orscriptworker/.git/config
Checkout the current master branch, tag and sign!
# make sure you've committed your changes first!
VERSION=<version>
git tag -s $VERSION -m"$VERSION"
Push!
# By default this will push the new tag to origin; make sure the tag gets pushed to
# mozilla-releng/mozilla-version
git push --tags
Someone with access to the mozilla-version package on pypi.python.org
needs to do the following (twine and wheel packages are installed):
# from https://packaging.python.org/tutorials/distributing-packages/#uploading-your-project-to-pypi
# Don't use `python setup.py register` or `python setup.py upload`; this may use
# cleartext auth!
# Using a python with `twine` in the virtualenv:
VERSION=4.1.2
# create the source tarball and wheel
python setup.py sdist bdist_wheel
# upload the source tarball + wheel
twine upload dist/mozilla-version-${VERSION}.tar.gz dist/mozilla-version-${VERSION}-py3-none-any.whl
That creates source tarball and wheel, and uploads it.