Skip to content
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
44 changes: 26 additions & 18 deletions pyrpm/spec.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@
"""Python module for parsing RPM spec files.

RPMs are build from a package's sources along with a spec file. The spec file controls how the RPM
is built. This module allows you to parse spec files and gives you simple access to various bits of
information that is contained in the spec file.

Current status: This module does not parse everything of a spec file. Only the pieces I needed. So
there is probably still plenty of stuff missing. However, it should not be terribly complicated to
add support for the missing pieces.
This module allows to parse RPM spec files and gives simple access to various bits of information contained in the spec file.

"""

Expand Down Expand Up @@ -125,13 +119,27 @@ def __init__(self, name: str, pattern_obj: re.Pattern) -> None:

def update_impl(self, spec_obj: "Spec", context: Dict[str, Any], match_obj: re.Match, line: str) -> Tuple["Spec", dict]:
name, value = match_obj.groups()
spec_obj.macros[name] = str(value)
raw_value = str(value)
stored_value = raw_value
if _macro_references_itself(raw_value, name):
try:
stored_value = replace_macros(raw_value, spec_obj)
except RuntimeError:
stored_value = raw_value
spec_obj.macros[name] = stored_value
if name not in _tag_names:
# Also make available as attribute of spec object
setattr(spec_obj, name, str(value))
setattr(spec_obj, name, stored_value)
return spec_obj, context


def _macro_references_itself(raw_value: str, macro_name: str) -> bool:
"""Check if a macro definition references itself."""
brace_pattern = rf"(?<!%)%{{{re.escape(macro_name)}}}"
bare_pattern = rf"(?<!%)%{re.escape(macro_name)}\b"
return bool(re.search(brace_pattern, raw_value) or re.search(bare_pattern, raw_value))


class _List(_Tag):
"""Parse a tag that expands to a list."""

Expand Down Expand Up @@ -332,8 +340,8 @@ def __init__(self, name: str) -> None:
assert isinstance(name, str)
self.line = name
self.name: str
self.operator: Optional[str]
self.version: Optional[str]
self.operator: str | None
self.version: str | None
match = Requirement.expr.match(name)
if match:
self.name = match.group(1)
Expand Down Expand Up @@ -436,9 +444,9 @@ def __init__(self) -> None:

self.sources_dict: Dict[str, str] = {}
self.patches_dict: Dict[str, str] = {}
self.macros: Dict[str, str] = {}
self.macros: Dict[str, str] = {"nil": ""}

self.name: Optional[str]
self.name: str | None
self.packages: List[Package] = []

@property
Expand Down Expand Up @@ -486,6 +494,9 @@ def from_string(cls, string: str) -> "Spec":
def replace_macros(string: str, spec: Spec, max_attempts: int = 1000) -> str:
"""Replace all macros in given string with corresponding values.

Note: If macros are not defined in the spec file, this won't try to
expand them.

For example, a string '%{name}-%{version}.tar.gz' will be transformed to 'foo-2.0.tar.gz'.

:param string A string containing macros that you want to be replaced.
Expand Down Expand Up @@ -537,18 +548,15 @@ def get_replacement_string(match: re.Match) -> str:
if len(parts) == 2:
return parts[1]

return spec.macros.get(macro, getattr(spec, macro))
return spec.macros.get(macro, getattr(spec, macro, ""))

if spec:
value = spec.macros.get(macro_name, getattr(spec, macro_name, None))
if value:
if value is not None:
return str(value)

return match.string[match.start() : match.end()]

# Recursively expand macros.
# Note: If macros are not defined in the spec file, this won't try to
# expand them.
attempt = 0
ret = ""
while attempt < max_attempts:
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ coverage==7.11.0
flit==3.12.0
mypy==1.18.2
pylint==4.0.2
pytest-asyncio==1.2.0
pytest-cov==7.0.0
pytest==8.4.2
rope==1.14.0
Loading