Coverage for rust2rpm/utils.py: 26%
104 statements
« prev ^ index » next coverage.py v7.6.4, created at 2024-10-27 15:21 +0100
« prev ^ index » next coverage.py v7.6.4, created at 2024-10-27 15:21 +0100
1import contextlib
2import glob
3import os
4from pathlib import Path
5import re
6import sys
7import shlex
8import shutil
9import subprocess
10import requests
11from typing import Optional
13from cargo2rpm.semver import Version
15from rust2rpm import log
18DEFAULT_EDITOR = "nano"
21@contextlib.contextmanager
22def remove_on_error(path):
23 try:
24 yield
25 except: # this is supposed to include ^C
26 os.unlink(path)
27 raise
30@contextlib.contextmanager
31def exit_on_common_errors():
32 """Suppress tracebacks on common "expected" exceptions"""
33 try:
34 yield
35 except requests.exceptions.HTTPError as e:
36 sys.exit(f"Failed to download metadata: {e}")
37 except subprocess.CalledProcessError as e:
38 cmd = shlex.join(e.cmd)
39 sys.exit(f"Subcommand failed with code {e.returncode}: {cmd}")
40 except FileNotFoundError as e:
41 sys.exit(str(e))
44def detect_editor() -> str:
45 terminal = os.getenv("TERM")
46 terminal_is_dumb = not terminal or terminal == "dumb"
47 editor = None
48 if not terminal_is_dumb:
49 editor = os.getenv("VISUAL")
50 if not editor:
51 editor = os.getenv("EDITOR")
52 if not editor:
53 if terminal_is_dumb:
54 raise Exception("Terminal is dumb, but $EDITOR unset")
55 editor = DEFAULT_EDITOR
56 return editor
59def detect_packager() -> Optional[str]:
60 # If we're forcing the fallback...
61 if os.getenv("RUST2RPM_NO_DETECT_PACKAGER"):
62 return None
64 # If we're supplying packager identity through an environment variable...
65 if packager := os.getenv("RUST2RPM_PACKAGER"):
66 return packager
68 # If we're detecting packager identity through rpmdev-packager...
69 if rpmdev_packager := shutil.which("rpmdev-packager"):
70 return subprocess.check_output(rpmdev_packager, text=True).strip()
72 # If we're detecting packager identity through git configuration...
73 if git := shutil.which("git"):
74 name = subprocess.check_output([git, "config", "user.name"], text=True).strip()
75 email = subprocess.check_output([git, "config", "user.email"], text=True).strip()
76 return f"{name} <{email}>"
78 return None
81def guess_crate_name() -> Optional[str]:
82 """Guess crate name from directory name and/or spec file name
84 If a spec file is present, we use the %crate variable. This is the best
85 option. But if we're in a new directory, and don't have a spec file yet,
86 let's assume that this is a dist-git directory and extract the crate name.
87 For compat packages, the directory name will contain a version suffix,
88 but a compat package would almost always be created from an existing
89 dist-git directory, hence there'd be a spec file, so we can ignore this.
90 """
91 specs = glob.glob("*.spec")
93 if len(specs) > 1:
94 log.error(
95 "Found multiple spec files in the current working directory; "
96 + "unable to determine crate name automatically."
97 )
98 return None
100 if len(specs) == 1:
101 crate = None
102 spec = specs[0]
104 for line in open(spec):
105 if m := re.match(r"^%(?:global|define)\s+crate\s+(\S+)\s+", line):
106 if crate:
107 log.error(
108 f"Found multiple definitions of the '%crate' macro in {spec!r}; "
109 + "unable to determine crate name automatically."
110 )
111 return None
112 crate = m.group(1)
113 if "%" in crate:
114 log.error("The value of the %crate macro appears to contain other macros and cannot be parsed.")
115 return None
116 if crate:
117 log.success(f"Found valid spec file {spec!r} for the {crate!r} crate.")
118 else:
119 log.error(f"Invalid spec file {spec!r}; unable to determine crate name automatically.")
120 return crate
122 dirname = os.path.basename(os.getcwd())
123 if m := re.match("^rust-([a-z+0-9_-]+)$", dirname):
124 crate = m.group(1)
125 log.info(f"Continuing with crate name {crate!r} based on the current working directory.")
126 return crate
128 return None
131def package_name_suffixed(name: str, suffix: Optional[str]):
132 joiner = "_" if suffix and name[-1].isdigit() else ""
133 return "rust-" + name + joiner + (suffix or "")
136def package_name_compat(name: str, version: str):
137 sv = Version.parse(version)
138 if sv.major > 0:
139 suffix = str(sv.major)
140 elif sv.minor > 0:
141 suffix = f"0.{str(sv.minor)}"
142 else:
143 suffix = f"0.0.{str(sv.patch)}"
144 return package_name_suffixed(name, suffix)
147def detect_rpmautospec(default_target: str, spec_file: Path):
148 """Guess whether %autorelease+%autochangelog should be used
150 Returns False if we're not on Fedora or if the spec file exists and
151 wasn't using rpmautospec already.
152 """
154 # We default to on only for selected distros for now…
155 if default_target not in {"fedora", "epel8"}:
156 return False
158 try:
159 text = spec_file.read_text()
160 except FileNotFoundError:
161 # A new file, let's try the new thing.
162 return True
164 # We are redoing an existing spec file. Figure out if it had
165 # %autochangelog enabled before.
166 autochangelog_re = r"^\s*%(?:autochangelog|\{\??autochangelog\})\b"
167 return any(re.match(autochangelog_re, line) for line in text.splitlines())