Merge pull request #1103 from mesonbuild/rewriter

Beginnings of a rewriter
pull/1154/head
Jussi Pakkanen 8 years ago committed by GitHub
commit 228a9035af
  1. 241
      mesonbuild/astinterpreter.py
  2. 603
      mesonbuild/interpreter.py
  3. 636
      mesonbuild/interpreterbase.py
  4. 127
      mesonbuild/mparser.py
  5. 2
      mesonbuild/optinterpreter.py
  6. 64
      mesonrewriter.py
  7. 58
      run_unittests.py
  8. 5
      test cases/rewrite/1 basic/added.txt
  9. 5
      test cases/rewrite/1 basic/meson.build
  10. 5
      test cases/rewrite/1 basic/removed.txt
  11. 5
      test cases/rewrite/2 subdirs/meson.build
  12. 1
      test cases/rewrite/2 subdirs/sub1/after.txt
  13. 1
      test cases/rewrite/2 subdirs/sub1/meson.build
  14. 2
      test cases/rewrite/2 subdirs/sub2/meson.build

@ -0,0 +1,241 @@
# Copyright 2016 The Meson development team
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# This class contains the basic functionality needed to run any interpreter
# or an interpreter-based tool.
from . import interpreterbase, mlog, mparser, mesonlib
from . import environment
from .interpreterbase import InterpreterException, InvalidArguments
import os, sys
class DontCareObject(interpreterbase.InterpreterObject):
pass
class MockExecutable(interpreterbase.InterpreterObject):
pass
class MockStaticLibrary(interpreterbase.InterpreterObject):
pass
class MockSharedLibrary(interpreterbase.InterpreterObject):
pass
class MockCustomTarget(interpreterbase.InterpreterObject):
pass
class MockRunTarget(interpreterbase.InterpreterObject):
pass
ADD_SOURCE = 0
REMOVE_SOURCE = 1
class AstInterpreter(interpreterbase.InterpreterBase):
def __init__(self, source_root, subdir):
super().__init__(source_root, subdir)
self.asts = {}
self.funcs.update({'project' : self.func_do_nothing,
'test' : self.func_do_nothing,
'benchmark' : self.func_do_nothing,
'install_headers' : self.func_do_nothing,
'install_man' : self.func_do_nothing,
'install_data' : self.func_do_nothing,
'install_subdir' : self.func_do_nothing,
'configuration_data' : self.func_do_nothing,
'configure_file' : self.func_do_nothing,
'find_program' : self.func_do_nothing,
'include_directories' : self.func_do_nothing,
'add_global_arguments' : self.func_do_nothing,
'add_global_link_arguments' : self.func_do_nothing,
'add_project_arguments' : self.func_do_nothing,
'add_project_link_arguments' : self.func_do_nothing,
'message' : self.func_do_nothing,
'generator' : self.func_do_nothing,
'error' : self.func_do_nothing,
'run_command' : self.func_do_nothing,
'assert' : self.func_do_nothing,
'subproject' : self.func_do_nothing,
'dependency' : self.func_do_nothing,
'get_option' : self.func_do_nothing,
'join_paths' : self.func_do_nothing,
'environment' : self.func_do_nothing,
'import' : self.func_do_nothing,
'vcs_tag' : self.func_do_nothing,
'add_languages' : self.func_do_nothing,
'declare_dependency' : self.func_do_nothing,
'files' : self.func_files,
'executable': self.func_executable,
'static_library' : self.func_static_lib,
'shared_library' : self.func_shared_lib,
'library' : self.func_library,
'build_target' : self.func_build_target,
'custom_target' : self.func_custom_target,
'run_target' : self.func_run_target,
'subdir' : self.func_subdir,
'set_variable' : self.func_set_variable,
'get_variable' : self.func_get_variable,
'is_variable' : self.func_is_variable,
})
def func_do_nothing(self, node, args, kwargs):
return True
def method_call(self, node):
return True
def func_executable(self, node, args, kwargs):
if args[0] == self.targetname:
if self.operation == ADD_SOURCE:
self.add_source_to_target(node, args, kwargs)
elif self.operation == REMOVE_SOURCE:
self.remove_source_from_target(node, args, kwargs)
else:
raise NotImplementedError('Bleep bloop')
return MockExecutable()
def func_static_lib(self, node, args, kwargs):
return MockStaticLibrary()
def func_shared_lib(self, node, args, kwargs):
return MockSharedLibrary()
def func_library(self, node, args, kwargs):
return self.func_shared_lib(node, args, kwargs)
def func_custom_target(self, node, args, kwargs):
return MockCustomTarget()
def func_run_target(self, node, args, kwargs):
return MockRunTarget()
def func_subdir(self, node, args, kwargs):
prev_subdir = self.subdir
subdir = os.path.join(prev_subdir, args[0])
self.subdir = subdir
buildfilename = os.path.join(self.subdir, environment.build_filename)
absname = os.path.join(self.source_root, buildfilename)
if not os.path.isfile(absname):
self.subdir = prev_subdir
raise InterpreterException('Nonexistant build def file %s.' % buildfilename)
with open(absname, encoding='utf8') as f:
code = f.read()
assert(isinstance(code, str))
try:
codeblock = mparser.Parser(code, self.subdir).parse()
self.asts[subdir] = codeblock
except mesonlib.MesonException as me:
me.file = buildfilename
raise me
self.evaluate_codeblock(codeblock)
self.subdir = prev_subdir
def func_files(self, node, args, kwargs):
if not isinstance(args, list):
return [args]
return args
def evaluate_arithmeticstatement(self, cur):
return 0
def evaluate_plusassign(self, node):
return 0
def evaluate_indexing(self, node):
return 0
def reduce_arguments(self, args):
assert(isinstance(args, mparser.ArgumentNode))
if args.incorrect_order():
raise InvalidArguments('All keyword arguments must be after positional arguments.')
return (args.arguments, args.kwargs)
def transform(self):
self.load_root_meson_file()
self.asts[''] = self.ast
self.sanity_check_ast()
self.parse_project()
self.run()
def add_source(self, targetname, filename):
self.operation = ADD_SOURCE
self.targetname = targetname
self.filename = filename
self.transform()
def remove_source(self, targetname, filename):
self.operation = REMOVE_SOURCE
self.targetname = targetname
self.filename = filename
self.transform()
def unknown_function_called(self, func_name):
mlog.warning('Unknown function called: ' + func_name)
def add_source_to_target(self, node, args, kwargs):
namespan = node.args.arguments[0].bytespan
buildfilename = os.path.join(self.source_root, self.subdir, environment.build_filename)
raw_data = open(buildfilename, 'r').read()
updated = raw_data[0:namespan[1]] + (", '%s'" % self.filename) + raw_data[namespan[1]:]
open(buildfilename, 'w').write(updated)
sys.exit(0)
def remove_argument_item(self, args, i):
assert(isinstance(args, mparser.ArgumentNode))
namespan = args.arguments[i].bytespan
# Usually remove the comma after this item but if it is
# the last argument, we need to remove the one before.
if i >= len(args.commas):
i -= 1
if i < 0:
commaspan = (0, 0) # Removed every entry in the list.
else:
commaspan = args.commas[i].bytespan
if commaspan[0] < namespan[0]:
commaspan, namespan = namespan, commaspan
buildfilename = os.path.join(self.source_root, args.subdir, environment.build_filename)
raw_data = open(buildfilename, 'r').read()
intermediary = raw_data[0:commaspan[0]] + raw_data[commaspan[1]:]
updated = intermediary[0:namespan[0]] + intermediary[namespan[1]:]
open(buildfilename, 'w').write(updated)
sys.exit(0)
def hacky_find_and_remove(self, node_to_remove):
for a in self.asts[node_to_remove.subdir].lines:
if a.lineno == node_to_remove.lineno:
if isinstance(a, mparser.AssignmentNode):
v = a.value
if not isinstance(v, mparser.ArrayNode):
raise NotImplementedError('Not supported yet, bro.')
args = v.args
for i in range(len(args.arguments)):
if isinstance(args.arguments[i], mparser.StringNode) and self.filename == args.arguments[i].value:
self.remove_argument_item(args, i)
raise NotImplementedError('Sukkess')
def remove_source_from_target(self, node, args, kwargs):
for i in range(1, len(node.args)):
# Is file name directly in function call as a string.
if isinstance(node.args.arguments[i], mparser.StringNode) and self.filename == node.args.arguments[i].value:
self.remove_argument_item(node.args, i)
# Is file name in a variable that gets expanded here.
if isinstance(node.args.arguments[i], mparser.IdNode):
avar = self.get_variable(node.args.arguments[i].value)
if not isinstance(avar, list):
raise NotImplementedError('Non-arrays not supported yet, sorry.')
for entry in avar:
if isinstance(entry, mparser.StringNode) and entry.value == self.filename:
self.hacky_find_and_remove(entry)
sys.exit('Could not find source %s in target %s.' % (self.filename, args[0]))

