diff options
author | con-f-use <con-f-use@gmx.net> | 2020-03-18 18:29:24 +0100 |
---|---|---|
committer | con-f-use <con-f-use@gmx.net> | 2020-03-18 18:29:24 +0100 |
commit | 1d1baf66446c4e8c049d2a21d86fe78dd3766250 (patch) | |
tree | 7836ef273ac7085ab0d4e1b6177977b6cebecb44 | |
parent | 69eb0fa5660645a9b28c078b5b3e8dddc74267fa (diff) | |
download | setuptools-scm-1d1baf66446c4e8c049d2a21d86fe78dd3766250.tar.gz |
Change ScmVersion.time to UTC
-rw-r--r-- | utils.py | 158 | ||||
-rw-r--r-- | version.py | 313 |
2 files changed, 471 insertions, 0 deletions
diff --git a/utils.py b/utils.py new file mode 100644 index 0000000..ab0dede --- /dev/null +++ b/utils.py @@ -0,0 +1,158 @@ +""" +utils +""" +from __future__ import print_function, unicode_literals +import inspect +import warnings +import sys +import shlex +import subprocess +import os +import io +import platform +import traceback +import datetime + + +DEBUG = bool(os.environ.get("SETUPTOOLS_SCM_DEBUG")) +IS_WINDOWS = platform.system() == "Windows" +PY2 = sys.version_info < (3,) +PY3 = sys.version_info > (3,) +string_types = (str,) if PY3 else (str, unicode) # noqa + + +def no_git_env(env): + # adapted from pre-commit + # Too many bugs dealing with environment variables and GIT: + # https://github.com/pre-commit/pre-commit/issues/300 + # In git 2.6.3 (maybe others), git exports GIT_WORK_TREE while running + # pre-commit hooks + # In git 1.9.1 (maybe others), git exports GIT_DIR and GIT_INDEX_FILE + # while running pre-commit hooks in submodules. + # GIT_DIR: Causes git clone to clone wrong thing + # GIT_INDEX_FILE: Causes 'error invalid object ...' during commit + for k, v in env.items(): + if k.startswith("GIT_"): + trace(k, v) + return { + k: v + for k, v in env.items() + if not k.startswith("GIT_") + or k in ("GIT_EXEC_PATH", "GIT_SSH", "GIT_SSH_COMMAND") + } + + +def trace(*k): + if DEBUG: + print(*k) + sys.stdout.flush() + + +def trace_exception(): + DEBUG and traceback.print_exc() + + +def ensure_stripped_str(str_or_bytes): + if isinstance(str_or_bytes, str): + return str_or_bytes.strip() + else: + return str_or_bytes.decode("utf-8", "surrogateescape").strip() + + +def _always_strings(env_dict): + """ + On Windows and Python 2, environment dictionaries must be strings + and not unicode. + """ + if IS_WINDOWS or PY2: + env_dict.update((key, str(value)) for (key, value) in env_dict.items()) + return env_dict + + +def _popen_pipes(cmd, cwd): + return subprocess.Popen( + cmd, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + cwd=str(cwd), + env=_always_strings( + dict( + no_git_env(os.environ), + # os.environ, + # try to disable i18n + LC_ALL="C", + LANGUAGE="", + HGPLAIN="1", + ) + ), + ) + + +def do_ex(cmd, cwd="."): + trace("cmd", repr(cmd)) + if os.name == "posix" and not isinstance(cmd, (list, tuple)): + cmd = shlex.split(cmd) + + p = _popen_pipes(cmd, cwd) + out, err = p.communicate() + if out: + trace("out", repr(out)) + if err: + trace("err", repr(err)) + if p.returncode: + trace("ret", p.returncode) + return ensure_stripped_str(out), ensure_stripped_str(err), p.returncode + + +def do(cmd, cwd="."): + out, err, ret = do_ex(cmd, cwd) + if ret: + print(err) + return out + + +def data_from_mime(path): + with io.open(path, encoding="utf-8") as fp: + content = fp.read() + trace("content", repr(content)) + # the complex conditions come from reading pseudo-mime-messages + data = dict(x.split(": ", 1) for x in content.splitlines() if ": " in x) + trace("data", data) + return data + + +class UTC(datetime.tzinfo): + _ZERO = datetime.timedelta(0) + def utcoffset(self, dt): + return self._ZERO + def tzname(self, dt): + return "UTC" + def dst(self, dt): + return self._ZERO +utc = UTC() + + +def function_has_arg(fn, argname): + assert inspect.isfunction(fn) + + if PY2: + argspec = inspect.getargspec(fn).args + else: + + argspec = inspect.signature(fn).parameters + + return argname in argspec + + +def has_command(name): + try: + p = _popen_pipes([name, "help"], ".") + except OSError: + trace(*sys.exc_info()) + res = False + else: + p.communicate() + res = not p.returncode + if not res: + warnings.warn("%r was not found" % name) + return res diff --git a/version.py b/version.py new file mode 100644 index 0000000..aa4bd49 --- /dev/null +++ b/version.py @@ -0,0 +1,313 @@ +from __future__ import print_function +import datetime +import warnings +import re +from itertools import chain, repeat, islice + +from .config import Configuration +from .utils import trace, string_types, utc + +from pkg_resources import iter_entry_points + +from pkg_resources import parse_version as pkg_parse_version + +SEMVER_MINOR = 2 +SEMVER_PATCH = 3 +SEMVER_LEN = 3 + + +def _pad(iterable, size, padding=None): + padded = chain(iterable, repeat(padding)) + return list(islice(padded, size)) + + +def _parse_version_tag(tag, config): + tagstring = tag if not isinstance(tag, string_types) else str(tag) + match = config.tag_regex.match(tagstring) + + result = None + if match: + if len(match.groups()) == 1: + key = 1 + else: + key = "version" + + result = { + "version": match.group(key), + "prefix": match.group(0)[: match.start(key)], + "suffix": match.group(0)[match.end(key) :], + } + + trace("tag '{}' parsed to {}".format(tag, result)) + return result + + +def _get_version_class(): + modern_version = pkg_parse_version("1.0") + if isinstance(modern_version, tuple): + return None + else: + return type(modern_version) + + +VERSION_CLASS = _get_version_class() + + +class SetuptoolsOutdatedWarning(Warning): + pass + + +# append so integrators can disable the warning +warnings.simplefilter("error", SetuptoolsOutdatedWarning, append=True) + + +def _warn_if_setuptools_outdated(): + if VERSION_CLASS is None: + warnings.warn("your setuptools is too old (<12)", SetuptoolsOutdatedWarning) + + +def callable_or_entrypoint(group, callable_or_name): + trace("ep", (group, callable_or_name)) + + if callable(callable_or_name): + return callable_or_name + + for ep in iter_entry_points(group, callable_or_name): + trace("ep found:", ep.name) + return ep.load() + + +def tag_to_version(tag, config=None): + """ + take a tag that might be prefixed with a keyword and return only the version part + :param config: optional configuration object + """ + trace("tag", tag) + + if not config: + config = Configuration() + + tagdict = _parse_version_tag(tag, config) + if not isinstance(tagdict, dict) or not tagdict.get("version", None): + warnings.warn("tag {!r} no version found".format(tag)) + return None + + version = tagdict["version"] + trace("version pre parse", version) + + if tagdict.get("suffix", ""): + warnings.warn( + "tag {!r} will be stripped of its suffix '{}'".format( + tag, tagdict["suffix"] + ) + ) + + if VERSION_CLASS is not None: + version = pkg_parse_version(version) + trace("version", repr(version)) + + return version + + +def tags_to_versions(tags, config=None): + """ + take tags that might be prefixed with a keyword and return only the version part + :param tags: an iterable of tags + :param config: optional configuration object + """ + result = [] + for tag in tags: + tag = tag_to_version(tag, config=config) + if tag: + result.append(tag) + return result + + +class ScmVersion(object): + def __init__( + self, + tag_version, + distance=None, + node=None, + dirty=False, + preformatted=False, + branch=None, + **kw + ): + if kw: + trace("unknown args", kw) + self.tag = tag_version + if dirty and distance is None: + distance = 0 + self.distance = distance + self.node = node + self.time = datetime.datetime.now(utc) + self._extra = kw + self.dirty = dirty + self.preformatted = preformatted + self.branch = branch + + @property + def extra(self): + warnings.warn( + "ScmVersion.extra is deprecated and will be removed in future", + category=DeprecationWarning, + stacklevel=2, + ) + return self._extra + + @property + def exact(self): + return self.distance is None + + def __repr__(self): + return self.format_with( + "<ScmVersion {tag} d={distance} n={node} d={dirty} b={branch}>" + ) + + def format_with(self, fmt, **kw): + return fmt.format( + time=self.time, + tag=self.tag, + distance=self.distance, + node=self.node, + dirty=self.dirty, + branch=self.branch, + **kw + ) + + def format_choice(self, clean_format, dirty_format, **kw): + return self.format_with(dirty_format if self.dirty else clean_format, **kw) + + def format_next_version(self, guess_next, fmt="{guessed}.dev{distance}", **kw): + guessed = guess_next(self.tag, **kw) + return self.format_with(fmt, guessed=guessed) + + +def _parse_tag(tag, preformatted, config): + if preformatted: + return tag + if VERSION_CLASS is None or not isinstance(tag, VERSION_CLASS): + tag = tag_to_version(tag, config) + return tag + + +def meta( + tag, distance=None, dirty=False, node=None, preformatted=False, config=None, **kw +): + if not config: + warnings.warn( + "meta invoked without explicit configuration," + " will use defaults where required." + ) + parsed_version = _parse_tag(tag, preformatted, config) + trace("version", tag, "->", parsed_version) + assert parsed_version is not None, "cant parse version %s" % tag + return ScmVersion(parsed_version, distance, node, dirty, preformatted, **kw) + + +def guess_next_version(tag_version): + version = _strip_local(str(tag_version)) + return _bump_dev(version) or _bump_regex(version) + + +def _strip_local(version_string): + public, sep, local = version_string.partition("+") + return public + + +def _bump_dev(version): + if ".dev" not in version: + return + + prefix, tail = version.rsplit(".dev", 1) + assert tail == "0", "own dev numbers are unsupported" + return prefix + + +def _bump_regex(version): + prefix, tail = re.match(r"(.*?)(\d+)$", version).groups() + return "%s%d" % (prefix, int(tail) + 1) + + +def guess_next_dev_version(version): + if version.exact: + return version.format_with("{tag}") + else: + return version.format_next_version(guess_next_version) + + +def guess_next_simple_semver(version, retain, increment=True): + parts = map(int, str(version).split(".")) + parts = _pad(parts, retain, 0) + if increment: + parts[-1] += 1 + parts = _pad(parts, SEMVER_LEN, 0) + return ".".join(map(str, parts)) + + +def simplified_semver_version(version): + if version.exact: + return guess_next_simple_semver(version.tag, retain=SEMVER_LEN, increment=False) + else: + if version.branch is not None and "feature" in version.branch: + return version.format_next_version( + guess_next_simple_semver, retain=SEMVER_MINOR + ) + else: + return version.format_next_version( + guess_next_simple_semver, retain=SEMVER_PATCH + ) + + +def _format_local_with_time(version, time_format): + + if version.exact or version.node is None: + return version.format_choice( + "", "+d{time:{time_format}}", time_format=time_format + ) + else: + return version.format_choice( + "+{node}", "+{node}.d{time:{time_format}}", time_format=time_format + ) + + +def get_local_node_and_date(version): + return _format_local_with_time(version, time_format="%Y%m%d") + + +def get_local_node_and_timestamp(version, fmt="%Y%m%d%H%M%S"): + return _format_local_with_time(version, time_format=fmt) + + +def get_local_dirty_tag(version): + return version.format_choice("", "+dirty") + + +def get_no_local_node(_): + return "" + + +def postrelease_version(version): + if version.exact: + return version.format_with("{tag}") + else: + return version.format_with("{tag}.post{distance}") + + +def format_version(version, **config): + trace("scm version", version) + trace("config", config) + if version.preformatted: + return version.tag + version_scheme = callable_or_entrypoint( + "setuptools_scm.version_scheme", config["version_scheme"] + ) + local_scheme = callable_or_entrypoint( + "setuptools_scm.local_scheme", config["local_scheme"] + ) + main_version = version_scheme(version) + trace("version", main_version) + local_version = local_scheme(version) + trace("local_version", local_version) + return version_scheme(version) + local_scheme(version) |