diff --git a/rust2rpm/cfg.py b/rust2rpm/cfg.py new file mode 100644 index 0000000..efccf87 --- /dev/null +++ b/rust2rpm/cfg.py @@ -0,0 +1,113 @@ +import ast +import ctypes +import functools +import platform +import sys + +import pyparsing as pp + +pp.ParserElement.enable_packrat() + +# ConfigurationPredicate : +# ConfigurationOption +# | ConfigurationAll +# | ConfigurationAny +# | ConfigurationNot +# ConfigurationOption : +# IDENTIFIER (= (STRING_LITERAL | RAW_STRING_LITERAL))? +# ConfigurationAll +# all ( ConfigurationPredicateList? ) +# ConfigurationAny +# any ( ConfigurationPredicateList? ) +# ConfigurationNot +# not ( ConfigurationPredicate ) +# ConfigurationPredicateList +# ConfigurationPredicate (, ConfigurationPredicate)* ,? + +# cfg(target_os = "macos") +# cfg(any(foo, bar)) +# cfg(all(unix, target_pointer_width = "32")) +# cfg(not(foo)) + +def _call(word, arg): + return pp.Group(pp.Literal(word) + pp.Suppress('(') + arg + pp.Suppress(')'), aslist=True) + +@functools.cache +def cfg_grammar(): + pred = pp.Forward() + + ident = pp.Word(pp.alphas + '_', pp.alphanums + '_') + option = pp.Group(ident + pp.Optional(pp.Suppress('=') + pp.quotedString), aslist=True) + + not_ = _call('not', pred) + + # pp.pyparsing_common.comma_separated_list? + # any_ = _call('any', pp.pyparsing_common.comma_separated_list(pred)) + # all_ = _call('all', pp.pyparsing_common.comma_separated_list(pred)) + # all_ = _call('all', pp.delimited_list(pred)) + + any_ = _call('any', pred + pp.ZeroOrMore(pp.Suppress(',') + pred)) + all_ = _call('all', pred + pp.ZeroOrMore(pp.Suppress(',') + pred)) + + pred <<= not_ | any_ | all_ | option + + grammar = _call('cfg', pred) + return grammar + +@functools.cache +def evaluate_variable(name): + # print(f'evaluate_variable: {expr}') + match name: + case 'target_arch': + return platform.machine() + + case 'target_os': + return 'linux' + + case 'target_family': + return 'unix' + + case 'unix': + return evaluate_variable('target_family') == 'unix' + + case 'windows': + return evaluate_variable('target_family') == 'windows' + + case 'target_env': + # Key-value option set with further disambiguating information about the + # target platform with information about the ABI or libc used + return ... + + case 'target_endian': + return sys.byteorder + + case 'target_pointer_width': + return str(ctypes.sizeof(ctypes.c_voidp) * 8) + + case 'target_vendor': + return 'unknown' + + case _: + print(f'Unknown variable {name}, assuming False') + return False + +def evaluate(expr, nested=False): + # print(f'evaluate: {expr}') + match expr: + case ['cfg', expr] if not nested: + return evaluate(expr, True) + case ['not', expr] if nested: + return not evaluate(expr, True) + case ['all', *args] if nested: + return all(evaluate(arg, True) for arg in args) + case ['any', *args] if nested: + 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 + case [variable] if nested: + x = evaluate_variable(variable) + return x + case _: + raise ValueError diff --git a/rust2rpm/tests/test_cfg.py b/rust2rpm/tests/test_cfg.py new file mode 100644 index 0000000..ea89b6b --- /dev/null +++ b/rust2rpm/tests/test_cfg.py @@ -0,0 +1,60 @@ +import pytest + +from rust2rpm import cfg + +def test_pyparsing_run_tests(): + g = cfg.cfg_grammar() + + g.run_tests("""\ + cfg(target_os = "macos") + cfg(any(foo, bar)) + cfg(all(unix, target_pointer_width = "32")) + cfg(not(foo)) + """) + +@pytest.mark.parametrize('expr, expected', [ + ('cfg(target_os = "macos")', + False), + ('cfg(any(foo, bar))', + False), + ('cfg(all(unix, target_pointer_width = "16"))', + False), + ('cfg(not(foo))', + True), + + ('cfg(unix)', + True), + ('cfg(not(unix))', + False), + ('cfg(windows)', + False), + ('cfg(linux)', # not defined + False), + ('cfg(not(windows))', + True), + ('cfg(any(unix, windows))', + True), + ('cfg(any(windows, unix))', + True), + ('cfg(any(windows, windows, windows))', + False), + + ('cfg(target_os = "linux")', + True), + ('cfg(any(target_os = "linux"))', + True), + ('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): + parsed = cfg.cfg_grammar().parse_string(expr) + value = cfg.evaluate(parsed[0]) + assert value == expected