@ -22,58 +22,17 @@ from . import optinterpreter
from . import compilers from . import compilers
from .wrap import wrap from .wrap import wrap
from . import mesonlib from . import mesonlib
from mesonbuild.interpreterbase import InterpreterBase
from mesonbuild.interpreterbase import check_stringlist, noPosargs, noKwargs, stringArgs
from mesonbuild.interpreterbase import InterpreterException, InvalidArguments, InvalidCode
from mesonbuild.interpreterbase import InterpreterObject, MutableInterpreterObject
import os, sys, subprocess, shutil, uuid, re import os, sys, subprocess, shutil, uuid, re
from functools import wraps
import importlib import importlib
import copy
run_depr_printed = False run_depr_printed = False
class InterpreterException(mesonlib.MesonException):
pass
class InvalidCode(InterpreterException):
pass
class InvalidArguments(InterpreterException):
pass
# Decorators for method calls.
def check_stringlist(a, msg='Arguments must be strings.'):
if not isinstance(a, list):
mlog.debug('Not a list:', str(a))
raise InvalidArguments('Argument not a list.')
if not all(isinstance(s, str) for s in a):
mlog.debug('Element not a string:', str(a))
raise InvalidArguments(msg)
def noPosargs(f):
@wraps(f)
def wrapped(self, node, args, kwargs):
if len(args) != 0:
raise InvalidArguments('Function does not take positional arguments.')
return f(self, node, args, kwargs)
return wrapped
def noKwargs(f):
@wraps(f)
def wrapped(self, node, args, kwargs):
if len(kwargs) != 0:
raise InvalidArguments('Function does not take keyword arguments.')
return f(self, node, args, kwargs)
return wrapped
def stringArgs(f):
@wraps(f)
def wrapped(self, node, args, kwargs):
assert(isinstance(args, list))
check_stringlist(args)
return f(self, node, args, kwargs)
return wrapped
def stringifyUserArguments(args): def stringifyUserArguments(args):
if isinstance(args, list): if isinstance(args, list):
return '[%s]' % ', '.join([stringifyUserArguments(x) for x in args]) return '[%s]' % ', '.join([stringifyUserArguments(x) for x in args])
@ -83,18 +42,6 @@ def stringifyUserArguments(args):
return "'%s'" % args return "'%s'" % args
raise InvalidArguments('Function accepts only strings, integers, lists and lists thereof.') raise InvalidArguments('Function accepts only strings, integers, lists and lists thereof.')
class InterpreterObject():
def __init__(self):
self.methods = {}
def method_call(self, method_name, args, kwargs):
if method_name in self.methods:
return self.methods[method_name](args, kwargs)
raise InvalidCode('Unknown method "%s" in object.' % method_name)
class MutableInterpreterObject(InterpreterObject):
def __init__(self):
super().__init__()
class TryRunResultHolder(InterpreterObject): class TryRunResultHolder(InterpreterObject):
def __init__(self, res): def __init__(self, res):
@ -1139,16 +1086,15 @@ class MesonMain(InterpreterObject):
return args[1] return args[1]
raise InterpreterException('Unknown cross property: %s.' % propname) raise InterpreterException('Unknown cross property: %s.' % propname)
class Interpreter(): class Interpreter(InterpreterBase):
def __init__(self, build, backend, subproject='', subdir='', subproject_dir='subprojects'): def __init__(self, build, backend, subproject='', subdir='', subproject_dir='subprojects'):
super().__init__(build.environment.get_source_dir(), subdir)
self.build = build self.build = build
self.environment = build.environment self.environment = build.environment
self.coredata = self.environment.get_coredata() self.coredata = self.environment.get_coredata()
self.backend = backend self.backend = backend
self.subproject = subproject self.subproject = subproject
self.subdir = subdir
self.source_root = build.environment.get_source_dir()
self.subproject_dir = subproject_dir self.subproject_dir = subproject_dir
option_file = os.path.join(self.source_root, self.subdir, 'meson_options.txt') option_file = os.path.join(self.source_root, self.subdir, 'meson_options.txt')
if os.path.exists(option_file): if os.path.exists(option_file):
@ -1156,22 +1102,9 @@ class Interpreter():
self.build.environment.cmd_line_options.projectoptions) self.build.environment.cmd_line_options.projectoptions)
oi.process(option_file) oi.process(option_file)
self.build.environment.merge_options(oi.options) self.build.environment.merge_options(oi.options)
mesonfile = os.path.join(self.source_root, self.subdir, environment.build_filename) self.load_root_meson_file()
if not os.path.isfile(mesonfile):
raise InvalidArguments('Missing Meson file in %s' % mesonfile)
with open(mesonfile, encoding='utf8') as mf:
code = mf.read()
if len(code.strip()) == 0:
raise InvalidCode('Builder file is empty.')
assert(isinstance(code, str))
try:
self.ast = mparser.Parser(code).parse()
except mesonlib.MesonException as me:
me.file = environment.build_filename
raise me
self.sanity_check_ast() self.sanity_check_ast()
self.variables = {} self.builtin.update({'meson': MesonMain(build, self)})
self.builtin = {'meson': MesonMain(build, self)}
self.generators = [] self.generators = []
self.visited_subdirs = {} self.visited_subdirs = {}
self.args_frozen = False self.args_frozen = False
@ -1196,7 +1129,7 @@ class Interpreter():
self.build_def_files = [os.path.join(self.subdir, environment.build_filename)] self.build_def_files = [os.path.join(self.subdir, environment.build_filename)]
def build_func_dict(self): def build_func_dict(self):
self.funcs = {'project' : self.func_project, self.funcs.update({'project' : self.func_project,
'message' : self.func_message, 'message' : self.func_message,
'error' : self.func_error, 'error' : self.func_error,
'executable': self.func_executable, 'executable': self.func_executable,
@ -1241,14 +1174,7 @@ class Interpreter():
'assert': self.func_assert, 'assert': self.func_assert,
'environment' : self.func_environment, 'environment' : self.func_environment,
'join_paths' : self.func_join_paths, 'join_paths' : self.func_join_paths,
} })
def parse_project(self):
"""
Parses project() and initializes languages, compilers etc. Do this
early because we need this before we parse the rest of the AST.
"""
self.evaluate_codeblock(self.ast, end=1)
def module_method_callback(self, invalues): def module_method_callback(self, invalues):
unwrap_single = False unwrap_single = False
@ -1295,16 +1221,6 @@ class Interpreter():
def get_variables(self): def get_variables(self):
return self.variables return self.variables
def sanity_check_ast(self):
if not isinstance(self.ast, mparser.CodeBlockNode):
raise InvalidCode('AST is of invalid type. Possibly a bug in the parser.')
if len(self.ast.lines) == 0:
raise InvalidCode('No statements in code.')
first = self.ast.lines[0]
if not isinstance(first, mparser.FunctionNode) or first.func_name != 'project':
raise InvalidCode('First statement must be a call to project')
def check_cross_stdlibs(self): def check_cross_stdlibs(self):
if self.build.environment.is_cross_build(): if self.build.environment.is_cross_build():
cross_info = self.build.environment.cross_info cross_info = self.build.environment.cross_info
@ -1321,71 +1237,6 @@ class Interpreter():
except KeyError as e: except KeyError as e:
pass pass
def run(self):
# Evaluate everything after the first line, which is project() because
# we already parsed that in self.parse_project()
self.evaluate_codeblock(self.ast, start=1)
mlog.log('Build targets in project:', mlog.bold(str(len(self.build.targets))))
def evaluate_codeblock(self, node, start=0, end=None):
if node is None:
return
if not isinstance(node, mparser.CodeBlockNode):
e = InvalidCode('Tried to execute a non-codeblock. Possibly a bug in the parser.')
e.lineno = node.lineno
e.colno = node.colno
raise e
statements = node.lines[start:end]
i = 0
while i < len(statements):
cur = statements[i]
try:
self.evaluate_statement(cur)
except Exception as e:
if not(hasattr(e, 'lineno')):
e.lineno = cur.lineno
e.colno = cur.colno
e.file = os.path.join(self.subdir, 'meson.build')
raise e
i += 1 # In THE FUTURE jump over blocks and stuff.
def get_variable(self, varname):
if varname in self.builtin:
return self.builtin[varname]
if varname in self.variables:
return self.variables[varname]
raise InvalidCode('Unknown variable "%s".' % varname)
def func_set_variable(self, node, args, kwargs):
if len(args) != 2:
raise InvalidCode('Set_variable takes two arguments.')
varname = args[0]
value = self.to_native(args[1])
self.set_variable(varname, value)
@noKwargs
def func_get_variable(self, node, args, kwargs):
if len(args)<1 or len(args)>2:
raise InvalidCode('Get_variable takes one or two arguments.')
varname = args[0]
if not isinstance(varname, str):
raise InterpreterException('First argument must be a string.')
try:
return self.variables[varname]
except KeyError:
pass
if len(args) == 2:
return args[1]
raise InterpreterException('Tried to get unknown variable "%s".' % varname)
@stringArgs
@noKwargs
def func_is_variable(self, node, args, kwargs):
if len(args) != 1:
raise InvalidCode('Is_variable takes two arguments.')
varname = args[0]
return varname in self.variables
@stringArgs @stringArgs
@noKwargs @noKwargs
def func_import(self, node, args, kwargs): def func_import(self, node, args, kwargs):
@ -1446,63 +1297,6 @@ class Interpreter():
if not value: if not value:
raise InterpreterException('Assert failed: ' + message) raise InterpreterException('Assert failed: ' + message)
def set_variable(self, varname, variable):
if variable is None:
raise InvalidCode('Can not assign None to variable.')
if not isinstance(varname, str):
raise InvalidCode('First argument to set_variable must be a string.')
if not self.is_assignable(variable):
raise InvalidCode('Assigned value not of assignable type.')
if re.match('[_a-zA-Z][_0-9a-zA-Z]*$', varname) is None:
raise InvalidCode('Invalid variable name: ' + varname)
if varname in self.builtin:
raise InvalidCode('Tried to overwrite internal variable "%s"' % varname)
self.variables[varname] = variable
def evaluate_statement(self, cur):
if isinstance(cur, mparser.FunctionNode):
return self.function_call(cur)
elif isinstance(cur, mparser.AssignmentNode):
return self.assignment(cur)
elif isinstance(cur, mparser.MethodNode):
return self.method_call(cur)
elif isinstance(cur, mparser.StringNode):
return cur.value
elif isinstance(cur, mparser.BooleanNode):
return cur.value
elif isinstance(cur, mparser.IfClauseNode):
return self.evaluate_if(cur)
elif isinstance(cur, mparser.IdNode):
return self.get_variable(cur.value)
elif isinstance(cur, mparser.ComparisonNode):
return self.evaluate_comparison(cur)
elif isinstance(cur, mparser.ArrayNode):
return self.evaluate_arraystatement(cur)
elif isinstance(cur, mparser.NumberNode):
return cur.value
elif isinstance(cur, mparser.AndNode):
return self.evaluate_andstatement(cur)
elif isinstance(cur, mparser.OrNode):
return self.evaluate_orstatement(cur)
elif isinstance(cur, mparser.NotNode):
return self.evaluate_notstatement(cur)
elif isinstance(cur, mparser.UMinusNode):
return self.evaluate_uminusstatement(cur)
elif isinstance(cur, mparser.ArithmeticNode):
return self.evaluate_arithmeticstatement(cur)
elif isinstance(cur, mparser.ForeachClauseNode):
return self.evaluate_foreach(cur)
elif isinstance(cur, mparser.PlusAssignmentNode):
return self.evaluate_plusassign(cur)
elif isinstance(cur, mparser.IndexNode):
return self.evaluate_indexing(cur)
elif isinstance(cur, mparser.TernaryNode):
return self.evaluate_ternary(cur)
elif self.is_elementary_type(cur):
return cur
else:
raise InvalidCode("Unknown statement.")
def validate_arguments(self, args, argcount, arg_types): def validate_arguments(self, args, argcount, arg_types):
if argcount is not None: if argcount is not None:
if argcount != len(args): if argcount != len(args):
@ -2193,7 +1987,7 @@ requirements use the version keyword argument instead.''')
code = f.read() code = f.read()
assert(isinstance(code, str)) assert(isinstance(code, str))
try: try:
codeblock = mparser.Parser(code).parse() codeblock = mparser.Parser(code, self.subdir).parse()
except mesonlib.MesonException as me: except mesonlib.MesonException as me:
me.file = buildfilename me.file = buildfilename
raise me raise me
@ -2376,21 +2170,9 @@ requirements use the version keyword argument instead.''')
def func_join_paths(self, node, args, kwargs): def func_join_paths(self, node, args, kwargs):
return os.path.join(*args).replace('\\', '/') return os.path.join(*args).replace('\\', '/')
def flatten(self, args): def run(self):
if isinstance(args, mparser.StringNode): super().run()
return args.value mlog.log('Build targets in project:', mlog.bold(str(len(self.build.targets))))
if isinstance(args, (int, str, InterpreterObject)):
return args
result = []
for a in args:
if isinstance(a, list):
rest = self.flatten(a)
result = result + rest
elif isinstance(a, mparser.StringNode):
result.append(a.value)
else:
result.append(a)
return result
def source_strings_to_files(self, sources): def source_strings_to_files(self, sources):
results = [] results = []
@ -2487,145 +2269,6 @@ requirements use the version keyword argument instead.''')
if not os.path.isfile(fname): if not os.path.isfile(fname):
raise InterpreterException('Tried to add non-existing source file %s.' % s) raise InterpreterException('Tried to add non-existing source file %s.' % s)
def function_call(self, node):
func_name = node.func_name
(posargs, kwargs) = self.reduce_arguments(node.args)
if func_name in self.funcs:
return self.funcs[func_name](node, self.flatten(posargs), kwargs)
else:
raise InvalidCode('Unknown function "%s".' % func_name)
def is_assignable(self, value):
return isinstance(value, (InterpreterObject, dependencies.Dependency,
str, int, list, mesonlib.File))
def assignment(self, node):
assert(isinstance(node, mparser.AssignmentNode))
var_name = node.var_name
if not isinstance(var_name, str):
raise InvalidArguments('Tried to assign value to a non-variable.')
value = self.evaluate_statement(node.value)
value = self.to_native(value)
if not self.is_assignable(value):
raise InvalidCode('Tried to assign an invalid value to variable.')
# For mutable objects we need to make a copy on assignment
if isinstance(value, MutableInterpreterObject):
value = copy.deepcopy(value)
self.set_variable(var_name, value)
return value
def reduce_arguments(self, args):
assert(isinstance(args, mparser.ArgumentNode))
if args.incorrect_order():
raise InvalidArguments('All keyword arguments must be after positional arguments.')
reduced_pos = [self.evaluate_statement(arg) for arg in args.arguments]
reduced_kw = {}
for key in args.kwargs.keys():
if not isinstance(key, str):
raise InvalidArguments('Keyword argument name is not a string.')
a = args.kwargs[key]
reduced_kw[key] = self.evaluate_statement(a)
if not isinstance(reduced_pos, list):
reduced_pos = [reduced_pos]
return (reduced_pos, reduced_kw)
def bool_method_call(self, obj, method_name, args):
obj = self.to_native(obj)
(posargs, _) = self.reduce_arguments(args)
if method_name == 'to_string':
if len(posargs) == 0:
if obj == True:
return 'true'
else:
return 'false'
elif len(posargs) == 2 and isinstance(posargs[0], str) and isinstance(posargs[1], str):
if obj == True:
return posargs[0]
else:
return posargs[1]
else:
raise InterpreterException('bool.to_string() must have either no arguments or exactly two string arguments that signify what values to return for true and false.')
elif method_name == 'to_int':
if obj == True:
return 1
else:
return 0
else:
raise InterpreterException('Unknown method "%s" for a boolean.' % method_name)
def int_method_call(self, obj, method_name, args):
obj = self.to_native(obj)
(posargs, _) = self.reduce_arguments(args)
if method_name == 'is_even':
if len(posargs) == 0:
return obj % 2 == 0
else:
raise InterpreterException('int.is_even() must have no arguments.')
elif method_name == 'is_odd':
if len(posargs) == 0:
return obj % 2 != 0
else:
raise InterpreterException('int.is_odd() must have no arguments.')
else:
raise InterpreterException('Unknown method "%s" for an integer.' % method_name)
def string_method_call(self, obj, method_name, args):
obj = self.to_native(obj)
(posargs, _) = self.reduce_arguments(args)
if method_name == 'strip':
return obj.strip()
elif method_name == 'format':
return self.format_string(obj, args)
elif method_name == 'to_upper':
return obj.upper()
elif method_name == 'to_lower':
return obj.lower()
elif method_name == 'underscorify':
return re.sub(r'[^a-zA-Z0-9]', '_', obj)
elif method_name == 'split':
if len(posargs) > 1:
raise InterpreterException('Split() must have at most one argument.')
elif len(posargs) == 1:
s = posargs[0]
if not isinstance(s, str):
raise InterpreterException('Split() argument must be a string')
return obj.split(s)
else:
return obj.split()
elif method_name == 'startswith' or method_name == 'contains' or method_name == 'endswith':
s = posargs[0]
if not isinstance(s, str):
raise InterpreterException('Argument must be a string.')
if method_name == 'startswith':
return obj.startswith(s)
elif method_name == 'contains':
return obj.find(s) >= 0
return obj.endswith(s)
elif method_name == 'to_int':
try:
return int(obj)
except Exception:
raise InterpreterException('String {!r} cannot be converted to int'.format(obj))
elif method_name == 'join':
if len(posargs) != 1:
raise InterpreterException('Join() takes exactly one argument.')
strlist = posargs[0]
check_stringlist(strlist)
return obj.join(strlist)
elif method_name == 'version_compare':
if len(posargs) != 1:
raise InterpreterException('Version_compare() takes exactly one argument.')
cmpr = posargs[0]
if not isinstance(cmpr, str):
raise InterpreterException('Version_compare() argument must be a string.')
return mesonlib.version_compare(obj, cmpr)
raise InterpreterException('Unknown method "%s" for a string.' % method_name)
def to_native(self, arg):
if isinstance(arg, (mparser.StringNode, mparser.NumberNode,
mparser.BooleanNode)):
return arg.value
return arg
def format_string(self, templ, args): def format_string(self, templ, args):
templ = self.to_native(templ) templ = self.to_native(templ)
@ -2638,32 +2281,6 @@ requirements use the version keyword argument instead.''')
templ = templ.replace('@{}@'.format(i), str(arg)) templ = templ.replace('@{}@'.format(i), str(arg))
return templ return templ
def method_call(self, node):
invokable = node.source_object
if isinstance(invokable, mparser.IdNode):
object_name = invokable.value
obj = self.get_variable(object_name)
else:
obj = self.evaluate_statement(invokable)
method_name = node.name
args = node.args
if isinstance(obj, mparser.StringNode):
obj = obj.get_value()
if isinstance(obj, str):
return self.string_method_call(obj, method_name, args)
if isinstance(obj, bool):
return self.bool_method_call(obj, method_name, args)
if isinstance(obj, int):
return self.int_method_call(obj, method_name, args)
if isinstance(obj, list):
return self.array_method_call(obj, method_name, self.reduce_arguments(args)[0])
if not isinstance(obj, InterpreterObject):
raise InvalidArguments('Variable "%s" is not callable.' % object_name)
(args, kwargs) = self.reduce_arguments(args)
if method_name == 'extract_objects':
self.validate_extraction(obj.held_object)
return obj.method_call(method_name, self.flatten(args), kwargs)
# Only permit object extraction from the same subproject # Only permit object extraction from the same subproject
def validate_extraction(self, buildtarget): def validate_extraction(self, buildtarget):
if not self.subdir.startswith(self.subproject_dir): if not self.subdir.startswith(self.subproject_dir):
@ -2675,20 +2292,6 @@ requirements use the version keyword argument instead.''')
if self.subdir.split('/')[1] != buildtarget.subdir.split('/')[1]: if self.subdir.split('/')[1] != buildtarget.subdir.split('/')[1]:
raise InterpreterException('Tried to extract objects from a different subproject.') raise InterpreterException('Tried to extract objects from a different subproject.')
def array_method_call(self, obj, method_name, args):
if method_name == 'contains':
return self.check_contains(obj, args)
elif method_name == 'length':
return len(obj)
elif method_name == 'get':
index = args[0]
if not isinstance(index, int):
raise InvalidArguments('Array index must be a number.')
if index < -len(obj) or index >= len(obj):
raise InvalidArguments('Array index %s is out of bounds for array of size %d.' % (index, len(obj)))
return obj[index]
raise InterpreterException('Arrays do not have a method called "%s".' % method_name)
def check_contains(self, obj, args): def check_contains(self, obj, args):
if len(args) != 1: if len(args) != 1:
raise InterpreterException('Contains method takes exactly one argument.') raise InterpreterException('Contains method takes exactly one argument.')
@ -2705,183 +2308,5 @@ requirements use the version keyword argument instead.''')
pass pass
return False return False
def evaluate_if(self, node):
assert(isinstance(node, mparser.IfClauseNode))
for i in node.ifs:
result = self.evaluate_statement(i.condition)
if not(isinstance(result, bool)):
raise InvalidCode('If clause {!r} does not evaluate to true or false.'.format(result))
if result:
self.evaluate_codeblock(i.block)
return
if not isinstance(node.elseblock, mparser.EmptyNode):
self.evaluate_codeblock(node.elseblock)
def evaluate_ternary(self, node):
assert(isinstance(node, mparser.TernaryNode))
result = self.evaluate_statement(node.condition)
if not isinstance(result, bool):
raise InterpreterException('Ternary condition is not boolean.')
if result:
return self.evaluate_statement(node.trueblock)
else:
return self.evaluate_statement(node.falseblock)
def evaluate_foreach(self, node):
assert(isinstance(node, mparser.ForeachClauseNode))
varname = node.varname.value
items = self.evaluate_statement(node.items)
if not isinstance(items, list):
raise InvalidArguments('Items of foreach loop is not an array')
for item in items:
self.set_variable(varname, item)
self.evaluate_codeblock(node.block)
def evaluate_plusassign(self, node):
assert(isinstance(node, mparser.PlusAssignmentNode))
varname = node.var_name
addition = self.evaluate_statement(node.value)
# Remember that all variables are immutable. We must always create a
# full new variable and then assign it.
old_variable = self.get_variable(varname)
if isinstance(old_variable, str):
if not isinstance(addition, str):
raise InvalidArguments('The += operator requires a string on the right hand side if the variable on the left is a string')
new_value = old_variable + addition
elif isinstance(old_variable, int):
if not isinstance(addition, int):
raise InvalidArguments('The += operator requires an int on the right hand side if the variable on the left is an int')
new_value = old_variable + addition
elif not isinstance(old_variable, list):
raise InvalidArguments('The += operator currently only works with arrays, strings or ints ')
# Add other data types here.
else:
if isinstance(addition, list):
new_value = old_variable + addition
else:
new_value = old_variable + [addition]
self.set_variable(varname, new_value)
def evaluate_indexing(self, node):
assert(isinstance(node, mparser.IndexNode))
iobject = self.evaluate_statement(node.iobject)
if not isinstance(iobject, list):
raise InterpreterException('Tried to index a non-array object.')
index = self.evaluate_statement(node.index)
if not isinstance(index, int):
raise InterpreterException('Index value is not an integer.')
if index < -len(iobject) or index >= len(iobject):
raise InterpreterException('Index %d out of bounds of array of size %d.' % (index, len(iobject)))
return iobject[index]
def is_elementary_type(self, v):
return isinstance(v, (int, float, str, bool, list))
def evaluate_comparison(self, node):
v1 = self.evaluate_statement(node.left)
v2 = self.evaluate_statement(node.right)
if self.is_elementary_type(v1):
val1 = v1
else:
val1 = v1.value
if self.is_elementary_type(v2):
val2 = v2
else:
val2 = v2.value
if node.ctype == '==':
return val1 == val2
elif node.ctype == '!=':
return val1 != val2
elif node.ctype == '<':
return val1 < val2
elif node.ctype == '<=':
return val1 <= val2
elif node.ctype == '>':
return val1 > val2
elif node.ctype == '>=':
return val1 >= val2
else:
raise InvalidCode('You broke my compare eval.')
def evaluate_andstatement(self, cur):
l = self.evaluate_statement(cur.left)
if isinstance(l, mparser.BooleanNode):
l = l.value
if not isinstance(l, bool):
raise InterpreterException('First argument to "and" is not a boolean.')
if not l:
return False
r = self.evaluate_statement(cur.right)
if isinstance(r, mparser.BooleanNode):
r = r.value
if not isinstance(r, bool):
raise InterpreterException('Second argument to "and" is not a boolean.')
return r
def evaluate_orstatement(self, cur):
l = self.evaluate_statement(cur.left)
if isinstance(l, mparser.BooleanNode):
l = l.get_value()
if not isinstance(l, bool):
raise InterpreterException('First argument to "or" is not a boolean.')
if l:
return True
r = self.evaluate_statement(cur.right)
if isinstance(r, mparser.BooleanNode):
r = r.get_value()
if not isinstance(r, bool):
raise InterpreterException('Second argument to "or" is not a boolean.')
return r
def evaluate_notstatement(self, cur):
v = self.evaluate_statement(cur.value)
if isinstance(v, mparser.BooleanNode):
v = v.value
if not isinstance(v, bool):
raise InterpreterException('Argument to "not" is not a boolean.')
return not v
def evaluate_uminusstatement(self, cur):
v = self.evaluate_statement(cur.value)
if isinstance(v, mparser.NumberNode):
v = v.value
if not isinstance(v, int):
raise InterpreterException('Argument to negation is not an integer.')
return -v
def evaluate_arithmeticstatement(self, cur):
l = self.to_native(self.evaluate_statement(cur.left))
r = self.to_native(self.evaluate_statement(cur.right))
if cur.operation == 'add':
try:
return l + r
except Exception as e:
raise InvalidCode('Invalid use of addition: ' + str(e))
elif cur.operation == 'sub':
if not isinstance(l, int) or not isinstance(r, int):
raise InvalidCode('Subtraction works only with integers.')
return l - r
elif cur.operation == 'mul':
if not isinstance(l, int) or not isinstance(r, int):
raise InvalidCode('Multiplication works only with integers.')
return l * r
elif cur.operation == 'div':
if not isinstance(l, int) or not isinstance(r, int):
raise InvalidCode('Division works only with integers.')
return l // r
elif cur.operation == 'mod':
if not isinstance(l, int) or not isinstance(r, int):
raise InvalidCode('Modulo works only with integers.')
return l % r
else:
raise InvalidCode('You broke me.')
def evaluate_arraystatement(self, cur):
(arguments, kwargs) = self.reduce_arguments(cur.args)
if len(kwargs) > 0:
raise InvalidCode('Keyword arguments are invalid in array construction.')
return arguments
def is_subproject(self): def is_subproject(self):
return self.subproject != '' return self.subproject != ''

