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 ctypes
import functools
import platform
import sys
import pyparsing as pp
from pyparsing import ParseException
@ -63,43 +60,69 @@ def cfg_grammar():
@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:
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":
return "linux"
return value == "linux"
case "target_family":
return "unix"
case "unix":
return evaluate_variable("target_family") == "unix"
case "windows":
return evaluate_variable("target_family") == "windows"
return value == "unix"
case "target_env":
# Key-value option set with further disambiguating information about the
# target platform with information about the ABI or libc used
return ...
# The "target_env" predicate is used to disambiguate target
# platforms based on its C library / C ABI (i.e. we can ignore
# "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":
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":
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":
return "unknown"
# On linux systems, "target_vendor" is always "unknown".
return value == "unknown"
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
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"):
expr = expr.asList() # compat with pyparsing 2.7.x
match expr:
@ -113,11 +136,9 @@ def evaluate(expr, nested=False):
return any(evaluate(arg, True) for arg in args)
case [variable, value] if nested:
v = ast.literal_eval(value)
x = evaluate_variable(variable)
return x == v
return evaluate_predicate(variable, v)
case [variable] if nested:
x = evaluate_variable(variable)
return x
return evaluate_atom(variable)
case _:
raise ValueError

View file

@ -21,7 +21,7 @@ def test_pyparsing_run_tests():
[
('cfg(target_os = "macos")', 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(unix)", True),
("cfg(not(unix))", False),
@ -36,7 +36,6 @@ def test_pyparsing_run_tests():
('cfg(all(target_os = "linux"))', 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(all(target_pointer_width = "16", target_pointer_width = "32", target_pointer_width = "64"))', False),
],
)
def test_expressions(expr, expected):