-
Notifications
You must be signed in to change notification settings - Fork 15
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement processing of conditions (#276)
Implement processing of conditions This is not a full support for conditions, i.e. it doesn't re-solve #231 where an entire section can be conditionalized (however, the new process_conditions() function is generic enough to be used as a base for the resolution of that issue), nevertheless I think it's quite an improvement. Note that macro definitions have to be parsed twice, because a macro definition can contain a condition (see https://src.fedoraproject.org/rpms/kernel/blob/rawhide/f/kernel.spec for examples), and vice versa, a condition can encapsulate a macro definition. Macro definitions and tags gained a valid attribute that can be used to determine if that particular macro definition/tag is valid and can affect other entities in the spec file or if it would be ignored when parsing the spec file with RPM. This will be used to fix Specfile.update_value() and Specfile.update_tag(), but it could be beneficial for other use cases as well. Related to packit/packit#2033. RELEASE NOTES BEGIN Macro definitions and tags gained a new valid attribute. A macro definition/tag is considered valid if it doesn't appear in a false branch of any condition appearing in the spec file. RELEASE NOTES END Reviewed-by: František Lachman <[email protected]> Reviewed-by: Nikola Forró
- Loading branch information
Showing
8 changed files
with
320 additions
and
14 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
# Copyright Contributors to the Packit project. | ||
# SPDX-License-Identifier: MIT | ||
|
||
import re | ||
from typing import TYPE_CHECKING, List, Optional, Tuple | ||
|
||
from specfile.exceptions import RPMException | ||
from specfile.macros import Macros | ||
|
||
if TYPE_CHECKING: | ||
from specfile.macro_definitions import MacroDefinitions | ||
from specfile.specfile import Specfile | ||
|
||
|
||
def resolve_expression( | ||
keyword: str, expression: str, context: Optional["Specfile"] = None | ||
) -> bool: | ||
""" | ||
Resolves a RPM expression. | ||
Args: | ||
keyword: Condition keyword, e.g. `%if` or `%ifarch`. | ||
expression: Expression string or a whitespace-delimited list | ||
of arches/OSes in case keyword is a variant of `%ifarch`/`%ifos`. | ||
context: `Specfile` instance that defines the context for macro expansions. | ||
Returns: | ||
Resolved expression as a boolean value. | ||
""" | ||
|
||
def expand(s): | ||
if not context: | ||
return Macros.expand(s) | ||
result = context.expand(s, skip_parsing=getattr(expand, "skip_parsing", False)) | ||
# parse only once | ||
expand.skip_parsing = True | ||
return result | ||
|
||
if keyword in ("%if", "%elif"): | ||
try: | ||
result = expand(f"%{{expr:{expression}}}") | ||
except RPMException: | ||
return False | ||
try: | ||
return int(result) != 0 | ||
except ValueError: | ||
return True | ||
elif keyword.endswith("arch"): | ||
target_cpu = expand("%{_target_cpu}") | ||
match = any(t for t in expression.split() if t == target_cpu) | ||
return not match if keyword == "%ifnarch" else match | ||
elif keyword.endswith("os"): | ||
target_os = expand("%{_target_os}") | ||
match = any(t for t in expression.split() if t == target_os) | ||
return not match if keyword == "%ifnos" else match | ||
return False | ||
|
||
|
||
def process_conditions( | ||
lines: List[str], | ||
macro_definitions: Optional["MacroDefinitions"] = None, | ||
context: Optional["Specfile"] = None, | ||
) -> List[Tuple[str, bool]]: | ||
""" | ||
Processes conditions in a spec file. Takes a list of lines and returns the same | ||
list of lines extended with information about their validity. A line is considered | ||
valid if it doesn't appear in a false branch of any condition. | ||
Args: | ||
lines: List of lines in a spec file. | ||
macro_definitions: Parsed macro definitions to be used to prevent parsing conditions | ||
inside their bodies (and most likely failing). | ||
context: `Specfile` instance that defines the context for macro expansions. | ||
Returns: | ||
List of tuples in the form of (line, validity). | ||
""" | ||
excluded_lines = [] | ||
for md in macro_definitions or []: | ||
position = md.get_position(macro_definitions) | ||
excluded_lines.append(range(position, position + len(md.body.splitlines()))) | ||
condition_regex = re.compile( | ||
r""" | ||
^ | ||
\s* # optional preceding whitespace | ||
(?P<kwd>%((el)?if(n?(arch|os))?|endif|else)) # keyword | ||
\s* | ||
( | ||
\s+ | ||
(?P<expr>.*?) # expression | ||
(?P<end>\s*|\\) # optional following whitespace | ||
# or a backslash indicating | ||
# that the expression continues | ||
# on the next line | ||
)? | ||
$ | ||
""", | ||
re.VERBOSE, | ||
) | ||
result = [] | ||
branches = [True] | ||
indexed_lines = list(enumerate(lines)) | ||
while indexed_lines: | ||
index, line = indexed_lines.pop(0) | ||
# ignore conditions inside macro definition body | ||
if any(index in r for r in excluded_lines): | ||
result.append((line, branches[-1])) | ||
continue | ||
m = condition_regex.match(line) | ||
if not m: | ||
result.append((line, branches[-1])) | ||
continue | ||
keyword = m.group("kwd") | ||
if keyword == "%endif": | ||
result.append((line, branches[-2])) | ||
branches.pop() | ||
elif keyword.startswith("%el"): | ||
result.append((line, branches[-2])) | ||
branches[-1] = not branches[-1] | ||
else: | ||
result.append((line, branches[-1])) | ||
expression = m.group("expr") | ||
if expression: | ||
if m.group("end") == "\\": | ||
expression += "\\" | ||
while expression.endswith("\\") and indexed_lines: | ||
_, line = indexed_lines.pop(0) | ||
result.append((line, branches[-1])) | ||
expression = expression[:-1] + line | ||
branch = ( | ||
False if not branches[-1] else resolve_expression(keyword, expression) | ||
) | ||
if keyword.startswith("%el"): | ||
branches[-1] = branch | ||
else: | ||
branches.append(branch) | ||
return result |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.