cargo: builder: Remove all duplicated functions

Keep only the Builder class, there is no point in duplicating
everything.
pull/12110/head
Xavier Claessens 2 years ago committed by Xavier Claessens
parent 013536fcb4
commit b5b952688f
  1. 218
      mesonbuild/cargo/builder.py
  2. 58
      mesonbuild/cargo/cfg.py
  3. 5
      mesonbuild/cargo/interpreter.py
  4. 102
      unittests/cargotests.py

@ -17,175 +17,26 @@ if T.TYPE_CHECKING:
import builtins
def _token(tid: str, filename: str, value: mparser.TV_TokenTypes) -> mparser.Token[mparser.TV_TokenTypes]:
"""Create a Token object, but with the line numbers stubbed out.
:param tid: the token id (such as string, number, etc)
:param filename: the filename that the token was generated from
:param value: the value of the token
:return: A Token object
"""
return mparser.Token(tid, filename, -1, -1, -1, (-1, -1), value)
def _symbol(filename: str, val: str) -> mparser.SymbolNode:
return mparser.SymbolNode(_token('', filename, val))
def string(value: str, filename: str) -> mparser.StringNode:
"""Build A StringNode
:param value: the value of the string
:param filename: the file that the value came from
:return: A StringNode
"""
return mparser.StringNode(_token('string', filename, value))
def number(value: int, filename: str) -> mparser.NumberNode:
"""Build A NumberNode
:param value: the value of the number
:param filename: the file that the value came from
:return: A NumberNode
"""
return mparser.NumberNode(_token('number', filename, str(value)))
def bool(value: builtins.bool, filename: str) -> mparser.BooleanNode:
"""Build A BooleanNode
:param value: the value of the boolean
:param filename: the file that the value came from
:return: A BooleanNode
"""
return mparser.BooleanNode(_token('bool', filename, value))
def array(value: T.List[mparser.BaseNode], filename: str) -> mparser.ArrayNode:
"""Build an Array Node
:param value: A list of nodes to insert into the array
:param filename: The file the array is from
:return: An ArrayNode built from the arguments
"""
args = mparser.ArgumentNode(_token('array', filename, 'unused'))
args.arguments = value
return mparser.ArrayNode(_symbol(filename, '['), args, _symbol(filename, ']'))
def identifier(value: str, filename: str) -> mparser.IdNode:
"""Build A IdNode
:param value: the value of the boolean
:param filename: the file that the value came from
:return: A BooleanNode
"""
return mparser.IdNode(_token('id', filename, value))
def method(name: str, id_: mparser.IdNode,
pos: T.Optional[T.List[mparser.BaseNode]] = None,
kw: T.Optional[T.Mapping[str, mparser.BaseNode]] = None,
) -> mparser.MethodNode:
"""Create a method call.
:param name: the name of the method
:param id_: the object to call the method of
:param pos: a list of positional arguments, defaults to None
:param kw: a dictionary of keyword arguments, defaults to None
:return: a method call object
"""
args = mparser.ArgumentNode(_token('array', id_.filename, 'unused'))
if pos is not None:
args.arguments = pos
if kw is not None:
args.kwargs = {identifier(k, id_.filename): v for k, v in kw.items()}
return mparser.MethodNode(id_, _symbol(id_.filename, '.'), identifier(name, id_.filename), _symbol(id_.filename, '('), args, _symbol(id_.filename, ')'))
def function(name: str, filename: str,
pos: T.Optional[T.List[mparser.BaseNode]] = None,
kw: T.Optional[T.Mapping[str, mparser.BaseNode]] = None,
) -> mparser.FunctionNode:
"""Create a function call.
:param name: the name of the function
:param filename: The name of the current file being evaluated
:param pos: a list of positional arguments, defaults to None
:param kw: a dictionary of keyword arguments, defaults to None
:return: a method call object
"""
args = mparser.ArgumentNode(_token('array', filename, 'unused'))
if pos is not None:
args.arguments = pos
if kw is not None:
args.kwargs = {identifier(k, filename): v for k, v in kw.items()}
return mparser.FunctionNode(identifier(name, filename), _symbol(filename, '('), args, _symbol(filename, ')'))
def equal(lhs: mparser.BaseNode, rhs: mparser.BaseNode) -> mparser.ComparisonNode:
"""Create an equality operation
:param lhs: The left hand side of the equal
:param rhs: the right hand side of the equal
:return: A compraison node
"""
return mparser.ComparisonNode('==', lhs, _symbol(lhs.filename, '=='), rhs)
def or_(lhs: mparser.BaseNode, rhs: mparser.BaseNode) -> mparser.OrNode:
"""Create and OrNode
:param lhs: The Left of the Node
:param rhs: The Right of the Node
:return: The OrNode
"""
return mparser.OrNode(lhs, _symbol(lhs.filename, 'or'), rhs)
def and_(lhs: mparser.BaseNode, rhs: mparser.BaseNode) -> mparser.AndNode:
"""Create an AndNode
:param lhs: The left of the And
:param rhs: The right of the And
:return: The AndNode
"""
return mparser.AndNode(lhs, _symbol(lhs.filename, 'and'), rhs)
def not_(value: mparser.BaseNode, filename: str) -> mparser.NotNode:
"""Create a not node
:param value: The value to negate
:param filename: the string filename
:return: The NotNode
"""
return mparser.NotNode(_token('not', filename, ''), _symbol(filename, 'not'), value)
def assign(value: mparser.BaseNode, varname: str, filename: str) -> mparser.AssignmentNode:
"""Create an AssignmentNode
:param value: The rvalue
:param varname: The lvalue
:param filename: The filename
:return: An AssignmentNode
"""
return mparser.AssignmentNode(identifier(varname, filename), _symbol(filename, '='), value)
def block(filename: str) -> mparser.CodeBlockNode:
return mparser.CodeBlockNode(_token('node', filename, ''))
@dataclasses.dataclass
class Builder:
filename: str
def _token(self, tid: str, value: mparser.TV_TokenTypes) -> mparser.Token[mparser.TV_TokenTypes]:
"""Create a Token object, but with the line numbers stubbed out.
:param tid: the token id (such as string, number, etc)
:param filename: the filename that the token was generated from
:param value: the value of the token
:return: A Token object
"""
return mparser.Token(tid, self.filename, -1, -1, -1, (-1, -1), value)
def _symbol(self, val: str) -> mparser.SymbolNode:
return mparser.SymbolNode(self._token('', val))
def assign(self, value: mparser.BaseNode, varname: str) -> mparser.AssignmentNode:
return assign(value, varname, self.filename)
return mparser.AssignmentNode(self.identifier(varname), self._symbol('='), value)
def string(self, value: str) -> mparser.StringNode:
"""Build A StringNode
@ -193,7 +44,7 @@ class Builder:
:param value: the value of the string
:return: A StringNode
"""
return string(value, self.filename)
return mparser.StringNode(self._token('string', value))
def number(self, value: int) -> mparser.NumberNode:
"""Build A NumberNode
@ -201,7 +52,7 @@ class Builder:
:param value: the value of the number
:return: A NumberNode
"""
return number(value, self.filename)
return mparser.NumberNode(self._token('number', str(value)))
def bool(self, value: builtins.bool) -> mparser.BooleanNode:
"""Build A BooleanNode
@ -209,7 +60,7 @@ class Builder:
:param value: the value of the boolean
:return: A BooleanNode
"""
return bool(value, self.filename)
return mparser.BooleanNode(self._token('bool', value))
def array(self, value: T.List[mparser.BaseNode]) -> mparser.ArrayNode:
"""Build an Array Node
@ -217,7 +68,9 @@ class Builder:
:param value: A list of nodes to insert into the array
:return: An ArrayNode built from the arguments
"""
return array(value, self.filename)
args = mparser.ArgumentNode(self._token('array', 'unused'))
args.arguments = value
return mparser.ArrayNode(self._symbol('['), args, self._symbol(']'))
def identifier(self, value: str) -> mparser.IdNode:
"""Build A IdNode
@ -225,7 +78,7 @@ class Builder:
:param value: the value of the boolean
:return: A BooleanNode
"""
return identifier(value, self.filename)
return mparser.IdNode(self._token('id', value))
def method(self, name: str, id_: mparser.IdNode,
pos: T.Optional[T.List[mparser.BaseNode]] = None,
@ -239,7 +92,12 @@ class Builder:
:param kw: a dictionary of keyword arguments, defaults to None
:return: a method call object
"""
return method(name, id_, pos or [], kw or {})
args = mparser.ArgumentNode(self._token('array', 'unused'))
if pos is not None:
args.arguments = pos
if kw is not None:
args.kwargs = {self.identifier(k): v for k, v in kw.items()}
return mparser.MethodNode(id_, self._symbol('.'), self.identifier(name), self._symbol('('), args, self._symbol(')'))
def function(self, name: str,
pos: T.Optional[T.List[mparser.BaseNode]] = None,
@ -252,7 +110,12 @@ class Builder:
:param kw: a dictionary of keyword arguments, defaults to None
:return: a method call object
"""
return function(name, self.filename, pos or [], kw or {})
args = mparser.ArgumentNode(self._token('array', 'unused'))
if pos is not None:
args.arguments = pos
if kw is not None:
args.kwargs = {self.identifier(k): v for k, v in kw.items()}
return mparser.FunctionNode(self.identifier(name), self._symbol('('), args, self._symbol(')'))
def equal(self, lhs: mparser.BaseNode, rhs: mparser.BaseNode) -> mparser.ComparisonNode:
"""Create an equality operation
@ -261,7 +124,7 @@ class Builder:
:param rhs: the right hand side of the equal
:return: A compraison node
"""
return equal(lhs, rhs)
return mparser.ComparisonNode('==', lhs, self._symbol('=='), rhs)
def or_(self, lhs: mparser.BaseNode, rhs: mparser.BaseNode) -> mparser.OrNode:
"""Create and OrNode
@ -270,7 +133,7 @@ class Builder:
:param rhs: The Right of the Node
:return: The OrNode
"""
return or_(lhs, rhs)
return mparser.OrNode(lhs, self._symbol('or'), rhs)
def and_(self, lhs: mparser.BaseNode, rhs: mparser.BaseNode) -> mparser.AndNode:
"""Create an AndNode
@ -279,12 +142,17 @@ class Builder:
:param rhs: The right of the And
:return: The AndNode
"""
return and_(lhs, rhs)
return mparser.AndNode(lhs, self._symbol('and'), rhs)
def not_(self, value: mparser.BaseNode, filename: str) -> mparser.NotNode:
def not_(self, value: mparser.BaseNode) -> mparser.NotNode:
"""Create a not node
:param value: The value to negate
:return: The NotNode
"""
return not_(value, self.filename)
return mparser.NotNode(self._token('not', ''), self._symbol('not'), value)
def block(self, lines: T.List[mparser.BaseNode]) -> mparser.CodeBlockNode:
block = mparser.CodeBlockNode(self._token('node', ''))
block.lines = lines
return block

@ -130,7 +130,6 @@ class IR:
"""Base IR node for Cargo CFG."""
filename: str
@dataclasses.dataclass
class String(IR):
@ -169,7 +168,7 @@ class Not(IR):
value: IR
def _parse(ast: _LEX_STREAM_AH, filename: str) -> IR:
def _parse(ast: _LEX_STREAM_AH) -> IR:
(token, value), n_stream = next(ast)
if n_stream is not None:
ntoken, _ = n_stream
@ -179,12 +178,12 @@ def _parse(ast: _LEX_STREAM_AH, filename: str) -> IR:
stream: T.List[_LEX_TOKEN]
if token is TokenType.IDENTIFIER:
if ntoken is TokenType.EQUAL:
return Equal(filename, Identifier(filename, value), _parse(ast, filename))
return Equal(Identifier(value), _parse(ast))
if token is TokenType.STRING:
return String(filename, value)
return String(value)
if token is TokenType.EQUAL:
# In this case the previous caller already has handled the equal
return _parse(ast, filename)
return _parse(ast)
if token in {TokenType.ANY, TokenType.ALL}:
type_ = All if token is TokenType.ALL else Any
assert ntoken is TokenType.LPAREN
@ -194,13 +193,13 @@ def _parse(ast: _LEX_STREAM_AH, filename: str) -> IR:
while token is not TokenType.RPAREN:
(token, value), _ = next(ast)
if token is TokenType.COMMA:
args.append(_parse(lookahead(iter(stream)), filename))
args.append(_parse(lookahead(iter(stream))))
stream.clear()
else:
stream.append((token, value))
if stream:
args.append(_parse(lookahead(iter(stream)), filename))
return type_(filename, args)
args.append(_parse(lookahead(iter(stream))))
return type_(args)
if token is TokenType.NOT:
next(ast) # advance the iterator to get rid of the LPAREN
stream = []
@ -208,69 +207,68 @@ def _parse(ast: _LEX_STREAM_AH, filename: str) -> IR:
while token is not TokenType.RPAREN: # type: ignore
(token, value), _ = next(ast)
stream.append((token, value))
return Not(filename, _parse(lookahead(iter(stream)), filename))
return Not(_parse(lookahead(iter(stream))))
raise MesonBugException(f'Unhandled Cargo token: {token}')
def parse(ast: _LEX_STREAM, filename: str) -> IR:
def parse(ast: _LEX_STREAM) -> IR:
"""Parse the tokenized list into Meson AST.
:param ast: An iterable of Tokens
:param filename: The name of the file being parsed
:return: An mparser Node to be used as a conditional
"""
ast_i: _LEX_STREAM_AH = lookahead(iter(ast))
return _parse(ast_i, filename)
return _parse(ast_i)
@functools.singledispatch
def ir_to_meson(ir: T.Any) -> mparser.BaseNode:
def ir_to_meson(ir: T.Any, build: builder.Builder) -> mparser.BaseNode:
raise NotImplementedError
@ir_to_meson.register
def _(ir: String) -> mparser.BaseNode:
return builder.string(ir.value, ir.filename)
def _(ir: String, build: builder.Builder) -> mparser.BaseNode:
return build.string(ir.value)
@ir_to_meson.register
def _(ir: Identifier) -> mparser.BaseNode:
host_machine = builder.identifier('host_machine', ir.filename)
def _(ir: Identifier, build: builder.Builder) -> mparser.BaseNode:
host_machine = build.identifier('host_machine')
if ir.value == "target_arch":
return builder.method('cpu_family', host_machine)
return build.method('cpu_family', host_machine)
elif ir.value in {"target_os", "target_family"}:
return builder.method('system', host_machine)
return build.method('system', host_machine)
elif ir.value == "target_endian":
return builder.method('endian', host_machine)
return build.method('endian', host_machine)
raise MesonBugException(f"Unhandled Cargo identifier: {ir.value}")
@ir_to_meson.register
def _(ir: Equal) -> mparser.BaseNode:
return builder.equal(ir_to_meson(ir.lhs), ir_to_meson(ir.rhs))
def _(ir: Equal, build: builder.Builder) -> mparser.BaseNode:
return build.equal(ir_to_meson(ir.lhs, build), ir_to_meson(ir.rhs, build))
@ir_to_meson.register
def _(ir: Not) -> mparser.BaseNode:
return builder.not_(ir_to_meson(ir.value), ir.filename)
def _(ir: Not, build: builder.Builder) -> mparser.BaseNode:
return build.not_(ir_to_meson(ir.value, build))
@ir_to_meson.register
def _(ir: Any) -> mparser.BaseNode:
def _(ir: Any, build: builder.Builder) -> mparser.BaseNode:
args = iter(reversed(ir.args))
last = next(args)
cur = builder.or_(ir_to_meson(next(args)), ir_to_meson(last))
cur = build.or_(ir_to_meson(next(args), build), ir_to_meson(last, build))
for a in args:
cur = builder.or_(ir_to_meson(a), cur)
cur = build.or_(ir_to_meson(a, build), cur)
return cur
@ir_to_meson.register
def _(ir: All) -> mparser.BaseNode:
def _(ir: All, build: builder.Builder) -> mparser.BaseNode:
args = iter(reversed(ir.args))
last = next(args)
cur = builder.and_(ir_to_meson(next(args)), ir_to_meson(last))
cur = build.and_(ir_to_meson(next(args), build), ir_to_meson(last, build))
for a in args:
cur = builder.and_(ir_to_meson(a), cur)
cur = build.and_(ir_to_meson(a, build), cur)
return cur

@ -445,7 +445,4 @@ def interpret(cargo: Manifest, env: Environment) -> mparser.CodeBlockNode:
if os.path.exists(os.path.join(env.source_dir, cargo.subdir, cargo.path, 'src', 'lib.rs')):
ast.extend(_create_lib(cargo, build))
# XXX: make this not awful
block = builder.block(filename)
block.lines = ast
return block
return build.block(ast)

@ -104,83 +104,83 @@ class CargoCfgTest(unittest.TestCase):
def test_parse(self) -> None:
cases = [
('target_os = "windows"', cfg.Equal('', cfg.Identifier('', "target_os"), cfg.String('', "windows"))),
('target_arch = "x86"', cfg.Equal('', cfg.Identifier('', "target_arch"), cfg.String('', "x86"))),
('target_family = "unix"', cfg.Equal('', cfg.Identifier('', "target_family"), cfg.String('', "unix"))),
('target_os = "windows"', cfg.Equal(cfg.Identifier("target_os"), cfg.String("windows"))),
('target_arch = "x86"', cfg.Equal(cfg.Identifier("target_arch"), cfg.String("x86"))),
('target_family = "unix"', cfg.Equal(cfg.Identifier("target_family"), cfg.String("unix"))),
('any(target_arch = "x86", target_arch = "x86_64")',
cfg.Any(
'', [
cfg.Equal('', cfg.Identifier('', "target_arch"), cfg.String('', "x86")),
cfg.Equal('', cfg.Identifier('', "target_arch"), cfg.String('', "x86_64")),
[
cfg.Equal(cfg.Identifier("target_arch"), cfg.String("x86")),
cfg.Equal(cfg.Identifier("target_arch"), cfg.String("x86_64")),
])),
('all(target_arch = "x86", target_os = "linux")',
cfg.All(
'', [
cfg.Equal('', cfg.Identifier('', "target_arch"), cfg.String('', "x86")),
cfg.Equal('', cfg.Identifier('', "target_os"), cfg.String('', "linux")),
[
cfg.Equal(cfg.Identifier("target_arch"), cfg.String("x86")),
cfg.Equal(cfg.Identifier("target_os"), cfg.String("linux")),
])),
('not(all(target_arch = "x86", target_os = "linux"))',
cfg.Not(
'',
cfg.All(
'', [
cfg.Equal('', cfg.Identifier('', "target_arch"), cfg.String('', "x86")),
cfg.Equal('', cfg.Identifier('', "target_os"), cfg.String('', "linux")),
[
cfg.Equal(cfg.Identifier("target_arch"), cfg.String("x86")),
cfg.Equal(cfg.Identifier("target_os"), cfg.String("linux")),
]))),
]
for data, expected in cases:
with self.subTest():
self.assertEqual(cfg.parse(iter(cfg.lexer(data)), ''), expected)
self.assertEqual(cfg.parse(iter(cfg.lexer(data))), expected)
def test_ir_to_meson(self) -> None:
HOST_MACHINE = builder.identifier('host_machine', '')
build = builder.Builder('')
HOST_MACHINE = build.identifier('host_machine')
cases = [
('target_os = "windows"',
builder.equal(builder.method('system', HOST_MACHINE),
builder.string('windows', ''))),
build.equal(build.method('system', HOST_MACHINE),
build.string('windows'))),
('target_arch = "x86"',
builder.equal(builder.method('cpu_family', HOST_MACHINE),
builder.string('x86', ''))),
build.equal(build.method('cpu_family', HOST_MACHINE),
build.string('x86'))),
('target_family = "unix"',
builder.equal(builder.method('system', HOST_MACHINE),
builder.string('unix', ''))),
build.equal(build.method('system', HOST_MACHINE),
build.string('unix'))),
('not(target_arch = "x86")',
builder.not_(builder.equal(
builder.method('cpu_family', HOST_MACHINE),
builder.string('x86', '')), '')),
build.not_(build.equal(
build.method('cpu_family', HOST_MACHINE),
build.string('x86')))),
('any(target_arch = "x86", target_arch = "x86_64")',
builder.or_(
builder.equal(builder.method('cpu_family', HOST_MACHINE),
builder.string('x86', '')),
builder.equal(builder.method('cpu_family', HOST_MACHINE),
builder.string('x86_64', '')))),
build.or_(
build.equal(build.method('cpu_family', HOST_MACHINE),
build.string('x86')),
build.equal(build.method('cpu_family', HOST_MACHINE),
build.string('x86_64')))),
('any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64")',
builder.or_(
builder.equal(builder.method('cpu_family', HOST_MACHINE),
builder.string('x86', '')),
builder.or_(
builder.equal(builder.method('cpu_family', HOST_MACHINE),
builder.string('x86_64', '')),
builder.equal(builder.method('cpu_family', HOST_MACHINE),
builder.string('aarch64', ''))))),
build.or_(
build.equal(build.method('cpu_family', HOST_MACHINE),
build.string('x86')),
build.or_(
build.equal(build.method('cpu_family', HOST_MACHINE),
build.string('x86_64')),
build.equal(build.method('cpu_family', HOST_MACHINE),
build.string('aarch64'))))),
('all(target_arch = "x86", target_arch = "x86_64")',
builder.and_(
builder.equal(builder.method('cpu_family', HOST_MACHINE),
builder.string('x86', '')),
builder.equal(builder.method('cpu_family', HOST_MACHINE),
builder.string('x86_64', '')))),
build.and_(
build.equal(build.method('cpu_family', HOST_MACHINE),
build.string('x86')),
build.equal(build.method('cpu_family', HOST_MACHINE),
build.string('x86_64')))),
('all(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64")',
builder.and_(
builder.equal(builder.method('cpu_family', HOST_MACHINE),
builder.string('x86', '')),
builder.and_(
builder.equal(builder.method('cpu_family', HOST_MACHINE),
builder.string('x86_64', '')),
builder.equal(builder.method('cpu_family', HOST_MACHINE),
builder.string('aarch64', ''))))),
build.and_(
build.equal(build.method('cpu_family', HOST_MACHINE),
build.string('x86')),
build.and_(
build.equal(build.method('cpu_family', HOST_MACHINE),
build.string('x86_64')),
build.equal(build.method('cpu_family', HOST_MACHINE),
build.string('aarch64'))))),
]
for data, expected in cases:
with self.subTest():
value = cfg.ir_to_meson(cfg.parse(iter(cfg.lexer(data)), ''))
value = cfg.ir_to_meson(cfg.parse(iter(cfg.lexer(data))), build)
self.assertEqual(value, expected)

Loading…
Cancel
Save