Skip to content

Commit

Permalink
Fix #7 rename to chronver; Clean up readme (#9)
Browse files Browse the repository at this point in the history
  • Loading branch information
robnagler authored Mar 18, 2024
1 parent 9a5c00f commit 70c0755
Show file tree
Hide file tree
Showing 11 changed files with 70 additions and 37 deletions.
71 changes: 52 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,31 +1,24 @@
## Introduction

RSChronVer is a
ChronVer is a
[setuptools.meta_build](https://setuptools.pypa.io/en/latest/build_meta.html)
plugin to add a
[chronological version](https://www.robnagler.com/2015/04/11/Major-Release-Syndrome.html)
(yyyymmdd.hhmmss)
to a Python package's metadata from the current Git commit of `HEAD`.
This ensures a unique, sortable version for packages on [PiPI](https://pypi.org).
This ensures a unique, sortable version for packages on [PyPI](https://pypi.org).

In development, if there are modifications, current time will be
In development, if there are modifications, the current time will be
returned. This ensures that `pip install` works, because there's
always a new version, increasing version.

A chronological version is distinct from a
[CalVer](https://calver.org), because it is alphabetically sortable.
There are no non-alphabetically sortable modifiers. The time stamp
identifies a specific commit so a commit SHA doesn't need to be
included as a modifier. We do adopt a stricter view of CalVer's tag
line: Versioning gets better with time.
always a new, increasing version.

## Usage

Add this to your `pyproject.toml`:

```toml
[build-system]
requires = ["setuptools>=61", "rschronver"]
requires = ["setuptools>=61", "chronver"]
build-backend = "setuptools.build_meta"

[project]
Expand Down Expand Up @@ -53,21 +46,61 @@ If you are using a Python before 3.8, use the backport
## Releases without breakage (almost)

[RadiaSoft](https://radiasoft.net) uses chronological versioning for
all its releases. We strive to maintain backward compatability for as
long as our users require it.
all its releases. This means no major releases, which implies breaking
changes. To make this happen, we strive to maintain backward
compatibility for as long as our users require it.

This is why there are no major or minor release numbers in
chronological versions; there are no breaking API changes (except
defects, of course) with each release. We feel it is our obligation to
not break uses by clients of our APIs.
not break our client's code.

There are times when this is impossible or impractical, such as the
move away from Python 2. In those cases, we give clients warnings and
an upgrade path and sometimes a tool that upgrades their data or code
automatically. This is an obligation that comes with chronological
versioning that RadiaSoft takes seriously.

Finally, we try to ensure breakage (and failures due to defects) is
[fail fast](https://en.wikipedia.org/wiki/Fail-fast_system). We don't
want our client's code continuing (doing damage) if it is using one
RadiaSoft's packages incorrectly.
## Why another versioning scheme?

ChronVer is a new implementation of our existing
[setup.py versioning module](https://github.com/radiasoft/pykern/blob/eaea8492e1e8485c8b35aee67ad1204e8838da97/pykern/pksetup.py#L684)
so it is not new at all. RadiaSoft has been using chronological
versioning for Python, Docker images, packaging (RPMs), etc. for over
a decade. Before that @robnagler (primary author) was using
chronlogical versioning since at least 2000. So there's a long history
here, and it has worked well for us.

[SemVer](https://semver.org) makes many assumptions about how software
works that conflicts with how we develop. In our model, software flows
like time, and releases are arbitary points on the time axis. SemVer
divides software releases into fixed epochs: major, minor, patch, and
additional labels. This requires people make complicated decisions
about these different kinds of epochs, which adds work and we think
adds little value. This can result in
[Major Release Syndrome](https://www.robnagler.com/2015/04/11/Major-Release-Syndrome.html),
which is an impediment to continuous software development and
deployment.

[CalVer](https://calver.org) has a great tag line: "Versioning gets
better with time." We agree! In CalVer, "time" means "date", that is,
days are the time quantum. This granularity does not allow for same
day releases, which we happen to do on occasion. A second is
ChronVer's time quantum for this reason. CalVer tries to be SemVer
compliant, which as noted above, is not how we develop software.

There are benefits to simplifying version strings to always being
lexicographically sortable. Any two ChronVers can be compared with a
single operator `v1 < v2`. We have a lot internal devops tooling in
multiple languages, and all languages support simple lexicographic
comparison of strings. A ChronVer is compatible with Python's `float`,
which is useful in some cases.

A ChronVer identifies any commit in any SCM. We use Git now. We used
to use CVS (which does not have a SHA), and ChronVer worked just as
well. All SCM's (that we know of) answer the question: what was the
commit at this timestamp? SemVer and CalVer have various schemes for
doing this, but they are awkward and break lexicographic comparison.

For more complete reasoning, refer to
[Major Release Syndrome](https://www.robnagler.com/2015/04/11/Major-Release-Syndrome.html).
4 changes: 2 additions & 2 deletions _own_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@
# Actual build system: "_own_version:build_meta"
from setuptools import build_meta as build_meta
import re
import rschronver.setuptools_hooks
import chronver.setuptools_hooks


def __getattr__(name):
if name == "from_git":
return rschronver.setuptools_hooks.version("rschronver")
return chronver.setuptools_hooks.version("chronver")
raise AttributeError(name)
2 changes: 1 addition & 1 deletion rschronver/__init__.py → chronver/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import importlib.metadata

try:
__version__ = importlib.metadata.version("rschronver")
__version__ = importlib.metadata.version("chronver")
except importlib.metadata.PackageNotFoundError:
# We only have a version once the package is installed.
pass
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
:license: http://www.apache.org/licenses/LICENSE-2.0.html
"""

import rschronver.git
import rschronver.pkg_info
import chronver.git
import chronver.pkg_info


def set_version(dist):
Expand All @@ -19,10 +19,10 @@ def version(name):
Returns:
str: chronological version or raises `LookupError`
"""
v = rschronver.git.version() or rschronver.pkg_info.version()
v = chronver.git.version() or chronver.pkg_info.version()
if v is None:
raise LookupError(
f"rschronver was unable to detect version for {name}.\n\n"
f"chronver was unable to detect version for {name}.\n\n"
"Make sure you're either building from a fully intact git repository "
"or PyPI tarballs. Most other sources (such as GitHub's tarballs, a "
"git checkout without the .git folder) don't contain the necessary "
Expand Down
6 changes: 3 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,15 @@ keywords = [
"vcs",
"version",
]
name = "rschronver"
name = "chronver"
readme = "README.md"
requires-python = ">=3.8"

[project.entry-points."setuptools.finalize_distribution_options"]
rschronver = "rschronver.setuptools_hooks:set_version"
chronver = "chronver.setuptools_hooks:set_version"

[project.urls]
Homepage = "https://github.com/radiasoft/rschronver"
Homepage = "https://github.com/radiasoft/chronver"

[tool.setuptools]
license-files = ["LICENSE"]
Expand Down
4 changes: 2 additions & 2 deletions test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ test_err() {
}

test_main() {
if grep -r -i pykern rschronver; then
test_err "rschronver python files may not use/mention pykern"
if grep -r -i pykern chronver; then
test_err "chronver python files may not use/mention pykern"
fi
# workaround https://github.com/radiasoft/pykern/issues/453
trap 'rm -f setup.py' EXIT
Expand Down
6 changes: 3 additions & 3 deletions tests/git_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

def test_basic():
from pykern import pkunit
from rschronver import git
from chronver import git
import datetime
import os
import time
Expand All @@ -32,7 +32,7 @@ def test_basic():

def test_git_status_error(capsys):
from pykern import pkunit, pkio, pkdebug
from rschronver import git
from chronver import git
import os
import subprocess

Expand All @@ -47,7 +47,7 @@ def test_git_status_error(capsys):
@contextlib.contextmanager
def _setup():
from pykern import pkunit, pkio, pkdebug
from rschronver import git
from chronver import git
import os

with pkunit.save_chdir_work() as d:
Expand Down
4 changes: 2 additions & 2 deletions tests/pkg_info_test.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""test rschronver.pkg_info
"""test chronver.pkg_info
:copyright: Copyright (c) 2024 RadiaSoft LLC. All Rights Reserved.
:license: http://www.apache.org/licenses/LICENSE-2.0.html
Expand All @@ -7,7 +7,7 @@

def test_basic():
from pykern import pkunit
from rschronver import pkg_info
from chronver import pkg_info

with pkunit.save_chdir_work():
a = pkg_info.version()
Expand Down
2 changes: 1 addition & 1 deletion tests/setuptools_hooks_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

def test_basic():
from pykern import pkunit
from rschronver import setuptools_hooks
from chronver import setuptools_hooks
import datetime
import os

Expand Down

0 comments on commit 70c0755

Please sign in to comment.