@ -0,0 +1,636 @@
# Copyright 2016 The Meson development team
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# This class contains the basic functionality needed to run any interpreter
# or an interpreter-based tool.
from . import mparser, mesonlib, mlog
from . import environment, dependencies
import os, copy, re
from functools import wraps
# Decorators for method calls.
def check_stringlist(a, msg='Arguments must be strings.'):
if not isinstance(a, list):
mlog.debug('Not a list:', str(a))
raise InvalidArguments('Argument not a list.')
if not all(isinstance(s, str) for s in a):
mlog.debug('Element not a string:', str(a))
raise InvalidArguments(msg)
def noPosargs(f):
@wraps(f)
def wrapped(self, node, args, kwargs):
if len(args) != 0:
raise InvalidArguments('Function does not take positional arguments.')
return f(self, node, args, kwargs)
return wrapped
def noKwargs(f):
@wraps(f)
def wrapped(self, node, args, kwargs):
if len(kwargs) != 0:
raise InvalidArguments('Function does not take keyword arguments.')
return f(self, node, args, kwargs)
return wrapped
def stringArgs(f):
@wraps(f)
def wrapped(self, node, args, kwargs):
assert(isinstance(args, list))
check_stringlist(args)
return f(self, node, args, kwargs)
return wrapped
class InterpreterException(mesonlib.MesonException):
pass
class InvalidCode(InterpreterException):
pass
class InvalidArguments(InterpreterException):
pass
class InterpreterObject():
def __init__(self):
self.methods = {}
def method_call(self, method_name, args, kwargs):
if method_name in self.methods:
return self.methods[method_name](args, kwargs)
raise InvalidCode('Unknown method "%s" in object.' % method_name)
class MutableInterpreterObject(InterpreterObject):
def __init__(self):
super().__init__()
class InterpreterBase:
def __init__(self, source_root, subdir):
self.source_root = source_root
self.funcs = {}
self.builtin = {}
self.subdir = subdir
self.variables = {}
def load_root_meson_file(self):
mesonfile = os.path.join(self.source_root, self.subdir, environment.build_filename)
if not os.path.isfile(mesonfile):
raise InvalidArguments('Missing Meson file in %s' % mesonfile)
with open(mesonfile, encoding='utf8') as mf:
code = mf.read()
if len(code.strip()) == 0:
raise InvalidCode('Builder file is empty.')
assert(isinstance(code, str))
try:
self.ast = mparser.Parser(code, self.subdir).parse()
except mesonlib.MesonException as me:
me.file = environment.build_filename
raise me
def parse_project(self):
"""
Parses project() and initializes languages, compilers etc. Do this
early because we need this before we parse the rest of the AST.
"""
self.evaluate_codeblock(self.ast, end=1)
def sanity_check_ast(self):
if not isinstance(self.ast, mparser.CodeBlockNode):
raise InvalidCode('AST is of invalid type. Possibly a bug in the parser.')
if len(self.ast.lines) == 0:
raise InvalidCode('No statements in code.')
first = self.ast.lines[0]
if not isinstance(first, mparser.FunctionNode) or first.func_name != 'project':
raise InvalidCode('First statement must be a call to project')
def run(self):
# Evaluate everything after the first line, which is project() because
# we already parsed that in self.parse_project()
self.evaluate_codeblock(self.ast, start=1)
def evaluate_codeblock(self, node, start=0, end=None):
if node is None:
return
if not isinstance(node, mparser.CodeBlockNode):
e = InvalidCode('Tried to execute a non-codeblock. Possibly a bug in the parser.')
e.lineno = node.lineno
e.colno = node.colno
raise e
statements = node.lines[start:end]
i = 0
while i < len(statements):
cur = statements[i]
try:
self.evaluate_statement(cur)
except Exception as e:
if not(hasattr(e, 'lineno')):
e.lineno = cur.lineno
e.colno = cur.colno
e.file = os.path.join(self.subdir, 'meson.build')
raise e
i += 1 # In THE FUTURE jump over blocks and stuff.
def evaluate_statement(self, cur):
if isinstance(cur, mparser.FunctionNode):
return self.function_call(cur)
elif isinstance(cur, mparser.AssignmentNode):
return self.assignment(cur)
elif isinstance(cur, mparser.MethodNode):
return self.method_call(cur)
elif isinstance(cur, mparser.StringNode):
return cur.value
elif isinstance(cur, mparser.BooleanNode):
return cur.value
elif isinstance(cur, mparser.IfClauseNode):
return self.evaluate_if(cur)
elif isinstance(cur, mparser.IdNode):
return self.get_variable(cur.value)
elif isinstance(cur, mparser.ComparisonNode):
return self.evaluate_comparison(cur)
elif isinstance(cur, mparser.ArrayNode):
return self.evaluate_arraystatement(cur)
elif isinstance(cur, mparser.NumberNode):
return cur.value
elif isinstance(cur, mparser.AndNode):
return self.evaluate_andstatement(cur)
elif isinstance(cur, mparser.OrNode):
return self.evaluate_orstatement(cur)
elif isinstance(cur, mparser.NotNode):
return self.evaluate_notstatement(cur)
elif isinstance(cur, mparser.UMinusNode):
return self.evaluate_uminusstatement(cur)
elif isinstance(cur, mparser.ArithmeticNode):
return self.evaluate_arithmeticstatement(cur)
elif isinstance(cur, mparser.ForeachClauseNode):
return self.evaluate_foreach(cur)
elif isinstance(cur, mparser.PlusAssignmentNode):
return self.evaluate_plusassign(cur)
elif isinstance(cur, mparser.IndexNode):
return self.evaluate_indexing(cur)
elif isinstance(cur, mparser.TernaryNode):
return self.evaluate_ternary(cur)
elif self.is_elementary_type(cur):
return cur
else:
raise InvalidCode("Unknown statement.")
def evaluate_arraystatement(self, cur):
(arguments, kwargs) = self.reduce_arguments(cur.args)
if len(kwargs) > 0:
raise InvalidCode('Keyword arguments are invalid in array construction.')
return arguments
def evaluate_notstatement(self, cur):
v = self.evaluate_statement(cur.value)
if isinstance(v, mparser.BooleanNode):
v = v.value
if not isinstance(v, bool):
raise InterpreterException('Argument to "not" is not a boolean.')
return not v
def evaluate_if(self, node):
assert(isinstance(node, mparser.IfClauseNode))
for i in node.ifs:
result = self.evaluate_statement(i.condition)
if not(isinstance(result, bool)):
raise InvalidCode('If clause {!r} does not evaluate to true or false.'.format(result))
if result:
self.evaluate_codeblock(i.block)
return
if not isinstance(node.elseblock, mparser.EmptyNode):
self.evaluate_codeblock(node.elseblock)
def evaluate_comparison(self, node):
v1 = self.evaluate_statement(node.left)
v2 = self.evaluate_statement(node.right)
if self.is_elementary_type(v1):
val1 = v1
else:
val1 = v1.value
if self.is_elementary_type(v2):
val2 = v2
else:
val2 = v2.value
if node.ctype == '==':
return val1 == val2
elif node.ctype == '!=':
return val1 != val2
elif node.ctype == '<':
return val1 < val2
elif node.ctype == '<=':
return val1 <= val2
elif node.ctype == '>':
return val1 > val2
elif node.ctype == '>=':
return val1 >= val2
else:
raise InvalidCode('You broke my compare eval.')
def evaluate_andstatement(self, cur):
l = self.evaluate_statement(cur.left)
if isinstance(l, mparser.BooleanNode):
l = l.value
if not isinstance(l, bool):
raise InterpreterException('First argument to "and" is not a boolean.')
if not l:
return False
r = self.evaluate_statement(cur.right)
if isinstance(r, mparser.BooleanNode):
r = r.value
if not isinstance(r, bool):
raise InterpreterException('Second argument to "and" is not a boolean.')
return r
def evaluate_orstatement(self, cur):
l = self.evaluate_statement(cur.left)
if isinstance(l, mparser.BooleanNode):
l = l.get_value()
if not isinstance(l, bool):
raise InterpreterException('First argument to "or" is not a boolean.')
if l:
return True
r = self.evaluate_statement(cur.right)
if isinstance(r, mparser.BooleanNode):
r = r.get_value()
if not isinstance(r, bool):
raise InterpreterException('Second argument to "or" is not a boolean.')
return r
def evaluate_uminusstatement(self, cur):
v = self.evaluate_statement(cur.value)
if isinstance(v, mparser.NumberNode):
v = v.value
if not isinstance(v, int):
raise InterpreterException('Argument to negation is not an integer.')
return -v
def evaluate_arithmeticstatement(self, cur):
l = self.to_native(self.evaluate_statement(cur.left))
r = self.to_native(self.evaluate_statement(cur.right))
if cur.operation == 'add':
try:
return l + r
except Exception as e:
raise InvalidCode('Invalid use of addition: ' + str(e))
elif cur.operation == 'sub':
if not isinstance(l, int) or not isinstance(r, int):
raise InvalidCode('Subtraction works only with integers.')
return l - r
elif cur.operation == 'mul':
if not isinstance(l, int) or not isinstance(r, int):
raise InvalidCode('Multiplication works only with integers.')
return l * r
elif cur.operation == 'div':
if not isinstance(l, int) or not isinstance(r, int):
raise InvalidCode('Division works only with integers.')
return l // r
elif cur.operation == 'mod':
if not isinstance(l, int) or not isinstance(r, int):
raise InvalidCode('Modulo works only with integers.')
return l % r
else:
raise InvalidCode('You broke me.')
def evaluate_ternary(self, node):
assert(isinstance(node, mparser.TernaryNode))
result = self.evaluate_statement(node.condition)
if not isinstance(result, bool):
raise InterpreterException('Ternary condition is not boolean.')
if result:
return self.evaluate_statement(node.trueblock)
else:
return self.evaluate_statement(node.falseblock)
def evaluate_foreach(self, node):
assert(isinstance(node, mparser.ForeachClauseNode))
varname = node.varname.value
items = self.evaluate_statement(node.items)
if not isinstance(items, list):
raise InvalidArguments('Items of foreach loop is not an array')
for item in items:
self.set_variable(varname, item)
self.evaluate_codeblock(node.block)
def evaluate_plusassign(self, node):
assert(isinstance(node, mparser.PlusAssignmentNode))
varname = node.var_name
addition = self.evaluate_statement(node.value)
# Remember that all variables are immutable. We must always create a
# full new variable and then assign it.
old_variable = self.get_variable(varname)
if isinstance(old_variable, str):
if not isinstance(addition, str):
raise InvalidArguments('The += operator requires a string on the right hand side if the variable on the left is a string')
new_value = old_variable + addition
elif isinstance(old_variable, int):
if not isinstance(addition, int):
raise InvalidArguments('The += operator requires an int on the right hand side if the variable on the left is an int')
new_value = old_variable + addition
elif not isinstance(old_variable, list):
raise InvalidArguments('The += operator currently only works with arrays, strings or ints ')
# Add other data types here.
else:
if isinstance(addition, list):
new_value = old_variable + addition
else:
new_value = old_variable + [addition]
self.set_variable(varname, new_value)
def evaluate_indexing(self, node):
assert(isinstance(node, mparser.IndexNode))
iobject = self.evaluate_statement(node.iobject)
if not isinstance(iobject, list):
raise InterpreterException('Tried to index a non-array object.')
index = self.evaluate_statement(node.index)
if not isinstance(index, int):
raise InterpreterException('Index value is not an integer.')
if index < -len(iobject) or index >= len(iobject):
raise InterpreterException('Index %d out of bounds of array of size %d.' % (index, len(iobject)))
return iobject[index]
def function_call(self, node):
func_name = node.func_name
(posargs, kwargs) = self.reduce_arguments(node.args)
if func_name in self.funcs:
return self.funcs[func_name](node, self.flatten(posargs), kwargs)
else:
self.unknown_function_called(func_name)
def method_call(self, node):
invokable = node.source_object
if isinstance(invokable, mparser.IdNode):
object_name = invokable.value
obj = self.get_variable(object_name)
else:
obj = self.evaluate_statement(invokable)
method_name = node.name
args = node.args
if isinstance(obj, mparser.StringNode):
obj = obj.get_value()
if isinstance(obj, str):
return self.string_method_call(obj, method_name, args)
if isinstance(obj, bool):
return self.bool_method_call(obj, method_name, args)
if isinstance(obj, int):
return self.int_method_call(obj, method_name, args)
if isinstance(obj, list):
return self.array_method_call(obj, method_name, self.reduce_arguments(args)[0])
if not isinstance(obj, InterpreterObject):
raise InvalidArguments('Variable "%s" is not callable.' % object_name)
(args, kwargs) = self.reduce_arguments(args)
if method_name == 'extract_objects':
self.validate_extraction(obj.held_object)
return obj.method_call(method_name, self.flatten(args), kwargs)
def bool_method_call(self, obj, method_name, args):
obj = self.to_native(obj)
(posargs, _) = self.reduce_arguments(args)
if method_name == 'to_string':
if len(posargs) == 0:
if obj == True:
return 'true'
else:
return 'false'
elif len(posargs) == 2 and isinstance(posargs[0], str) and isinstance(posargs[1], str):
if obj == True:
return posargs[0]
else:
return posargs[1]
else:
raise InterpreterException('bool.to_string() must have either no arguments or exactly two string arguments that signify what values to return for true and false.')
elif method_name == 'to_int':
if obj == True:
return 1
else:
return 0
else:
raise InterpreterException('Unknown method "%s" for a boolean.' % method_name)
def int_method_call(self, obj, method_name, args):
obj = self.to_native(obj)
(posargs, _) = self.reduce_arguments(args)
if method_name == 'is_even':
if len(posargs) == 0:
return obj % 2 == 0
else:
raise InterpreterException('int.is_even() must have no arguments.')
elif method_name == 'is_odd':
if len(posargs) == 0:
return obj % 2 != 0
else:
raise InterpreterException('int.is_odd() must have no arguments.')
else:
raise InterpreterException('Unknown method "%s" for an integer.' % method_name)
def string_method_call(self, obj, method_name, args):
obj = self.to_native(obj)
(posargs, _) = self.reduce_arguments(args)
if method_name == 'strip':
return obj.strip()
elif method_name == 'format':
return self.format_string(obj, args)
elif method_name == 'to_upper':
return obj.upper()
elif method_name == 'to_lower':
return obj.lower()
elif method_name == 'underscorify':
return re.sub(r'[^a-zA-Z0-9]', '_', obj)
elif method_name == 'split':
if len(posargs) > 1:
raise InterpreterException('Split() must have at most one argument.')
elif len(posargs) == 1:
s = posargs[0]
if not isinstance(s, str):
raise InterpreterException('Split() argument must be a string')
return obj.split(s)
else:
return obj.split()
elif method_name == 'startswith' or method_name == 'contains' or method_name == 'endswith':
s = posargs[0]
if not isinstance(s, str):
raise InterpreterException('Argument must be a string.')
if method_name == 'startswith':
return obj.startswith(s)
elif method_name == 'contains':
return obj.find(s) >= 0
return obj.endswith(s)
elif method_name == 'to_int':
try:
return int(obj)
except Exception:
raise InterpreterException('String {!r} cannot be converted to int'.format(obj))
elif method_name == 'join':
if len(posargs) != 1:
raise InterpreterException('Join() takes exactly one argument.')
strlist = posargs[0]
check_stringlist(strlist)
return obj.join(strlist)
elif method_name == 'version_compare':
if len(posargs) != 1:
raise InterpreterException('Version_compare() takes exactly one argument.')
cmpr = posargs[0]
if not isinstance(cmpr, str):
raise InterpreterException('Version_compare() argument must be a string.')
return mesonlib.version_compare(obj, cmpr)
raise InterpreterException('Unknown method "%s" for a string.' % method_name)
def unknown_function_called(self, func_name):
raise InvalidCode('Unknown function "%s".' % func_name)
def array_method_call(self, obj, method_name, args):
if method_name == 'contains':
return self.check_contains(obj, args)
elif method_name == 'length':
return len(obj)
elif method_name == 'get':
index = args[0]
if not isinstance(index, int):
raise InvalidArguments('Array index must be a number.')
if index < -len(obj) or index >= len(obj):
raise InvalidArguments('Array index %s is out of bounds for array of size %d.' % (index, len(obj)))
return obj[index]
raise InterpreterException('Arrays do not have a method called "%s".' % method_name)
def reduce_arguments(self, args):
assert(isinstance(args, mparser.ArgumentNode))
if args.incorrect_order():
raise InvalidArguments('All keyword arguments must be after positional arguments.')
reduced_pos = [self.evaluate_statement(arg) for arg in args.arguments]
reduced_kw = {}
for key in args.kwargs.keys():
if not isinstance(key, str):
raise InvalidArguments('Keyword argument name is not a string.')
a = args.kwargs[key]
reduced_kw[key] = self.evaluate_statement(a)
if not isinstance(reduced_pos, list):
reduced_pos = [reduced_pos]
return (reduced_pos, reduced_kw)
def flatten(self, args):
if isinstance(args, mparser.StringNode):
return args.value
if isinstance(args, (int, str, InterpreterObject)):
return args
result = []
for a in args:
if isinstance(a, list):
rest = self.flatten(a)
result = result + rest
elif isinstance(a, mparser.StringNode):
result.append(a.value)
else:
result.append(a)
return result
def assignment(self, node):
assert(isinstance(node, mparser.AssignmentNode))
var_name = node.var_name
if not isinstance(var_name, str):
raise InvalidArguments('Tried to assign value to a non-variable.')
value = self.evaluate_statement(node.value)
value = self.to_native(value)
if not self.is_assignable(value):
raise InvalidCode('Tried to assign an invalid value to variable.')
# For mutable objects we need to make a copy on assignment
if isinstance(value, MutableInterpreterObject):
value = copy.deepcopy(value)
self.set_variable(var_name, value)
return value
def set_variable(self, varname, variable):
if variable is None:
raise InvalidCode('Can not assign None to variable.')
if not isinstance(varname, str):
raise InvalidCode('First argument to set_variable must be a string.')
if not self.is_assignable(variable):
raise InvalidCode('Assigned value not of assignable type.')
if re.match('[_a-zA-Z][_0-9a-zA-Z]*$', varname) is None:
raise InvalidCode('Invalid variable name: ' + varname)
if varname in self.builtin:
raise InvalidCode('Tried to overwrite internal variable "%s"' % varname)
self.variables[varname] = variable
def get_variable(self, varname):
if varname in self.builtin:
return self.builtin[varname]
if varname in self.variables:
return self.variables[varname]
raise InvalidCode('Unknown variable "%s".' % varname)
def to_native(self, arg):
if isinstance(arg, (mparser.StringNode, mparser.NumberNode,
mparser.BooleanNode)):
return arg.value
return arg
def is_assignable(self, value):
return isinstance(value, (InterpreterObject, dependencies.Dependency,
str, int, list, mesonlib.File))
def func_build_target(self, node, args, kwargs):
if 'target_type' not in kwargs:
raise InterpreterException('Missing target_type keyword argument')
target_type = kwargs.pop('target_type')
if target_type == 'executable':
return self.func_executable(node, args, kwargs)
elif target_type == 'shared_library':
return self.func_shared_lib(node, args, kwargs)
elif target_type == 'static_library':
return self.func_static_lib(node, args, kwargs)
elif target_type == 'library':
return self.func_library(node, args, kwargs)
elif target_type == 'jar':
return self.func_jar(node, args, kwargs)
else:
raise InterpreterException('Unknown target_type.')
def func_set_variable(self, node, args, kwargs):
if len(args) != 2:
raise InvalidCode('Set_variable takes two arguments.')
varname = args[0]
value = self.to_native(args[1])
self.set_variable(varname, value)
# @noKwargs
def func_get_variable(self, node, args, kwargs):
if len(args)<1 or len(args)>2:
raise InvalidCode('Get_variable takes one or two arguments.')
varname = args[0]
if not isinstance(varname, str):
raise InterpreterException('First argument must be a string.')
try:
return self.variables[varname]
except KeyError:
pass
if len(args) == 2:
return args[1]
raise InterpreterException('Tried to get unknown variable "%s".' % varname)
@stringArgs
@noKwargs
def func_is_variable(self, node, args, kwargs):
if len(args) != 1:
raise InvalidCode('Is_variable takes two arguments.')
varname = args[0]
return varname in self.variables
def is_elementary_type(self, v):
return isinstance(v, (int, float, str, bool, list))

