From f8828ecb99c1b359a79f4ca0ad58a52bf558b05c Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Tue, 25 Aug 2026 12:53:55 +0200 Subject: [PATCH 1/3] setup.py: fail fast if the version can not be determined, fixes #7259 setuptools_scm computes the borg version from git tags. In a shallow clone or a clone without tags it does not fail the build, it just yields a wrong version like 0.1.dev1+gedcff4f - and that is only noticed after cythonizing and compiling everything, which wastes >5min of CI time for nothing. So ask setuptools_scm for the version before building anything, rather than reimplementing its tag lookup here: - shallow repository: use setuptools_scm's own fail_on_shallow pre_parse hook, which also catches a shallow clone that does happen to have a tag (the distance would be wrong there). Passed as a get_version() argument, not put into pyproject.toml, because the "scm" table only exists in recent setuptools-scm and older ones hard-fail on unknown keys there, see #10193. - no tags at all: a full clone with the tags deleted is not shallow, so fail_on_shallow does not catch it. setuptools_scm falls back to a 0.x tag there and borg has no 0.x releases, so that is what we look at. - no version source at all: setuptools_scm raises its own helpful error, just much earlier than before. Builds that get their version from SETUPTOOLS_SCM_PRETEND_VERSION or from a sdist's PKG-INFO are not affected, and neither are ReadTheDocs builds. Co-Authored-By: Claude Opus 5 --- setup.py | 75 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/setup.py b/setup.py index db4061d21a..ad39da36e2 100644 --- a/setup.py +++ b/setup.py @@ -3,6 +3,7 @@ import os import re import sys +import warnings from collections import defaultdict try: @@ -45,6 +46,80 @@ # Are we building on ReadTheDocs? on_rtd = os.environ.get("READTHEDOCS") + +def check_version_detectable(): + """Fail before building anything if the borg version can not be determined. + + The version is computed from git tags by setuptools_scm. In a shallow clone or a clone + without tags this does not fail, it just yields a wrong version like 0.1.dev1+gedcff4f - + and that is only noticed after cythonizing and compiling everything, see #7259. + + We ask setuptools_scm itself instead of reimplementing its tag lookup here. + """ + if on_rtd: + return # building the docs does not need an exact version. + try: + from setuptools_scm import get_version + except ImportError: + return # can not check. if the version is really needed, setup() will complain later. + + here = os.path.dirname(os.path.abspath(__file__)) + found_tags = [] + + def capture_tag(scm_version): + # a custom version_scheme is only used to get at the tag setuptools_scm found, + # the version we return here is not used for anything. + found_tags.append(str(scm_version.tag)) + return "0" + + def fail(reason): + raise SystemExit( + "Can not determine the borg version: %s\n" + "\n" + "The version is computed from git tags, so building here would silently produce a\n" + "wrong version (like 0.1.dev1+gedcff4f). Use one of these:\n" + "\n" + "- clone the full repository (a shallow clone or a clone without tags will not work):\n" + " git clone https://github.com/borgbackup/borg.git\n" + "- fetch what your existing clone is missing:\n" + " git fetch --unshallow --tags\n" + "- or give the version explicitly, e.g. when building without git:\n" + " SETUPTOOLS_SCM_PRETEND_VERSION=2.0.0b23 pip install -e ." % reason + ) + + def ask_setuptools_scm(**extra): + del found_tags[:] + with warnings.catch_warnings(): + # the real build triggers the same warnings, we do not want to duplicate them. + warnings.simplefilter("ignore") + # raises LookupError (with its own helpful message) if there is no version source at all. + get_version(root=here, version_scheme=capture_tag, local_scheme=lambda scm_version: "", **extra) + + try: + # let setuptools_scm fail on a shallow repository instead of computing a version from + # truncated history, see + # https://setuptools-scm.readthedocs.io/en/latest/integrations/#enforce-fail-on-shallow-repositories + # This is not in pyproject.toml on purpose: the "scm" table only exists in recent + # setuptools-scm and older ones hard-fail on unknown keys there, see #10193. + ask_setuptools_scm(scm={"git": {"pre_parse": "fail_on_shallow"}}) + except TypeError: # setuptools-scm too old for the "scm" config, check what we can without it. + ask_setuptools_scm() + except ValueError as err: # that is how fail_on_shallow complains. + fail(str(err).splitlines()[0]) + + if not found_tags: + # no tag was looked at: the version came from SETUPTOOLS_SCM_PRETEND_VERSION or, when + # building from a sdist, from PKG-INFO. Nothing to check in that case. + return + if found_tags[0].startswith("0."): + # borg has no 0.x releases, so this is the fallback tag setuptools_scm uses when it does + # not find any tag at all. Note: a full clone without tags is not shallow, so + # fail_on_shallow does not catch this one. + fail("setuptools_scm did not find a borg release tag.") + + +check_version_detectable() + # Extra cflags for all extensions, usually just warnings we want to enable explicitly cflags = ["-Wall", "-Wextra", "-Wpointer-arith"] From 9acbf7ad7f241216f7b059f2b69c295d4d71ff4a Mon Sep 17 00:00:00 2001 From: TW Date: Tue, 25 Aug 2026 15:07:32 +0200 Subject: [PATCH 2/3] Update setup.py Co-authored-by: Josh Soref <2119212+jsoref@users.noreply.github.com> --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index ad39da36e2..6add759dec 100644 --- a/setup.py +++ b/setup.py @@ -74,7 +74,7 @@ def capture_tag(scm_version): def fail(reason): raise SystemExit( - "Can not determine the borg version: %s\n" + "Cannot determine the borg version: %s\n" "\n" "The version is computed from git tags, so building here would silently produce a\n" "wrong version (like 0.1.dev1+gedcff4f). Use one of these:\n" From eed1f8c77fb9cac3093d52d4d18e9f4bebcf062e Mon Sep 17 00:00:00 2001 From: TW Date: Tue, 25 Aug 2026 15:07:41 +0200 Subject: [PATCH 3/3] Update setup.py Co-authored-by: Josh Soref <2119212+jsoref@users.noreply.github.com> --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 6add759dec..66eb0a4006 100644 --- a/setup.py +++ b/setup.py @@ -61,7 +61,7 @@ def check_version_detectable(): try: from setuptools_scm import get_version except ImportError: - return # can not check. if the version is really needed, setup() will complain later. + return # cannot check. if the version is really needed, setup() will complain later. here = os.path.dirname(os.path.abspath(__file__)) found_tags = []