Skip to content

refactor: split tracing and command running into own modules #808

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Mar 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ repos:
- id: pyproject-fmt

- repo: https://github.com/pre-commit/mirrors-mypy
rev: 'v1.0.0'
rev: 'v1.0.1'
hooks:
- id: mypy
args: [--strict]
Expand All @@ -44,3 +44,4 @@ repos:
- tokenize-rt==3.2.0
- pytest == 7.1
- importlib_metadata
- typing-extensions>=4.5
1 change: 1 addition & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ include mypy.ini
include testing/Dockerfile.*
include src/setuptools_scm/.git_archival.txt
recursive-include testing *.bash
prune nextgen
4 changes: 2 additions & 2 deletions src/setuptools_scm/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ def dump_version(
target = os.path.normpath(os.path.join(root, write_to))
ext = os.path.splitext(target)[1]
template = template or TEMPLATES.get(ext)
from .utils import trace
from ._trace import trace

trace("dump", write_to, version)
if template is None:
Expand Down Expand Up @@ -106,7 +106,7 @@ def _version_missing(config: Configuration) -> NoReturn:


def get_version(
root: str = ".",
root: _t.PathT = ".",
version_scheme: _t.VERSION_SCHEME = DEFAULT_VERSION_SCHEME,
local_scheme: _t.VERSION_SCHEME = DEFAULT_LOCAL_SCHEME,
write_to: _t.PathT | None = None,
Expand Down
2 changes: 1 addition & 1 deletion src/setuptools_scm/_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@
)
from ._integration.pyproject_reading import read_pyproject as _read_pyproject
from ._overrides import read_toml_overrides
from ._trace import trace
from ._version_cls import _validate_version_cls
from ._version_cls import _VersionT
from ._version_cls import Version as _Version
from .utils import trace

DEFAULT_TAG_REGEX = re.compile(
r"^(?:[\w-]+-)?(?P<version>[vV]?\d+(?:\.\d+){0,2}[^\+]*)(?:\+.*)?$"
Expand Down
2 changes: 1 addition & 1 deletion src/setuptools_scm/_entrypoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from typing import TYPE_CHECKING

from . import version
from .utils import trace
from ._trace import trace

if TYPE_CHECKING:
from ._config import Configuration
Expand Down
2 changes: 1 addition & 1 deletion src/setuptools_scm/_overrides.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from . import _config
from . import version
from ._integration.pyproject_reading import lazy_toml_load
from .utils import trace
from ._trace import trace

PRETEND_KEY = "SETUPTOOLS_SCM_PRETEND_VERSION"
PRETEND_KEY_NAMED = PRETEND_KEY + "_FOR_{name}"
Expand Down
104 changes: 104 additions & 0 deletions src/setuptools_scm/_run_cmd.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
from __future__ import annotations

import os
import shlex
import subprocess
from typing import Mapping

from . import _trace
from . import _types as _t


def no_git_env(env: Mapping[str, str]) -> dict[str, str]:
# 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.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 avoid_pip_isolation(env: Mapping[str, str]) -> dict[str, str]:
"""
pip build isolation can break Mercurial
(see https://github.com/pypa/pip/issues/10635)

pip uses PYTHONNOUSERSITE and a path in PYTHONPATH containing "pip-build-env-".
"""
new_env = {k: v for k, v in env.items() if k != "PYTHONNOUSERSITE"}
if "PYTHONPATH" not in new_env:
return new_env

new_env["PYTHONPATH"] = os.pathsep.join(
[
path
for path in new_env["PYTHONPATH"].split(os.pathsep)
if "pip-build-env-" not in path
]
)
return new_env


def ensure_stripped_str(str_or_bytes: str | bytes) -> str:
if isinstance(str_or_bytes, str):
return str_or_bytes.strip()
else:
return str_or_bytes.decode("utf-8", "surrogateescape").strip()


def run(
cmd: _t.CMD_TYPE,
cwd: _t.PathT,
*,
strip: bool = True,
trace: bool = True,
timeout: int = 20,
check: bool = False,
) -> subprocess.CompletedProcess[str]:
if isinstance(cmd, str):
cmd = shlex.split(cmd)
else:
cmd = [os.fspath(x) for x in cmd]
if trace:
_trace.trace_command(cmd, cwd)
res = subprocess.run(
cmd,
capture_output=True,
cwd=str(cwd),
env=dict(
avoid_pip_isolation(no_git_env(os.environ)),
# os.environ,
# try to disable i18n
LC_ALL="C",
LANGUAGE="",
HGPLAIN="1",
),
text=True,
timeout=timeout,
)
if strip:
if res.stdout:
res.stdout = ensure_stripped_str(res.stdout)
res.stderr = ensure_stripped_str(res.stderr)
if trace:
if res.stdout:
_trace.trace("out:\n", res.stdout, indent=True)
if res.stderr:
_trace.trace("err:\n", res.stderr, indent=True)
if res.returncode:
_trace.trace("ret:", res.returncode)
if check:
res.check_returncode()
return res
30 changes: 30 additions & 0 deletions src/setuptools_scm/_trace.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from __future__ import annotations

import os
import sys
import textwrap
from typing import Sequence

from . import _types as _t

DEBUG: bool = bool(os.environ.get("SETUPTOOLS_SCM_DEBUG"))


def trace(*k: object, indent: bool = False) -> None:
if not DEBUG:
if indent and len(k) > 1:
k = (k[0],) + tuple(textwrap.indent(str(s), " ") for s in k[1:])
print(*k, file=sys.stderr, flush=True)


def _unsafe_quote_for_display(item: _t.PathT) -> str:
# give better results than shlex.join in our cases
text = os.fspath(item)
return text if all(c not in text for c in " {[:") else f'"{text}"'


def trace_command(cmd: Sequence[_t.PathT], cwd: _t.PathT) -> None:
if not DEBUG:
return
cmd_4_trace = " ".join(map(_unsafe_quote_for_display, cmd))
trace(f"---\n > {cwd}\\$ ", cmd_4_trace, indent=True)
3 changes: 2 additions & 1 deletion src/setuptools_scm/_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from typing import Any
from typing import Callable
from typing import List
from typing import Sequence
from typing import Tuple
from typing import TypeVar
from typing import Union
Expand All @@ -16,7 +17,7 @@

PathT: TypeAlias = Union["os.PathLike[str]", str]

CMD_TYPE: TypeAlias = Union[List[str], str]
CMD_TYPE: TypeAlias = Union[Sequence[PathT], str]

VERSION_SCHEME: TypeAlias = Union[str, Callable[["version.ScmVersion"], str]]
VERSION_SCHEMES: TypeAlias = Union[List[str], Tuple[str, ...], VERSION_SCHEME]
Expand Down
2 changes: 1 addition & 1 deletion src/setuptools_scm/discover.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

from . import _types as _t
from ._config import Configuration
from .utils import trace
from ._trace import trace


def walk_potential_roots(
Expand Down
2 changes: 1 addition & 1 deletion src/setuptools_scm/file_finder.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from typing_extensions import TypeGuard

from . import _types as _t
from .utils import trace
from ._trace import trace


def scm_find_files(
Expand Down
16 changes: 7 additions & 9 deletions src/setuptools_scm/file_finder_git.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,31 +7,30 @@
from typing import IO

from . import _types as _t
from ._run_cmd import run as _run
from ._trace import trace
from .file_finder import is_toplevel_acceptable
from .file_finder import scm_find_files
from .utils import data_from_mime
from .utils import do_ex
from .utils import trace


log = logging.getLogger(__name__)


def _git_toplevel(path: str) -> str | None:
try:
cwd = os.path.abspath(path or ".")
out, err, ret = do_ex(["git", "rev-parse", "HEAD"], cwd=cwd)
if ret != 0:
res = _run(["git", "rev-parse", "HEAD"], cwd=cwd)
if res.returncode:
# BAIL if there is no commit
log.error("listing git files failed - pretending there aren't any")
return None
out, err, ret = do_ex(
res = _run(
["git", "rev-parse", "--show-prefix"],
cwd=cwd,
)
if ret != 0:
if res.returncode:
return None
out = out.strip()[:-1] # remove the trailing pathsep
out = res.stdout[:-1] # remove the trailing pathsep
if not out:
out = cwd
else:
Expand Down Expand Up @@ -93,7 +92,6 @@ def git_find_files(path: _t.PathT = "") -> list[str]:
toplevel = _git_toplevel(os.fspath(path))
if not is_toplevel_acceptable(toplevel):
return []
assert toplevel is not None # mypy ignores typeguard
fullpath = os.path.abspath(os.path.normpath(path))
if not fullpath.startswith(toplevel):
trace("toplevel mismatch", toplevel, fullpath)
Expand Down
10 changes: 5 additions & 5 deletions src/setuptools_scm/file_finder_hg.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,22 @@
import subprocess

from . import _types as _t
from ._run_cmd import run as _run
from ._trace import trace
from .file_finder import is_toplevel_acceptable
from .file_finder import scm_find_files
from .utils import data_from_mime
from .utils import do_ex
from .utils import trace


def _hg_toplevel(path: str) -> str | None:
try:
out: str = subprocess.check_output(
res = _run(
["hg", "root"],
cwd=(path or "."),
text=True,
stderr=subprocess.DEVNULL,
)
return os.path.normcase(os.path.realpath(out.strip()))
res.check_returncode()
return os.path.normcase(os.path.realpath(res.stdout))
except subprocess.CalledProcessError:
# hg returned error, we are not in a mercurial repo
return None
Expand Down
2 changes: 1 addition & 1 deletion src/setuptools_scm/git.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@

from . import _types as _t
from . import Configuration
from ._trace import trace
from .scm_workdir import Workdir
from .utils import _CmdResult
from .utils import data_from_mime
from .utils import do_ex
from .utils import require_command
from .utils import trace
from .version import meta
from .version import ScmVersion
from .version import tag_to_version
Expand Down
2 changes: 1 addition & 1 deletion src/setuptools_scm/hacks.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from . import _types as _t
from . import Configuration
from .utils import data_from_mime
from .utils import trace
from ._trace import trace
from .version import meta
from .version import ScmVersion
from .version import tag_to_version
Expand Down
Loading