@ -22,10 +22,12 @@ class ParseException(MesonException):
self.colno = colno self.colno = colno
class Token: class Token:
def __init__(self, tid, lineno, colno, value): def __init__(self, tid, subdir, lineno, colno, bytespan, value):
self.tid = tid self.tid = tid
self.subdir = subdir
self.lineno = lineno self.lineno = lineno
self.colno = colno self.colno = colno
self.bytespan = bytespan
self.value = value self.value = value
def __eq__(self, other): def __eq__(self, other):
@ -71,7 +73,7 @@ class Lexer:
('questionmark', re.compile(r'\?')), ('questionmark', re.compile(r'\?')),
] ]
def lex(self, code): def lex(self, code, subdir):
lineno = 1 lineno = 1
line_start = 0 line_start = 0
loc = 0; loc = 0;
@ -87,7 +89,10 @@ class Lexer:
curline = lineno curline = lineno
col = mo.start()-line_start col = mo.start()-line_start
matched = True matched = True
span_start = loc
loc = mo.end() loc = mo.end()
span_end = loc
bytespan = (span_start, span_end)
match_text = mo.group() match_text = mo.group()
if tid == 'ignore' or tid == 'comment': if tid == 'ignore' or tid == 'comment':
break break
@ -123,40 +128,41 @@ class Lexer:
tid = match_text tid = match_text
else: else:
value = match_text value = match_text
yield Token(tid, curline, col, value) yield Token(tid, subdir, curline, col, bytespan, value)
break break
if not matched: if not matched:
raise ParseException('lexer', lineno, col) raise ParseException('lexer', lineno, col)
class BooleanNode: class ElementaryNode:
def __init__(self, token, value): def __init__(self, token):
self.lineno = token.lineno self.lineno = token.lineno
self.subdir = token.subdir
self.colno = token.colno self.colno = token.colno
self.value = token.value
self.bytespan = token.bytespan
class BooleanNode(ElementaryNode):
def __init__(self, token, value):
super().__init__(token)
self.value = value self.value = value
assert(isinstance(self.value, bool)) assert(isinstance(self.value, bool))
class IdNode: class IdNode(ElementaryNode):
def __init__(self, token): def __init__(self, token):
self.lineno = token.lineno super().__init__(token)
self.colno = token.colno
self.value = token.value
assert(isinstance(self.value, str)) assert(isinstance(self.value, str))
def __str__(self): def __str__(self):
return "Id node: '%s' (%d, %d)." % (self.value, self.lineno, self.colno) return "Id node: '%s' (%d, %d)." % (self.value, self.lineno, self.colno)
class NumberNode: class NumberNode(ElementaryNode):
def __init__(self, token): def __init__(self, token):
self.lineno = token.lineno super().__init__(token)
self.colno = token.colno
self.value = token.value
assert(isinstance(self.value, int)) assert(isinstance(self.value, int))
class StringNode: class StringNode(ElementaryNode):
def __init__(self, token): def __init__(self, token):
self.lineno = token.lineno super().__init__(token)
self.colno = token.colno
self.value = token.value
assert(isinstance(self.value, str)) assert(isinstance(self.value, str))
def __str__(self): def __str__(self):
@ -164,20 +170,23 @@ class StringNode:
class ArrayNode: class ArrayNode:
def __init__(self, args): def __init__(self, args):
self.subdir = args.subdir
self.lineno = args.lineno self.lineno = args.lineno
self.colno = args.colno self.colno = args.colno
self.args = args self.args = args
class EmptyNode: class EmptyNode:
def __init__(self): def __init__(self):
self.subdir =''
self.lineno = 0 self.lineno = 0
self.colno = 0 self.colno = 0
self.value = None self.value = None
class OrNode: class OrNode:
def __init__(self, lineno, colno, left, right): def __init__(self, left, right):
self.lineno = lineno self.subdir = left.subdir
self.colno = colno self.lineno = left.lineno
self.colno = left.colno
self.left = left self.left = left
self.right = right self.right = right
@ -189,42 +198,48 @@ class AndNode:
self.right = right self.right = right
class ComparisonNode: class ComparisonNode:
def __init__(self, lineno, colno, ctype, left, right): def __init__(self, ctype, left, right):
self.lineno = lineno self.lineno = left.lineno
self.colno = colno self.colno = left.colno
self.subdir = left.subdir
self.left = left self.left = left
self.right = right self.right = right
self.ctype = ctype self.ctype = ctype
class ArithmeticNode: class ArithmeticNode:
def __init__(self, lineno, colno, operation, left, right): def __init__(self,operation, left, right):
self.lineno = lineno self.subdir = left.subdir
self.colno = colno self.lineno = left.lineno
self.colno = left.colno
self.left = left self.left = left
self.right = right self.right = right
self.operation = operation self.operation = operation
class NotNode: class NotNode:
def __init__(self, lineno, colno, value): def __init__(self, location_node, value):
self.lineno = lineno self.subdir = location_node.subdir
self.colno = colno self.lineno = location_node.lineno
self.colno = location_node.colno
self.value = value self.value = value
class CodeBlockNode: class CodeBlockNode:
def __init__(self, lineno, colno): def __init__(self, location_node):
self.lineno = lineno self.subdir = location_node.subdir
self.colno = colno self.lineno = location_node.lineno
self.colno = location_node.colno
self.lines = [] self.lines = []
class IndexNode: class IndexNode:
def __init__(self, iobject, index): def __init__(self, iobject, index):
self.iobject = iobject self.iobject = iobject
self.index = index self.index = index
self.subdir = iobject.subdir
self.lineno = iobject.lineno self.lineno = iobject.lineno
self.colno = iobject.colno self.colno = iobject.colno
class MethodNode: class MethodNode:
def __init__(self, lineno, colno, source_object, name, args): def __init__(self, subdir, lineno, colno, source_object, name, args):
self.subdir = subdir
self.lineno = lineno self.lineno = lineno
self.colno = colno self.colno = colno
self.source_object = source_object self.source_object = source_object
@ -233,7 +248,8 @@ class MethodNode:
self.args = args self.args = args
class FunctionNode: class FunctionNode:
def __init__(self, lineno, colno, func_name, args): def __init__(self, subdir, lineno, colno, func_name, args):
self.subdir = subdir
self.lineno = lineno self.lineno = lineno
self.colno = colno self.colno = colno
self.func_name = func_name self.func_name = func_name
@ -272,9 +288,10 @@ class IfClauseNode():
self.elseblock = EmptyNode() self.elseblock = EmptyNode()
class UMinusNode(): class UMinusNode():
def __init__(self, lineno, colno, value): def __init__(self, current_location, value):
self.lineno = lineno self.subdir = current_location.subdir
self.colno = colno self.lineno = current_location.lineno
self.colno = current_location.colno
self.value = value self.value = value
class IfNode(): class IfNode():
@ -296,7 +313,9 @@ class ArgumentNode():
def __init__(self, token): def __init__(self, token):
self.lineno = token.lineno self.lineno = token.lineno
self.colno = token.colno self.colno = token.colno
self.subdir = token.subdir
self.arguments = [] self.arguments = []
self.commas = []
self.kwargs = {} self.kwargs = {}
self.order_error = False self.order_error = False
@ -351,8 +370,8 @@ comparison_map = {'equal': '==',
# 9 plain token # 9 plain token
class Parser: class Parser:
def __init__(self, code): def __init__(self, code, subdir):
self.stream = Lexer().lex(code) self.stream = Lexer().lex(code, subdir)
self.getsym() self.getsym()
self.in_ternary = False self.in_ternary = False
@ -360,7 +379,7 @@ class Parser:
try: try:
self.current = next(self.stream) self.current = next(self.stream)
except StopIteration: except StopIteration:
self.current = Token('eof', 0, 0, None) self.current = Token('eof', '', 0, 0, (0, 0), None)
def accept(self, s): def accept(self, s):
if self.current.tid == s: if self.current.tid == s:
@ -409,7 +428,7 @@ class Parser:
def e2(self): def e2(self):
left = self.e3() left = self.e3()
while self.accept('or'): while self.accept('or'):
left = OrNode(left.lineno, left.colno, left, self.e3()) left = OrNode(left, self.e3())
return left return left
def e3(self): def e3(self):
@ -422,7 +441,7 @@ class Parser:
left = self.e5() left = self.e5()
for nodename, operator_type in comparison_map.items(): for nodename, operator_type in comparison_map.items():
if self.accept(nodename): if self.accept(nodename):
return ComparisonNode(left.lineno, left.colno, operator_type, left, self.e5()) return ComparisonNode(operator_type, left, self.e5())
return left return left
def e5(self): def e5(self):
@ -431,38 +450,38 @@ class Parser:
def e5add(self): def e5add(self):
left = self.e5sub() left = self.e5sub()
if self.accept('plus'): if self.accept('plus'):
return ArithmeticNode(left.lineno, left.colno, 'add', left, self.e5add()) return ArithmeticNode('add', left, self.e5add())
return left return left
def e5sub(self): def e5sub(self):
left = self.e5mod() left = self.e5mod()
if self.accept('dash'): if self.accept('dash'):
return ArithmeticNode(left.lineno, left.colno, 'sub', left, self.e5sub()) return ArithmeticNode('sub', left, self.e5sub())
return left return left
def e5mod(self): def e5mod(self):
left = self.e5mul() left = self.e5mul()
if self.accept('percent'): if self.accept('percent'):
return ArithmeticNode(left.lineno, left.colno, 'mod', left, self.e5mod()) return ArithmeticNode('mod', left, self.e5mod())
return left return left
def e5mul(self): def e5mul(self):
left = self.e5div() left = self.e5div()
if self.accept('star'): if self.accept('star'):
return ArithmeticNode(left.lineno, left.colno, 'mul', left, self.e5mul()) return ArithmeticNode('mul', left, self.e5mul())
return left return left
def e5div(self): def e5div(self):
left = self.e6() left = self.e6()
if self.accept('fslash'): if self.accept('fslash'):
return ArithmeticNode(left.lineno, left.colno, 'div', left, self.e5div()) return ArithmeticNode('div', left, self.e5div())
return left return left
def e6(self): def e6(self):
if self.accept('not'): if self.accept('not'):
return NotNode(self.current.lineno, self.current.colno, self.e7()) return NotNode(self.current, self.e7())
if self.accept('dash'): if self.accept('dash'):
return UMinusNode(self.current.lineno, self.current.colno, self.e7()) return UMinusNode(self.current, self.e7())
return self.e7() return self.e7()
def e7(self): def e7(self):
@ -473,7 +492,7 @@ class Parser:
if not isinstance(left, IdNode): if not isinstance(left, IdNode):
raise ParseException('Function call must be applied to plain id', raise ParseException('Function call must be applied to plain id',
left.lineno, left.colno) left.lineno, left.colno)
left = FunctionNode(left.lineno, left.colno, left.value, args) left = FunctionNode(left.subdir, left.lineno, left.colno, left.value, args)
go_again = True go_again = True
while go_again: while go_again:
go_again = False go_again = False
@ -516,15 +535,19 @@ class Parser:
a = ArgumentNode(s) a = ArgumentNode(s)
while not isinstance(s, EmptyNode): while not isinstance(s, EmptyNode):
potential = self.current
if self.accept('comma'): if self.accept('comma'):
a.commas.append(potential)
a.append(s) a.append(s)
elif self.accept('colon'): elif self.accept('colon'):
if not isinstance(s, IdNode): if not isinstance(s, IdNode):
raise ParseException('Keyword argument must be a plain identifier.', raise ParseException('Keyword argument must be a plain identifier.',
s.lineno, s.colno) s.lineno, s.colno)
a.set_kwarg(s.value, self.statement()) a.set_kwarg(s.value, self.statement())
potential = self.current
if not self.accept('comma'): if not self.accept('comma'):
return a return a
a.commas.append(potential)
else: else:
a.append(s) a.append(s)
return a return a
@ -539,7 +562,7 @@ class Parser:
self.expect('lparen') self.expect('lparen')
args = self.args() args = self.args()
self.expect('rparen') self.expect('rparen')
method = MethodNode(methodname.lineno, methodname.colno, source_object, methodname.value, args) method = MethodNode(methodname.subdir, methodname.lineno, methodname.colno, source_object, methodname.value, args)
if self.accept('dot'): if self.accept('dot'):
return self.method_call(method) return self.method_call(method)
return method return method
@ -593,7 +616,7 @@ class Parser:
return self.statement() return self.statement()
def codeblock(self): def codeblock(self):
block = CodeBlockNode(self.current.lineno, self.current.colno) block = CodeBlockNode(self.current)
cond = True cond = True
while cond: while cond:
curline = self.line() curline = self.line()

@ -79,7 +79,7 @@ class OptionInterpreter:
def process(self, option_file): def process(self, option_file):
try: try:
with open(option_file, 'r', encoding='utf8') as f: with open(option_file, 'r', encoding='utf8') as f:
ast = mparser.Parser(f.read()).parse() ast = mparser.Parser(f.read(), '').parse()
except mesonlib.MesonException as me: except mesonlib.MesonException as me:
me.file = option_file me.file = option_file
raise me raise me

@ -0,0 +1,64 @@
#!/usr/bin/env python3
# Copyright 2016 The Meson development team
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# This class contains the basic functionality needed to run any interpreter
# or an interpreter-based tool.
# This tool is used to manipulate an existing Meson build definition.
#
# - add a file to a target
# - remove files from a target
# - move targets
# - reindent?
import mesonbuild.astinterpreter
from mesonbuild.mesonlib import MesonException
from mesonbuild import mlog
import sys, traceback
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--sourcedir', default='.',
help='Path to source directory.')
parser.add_argument('--target', default=None,
help='Name of target to edit.')
parser.add_argument('--filename', default=None,
help='Name of source file to add or remove to target.')
parser.add_argument('commands', nargs='+')
if __name__ == '__main__':
options = parser.parse_args()
if options.target is None or options.filename is None:
sys.exit("Must specify both target and filename.")
print('This tool is highly experimental, use with care.')
rewriter = mesonbuild.astinterpreter.AstInterpreter(options.sourcedir, '')
try:
if options.commands[0] == 'add':
rewriter.add_source(options.target, options.filename)
elif options.commands[0] == 'remove':
rewriter.remove_source(options.target, options.filename)
else:
sys.exit('Unknown command: ' + options.commands[0])
except Exception as e:
if isinstance(e, MesonException):
if hasattr(e, 'file') and hasattr(e, 'lineno') and hasattr(e, 'colno'):
mlog.log(mlog.red('\nMeson encountered an error in file %s, line %d, column %d:' % (e.file, e.lineno, e.colno)))
else:
mlog.log(mlog.red('\nMeson encountered an error:'))
mlog.log(e)
else:
traceback.print_exc()
sys.exit(1)

