Ensure rust2rpm doesn't autogenerate architecture-dependent patches

The logic in the cfg-expression evaluator checked *too many cases*,
some of which would have resulted in generation of broken patches
and packages. We need to ensure that the evaluation is independent
of the host architecture (i.e. we need to keep *all* dependencies
that are valid on *any* of our build targets, even if they end up
being unused on *some* of them).
This commit is contained in:
Fabio Valentini 2022-07-25 12:09:43 +02:00
parent cc4e5fd341
commit f0d04b09e8
No known key found for this signature in database
GPG key ID: 5AC5F572E5D410AF
2 changed files with 47 additions and 27 deletions

View file

@ -1,8 +1,5 @@
import ast import ast
import ctypes
import functools import functools
import platform
import sys
import pyparsing as pp import pyparsing as pp
from pyparsing import ParseException from pyparsing import ParseException
@ -63,43 +60,69 @@ def cfg_grammar():
@functools.cache @functools.cache
def evaluate_variable(name): def evaluate_predicate(name: str, value: str) -> bool:
# based on: https://doc.rust-lang.org/reference/conditional-compilation.html
match name: match name:
case "target_arch": case "target_arch":
return platform.machine() # Needs to be ignored, as we cannot generate patches that are
# different depending on the host architecture - except if the
# target architecture is "wasm32", which we don't support.
return value != "wasm32"
case "target_feature":
# The "target_feature" predicate can be ignored as well, since the
# valid values for this predicate are architecture-dependent.
return True
case "target_os": case "target_os":
return "linux" return value == "linux"
case "target_family": case "target_family":
return "unix" return value == "unix"
case "unix":
return evaluate_variable("target_family") == "unix"
case "windows":
return evaluate_variable("target_family") == "windows"
case "target_env": case "target_env":
# Key-value option set with further disambiguating information about the # The "target_env" predicate is used to disambiguate target
# target platform with information about the ABI or libc used # platforms based on its C library / C ABI (i.e. we can ignore
return ... # "msvc" and "musl"), and if there's no need to disambiguate, the
# value can be the empty string.
return value in ["", "gnu"]
case "target_endian": case "target_endian":
return sys.byteorder # Needs to be ignored, as we cannot generate patches that are
# different depending on the host architecture.
return True
case "target_pointer_width": case "target_pointer_width":
return str(ctypes.sizeof(ctypes.c_void_p) * 8) # Needs to be ignored, as we cannot generate patches that are
# different depending on the host architecture.
return True
case "target_vendor": case "target_vendor":
return "unknown" # On linux systems, "target_vendor" is always "unknown".
return value == "unknown"
case _: case _:
log.warn(f"Ignoring unknown variable {name!r} in cfg-expression.") log.warn(f'Ignoring invalid predicate \'"{name}" = "{value}"\' in cfg-expression.')
return False return False
def evaluate(expr, nested=False): @functools.cache
def evaluate_atom(name: str) -> bool:
match name:
case "unix":
return True
case "windows":
return False
case _:
log.warn(
f"Ignoring invalid atom {name!r} in cfg-expression. "
+ 'Only "unix" and "windows" are valid names for atoms.'
)
return False
def evaluate(expr, nested=False) -> bool:
if hasattr(expr, "asList"): if hasattr(expr, "asList"):
expr = expr.asList() # compat with pyparsing 2.7.x expr = expr.asList() # compat with pyparsing 2.7.x
match expr: match expr:
@ -113,11 +136,9 @@ def evaluate(expr, nested=False):
return any(evaluate(arg, True) for arg in args) return any(evaluate(arg, True) for arg in args)
case [variable, value] if nested: case [variable, value] if nested:
v = ast.literal_eval(value) v = ast.literal_eval(value)
x = evaluate_variable(variable) return evaluate_predicate(variable, v)
return x == v
case [variable] if nested: case [variable] if nested:
x = evaluate_variable(variable) return evaluate_atom(variable)
return x
case _: case _:
raise ValueError raise ValueError

View file

@ -21,7 +21,7 @@ def test_pyparsing_run_tests():
[ [
('cfg(target_os = "macos")', False), ('cfg(target_os = "macos")', False),
("cfg(any(foo, bar))", False), ("cfg(any(foo, bar))", False),
('cfg(all(unix, target_pointer_width = "16"))', False), ('cfg(all(unix, target_pointer_width = "16"))', True),
("cfg(not(foo))", True), ("cfg(not(foo))", True),
("cfg(unix)", True), ("cfg(unix)", True),
("cfg(not(unix))", False), ("cfg(not(unix))", False),
@ -36,7 +36,6 @@ def test_pyparsing_run_tests():
('cfg(all(target_os = "linux"))', True), ('cfg(all(target_os = "linux"))', True),
('cfg(any(target_os = "linux", target_os = "macos"))', True), ('cfg(any(target_os = "linux", target_os = "macos"))', True),
('cfg(any(target_pointer_width = "16", target_pointer_width = "32", target_pointer_width = "64"))', True), ('cfg(any(target_pointer_width = "16", target_pointer_width = "32", target_pointer_width = "64"))', True),
('cfg(all(target_pointer_width = "16", target_pointer_width = "32", target_pointer_width = "64"))', False),
], ],
) )
def test_expressions(expr, expected): def test_expressions(expr, expected):