@ -341,5 +341,63 @@ class LinuxlikeTests(unittest.TestCase):
Oargs = [arg for arg in cmd if arg.startswith('-O')] Oargs = [arg for arg in cmd if arg.startswith('-O')]
self.assertEqual(Oargs, [Oflag, '-O0']) self.assertEqual(Oargs, [Oflag, '-O0'])
class RewriterTests(unittest.TestCase):
def setUp(self):
super().setUp()
src_root = os.path.dirname(__file__)
self.testroot = tempfile.mkdtemp()
self.rewrite_command = [sys.executable, os.path.join(src_root, 'mesonrewriter.py')]
self.tmpdir = tempfile.mkdtemp()
self.workdir = os.path.join(self.tmpdir, 'foo')
self.test_dir = os.path.join(src_root, 'test cases/rewrite')
def tearDown(self):
shutil.rmtree(self.tmpdir)
def read_contents(self, fname):
with open(os.path.join(self.workdir, fname)) as f:
return f.read()
def check_effectively_same(self, mainfile, truth):
mf = self.read_contents(mainfile)
t = self.read_contents(truth)
# Rewriting is not guaranteed to do a perfect job of
# maintaining whitespace.
self.assertEqual(mf.replace(' ', ''), t.replace(' ', ''))
def prime(self, dirname):
shutil.copytree(os.path.join(self.test_dir, dirname), self.workdir)
def test_basic(self):
self.prime('1 basic')
subprocess.check_output(self.rewrite_command + ['remove',
'--target=trivialprog',
'--filename=notthere.c',
'--sourcedir', self.workdir])
self.check_effectively_same('meson.build', 'removed.txt')
subprocess.check_output(self.rewrite_command + ['add',
'--target=trivialprog',
'--filename=notthere.c',
'--sourcedir', self.workdir])
self.check_effectively_same('meson.build', 'added.txt')
subprocess.check_output(self.rewrite_command + ['remove',
'--target=trivialprog',
'--filename=notthere.c',
'--sourcedir', self.workdir])
self.check_effectively_same('meson.build', 'removed.txt')
def test_subdir(self):
self.prime('2 subdirs')
top = self.read_contents('meson.build')
s2 = self.read_contents('sub2/meson.build')
subprocess.check_output(self.rewrite_command + ['remove',
'--target=something',
'--filename=second.c',
'--sourcedir', self.workdir])
self.check_effectively_same('sub1/meson.build', 'sub1/after.txt')
self.assertEqual(top, self.read_contents('meson.build'))
self.assertEqual(s2, self.read_contents('sub2/meson.build'))
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()

@ -0,0 +1,5 @@
project('rewritetest', 'c')
sources = ['trivial.c']
exe = executable('trivialprog', 'notthere.c', sources)

@ -0,0 +1,5 @@
project('rewritetest', 'c')
sources = ['trivial.c', 'notthere.c']
exe = executable('trivialprog', sources)

@ -0,0 +1,5 @@
project('rewritetest', 'c')
sources = ['trivial.c']
exe = executable('trivialprog', sources)

@ -0,0 +1,5 @@
project('subdir rewrite', 'c')
subdir('sub1')
subdir('sub2')

@ -0,0 +1 @@
srcs = ['first.c', 'second.c']

@ -0,0 +1,2 @@
executable('something', srcs)
Loading…
Cancel
Save