Merge pull request #6627 from jon-turney/cwd-relative-file-locations

Consistently report file locations relative to cwd
pull/6718/head
Jussi Pakkanen 5 years ago committed by GitHub
commit 25cbcb19a9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 9
      docs/markdown/snippets/consistent_file_locations.md
  2. 4
      mesonbuild/ast/interpreter.py
  3. 4
      mesonbuild/interpreter.py
  4. 4
      mesonbuild/interpreterbase.py
  5. 17
      mesonbuild/mesonlib.py
  6. 16
      mesonbuild/mlog.py
  7. 70
      mesonbuild/mparser.py
  8. 7
      mesonbuild/optinterpreter.py
  9. 35
      mesonbuild/rewriter.py
  10. 51
      run_unittests.py
  11. 2
      test cases/failing/100 subdir parse error/meson.build
  12. 1
      test cases/failing/100 subdir parse error/subdir/meson.build
  13. 1
      test cases/failing/101 invalid option file/meson.build
  14. 1
      test cases/failing/101 invalid option file/meson_options.txt

@ -0,0 +1,9 @@
## Consistently report file locations relative to cwd
The paths for filenames in error and warning locations are now consistently
reported relative to the current working directory (when possible), or as
absolute paths (when a relative path does not exist, e.g. a Windows path
starting with a different drive letter to the current working directory).
(The previous behaviour was to report a path relative to the source root for all
warnings and most errors, and relative to cwd for certain parser errors)

@ -154,9 +154,9 @@ class AstInterpreter(interpreterbase.InterpreterBase):
code = f.read()
assert(isinstance(code, str))
try:
codeblock = mparser.Parser(code, subdir).parse()
codeblock = mparser.Parser(code, absname).parse()
except mesonlib.MesonException as me:
me.file = buildfilename
me.file = absname
raise me
self.subdir = subdir

@ -3748,9 +3748,9 @@ This will become a hard error in the future.''' % kwargs['input'], location=self
code = f.read()
assert(isinstance(code, str))
try:
codeblock = mparser.Parser(code, self.subdir).parse()
codeblock = mparser.Parser(code, absname).parse()
except mesonlib.MesonException as me:
me.file = buildfilename
me.file = absname
raise me
try:
self.evaluate_codeblock(codeblock)

@ -381,7 +381,7 @@ class InterpreterBase:
raise InvalidCode('Builder file is empty.')
assert(isinstance(code, str))
try:
self.ast = mparser.Parser(code, self.subdir).parse()
self.ast = mparser.Parser(code, mesonfile).parse()
except mesonlib.MesonException as me:
me.file = mesonfile
raise me
@ -432,7 +432,7 @@ class InterpreterBase:
if not hasattr(e, 'lineno'):
e.lineno = cur.lineno
e.colno = cur.colno
e.file = os.path.join(self.subdir, 'meson.build')
e.file = os.path.join(self.source_root, self.subdir, environment.build_filename)
raise e
i += 1 # In THE FUTURE jump over blocks and stuff.

@ -122,13 +122,6 @@ an_unpicklable_object = threading.Lock()
class MesonException(Exception):
'''Exceptions thrown by Meson'''
def get_msg_with_context(self):
s = ''
if hasattr(self, 'lineno') and hasattr(self, 'file'):
s = get_error_location_string(self.file, self.lineno) + ' '
s += str(self)
return s
class EnvironmentException(MesonException):
'''Exceptions thrown while processing and creating the build environment'''
@ -1339,16 +1332,6 @@ def detect_subprojects(spdir_name, current_dir='', result=None):
result[basename] = [trial]
return result
# This isn't strictly correct. What we really want here is something like:
# class StringProtocol(typing_extensions.Protocol):
#
# def __str__(self) -> str: ...
#
# This would more accurately embody what this funcitonc an handle, but we
# don't have that yet, so instead we'll do some casting to work around it
def get_error_location_string(fname: str, lineno: str) -> str:
return '{}:{}:'.format(fname, lineno)
def substring_is_in_list(substr: str, strlist: T.List[str]) -> bool:
for s in strlist:
if substr in s:

@ -221,11 +221,19 @@ def log_once(*args: T.Union[str, AnsiDecorator], is_error: bool = False,
_logged_once.add(t)
log(*args, is_error=is_error, **kwargs)
# This isn't strictly correct. What we really want here is something like:
# class StringProtocol(typing_extensions.Protocol):
#
# def __str__(self) -> str: ...
#
# This would more accurately embody what this function can handle, but we
# don't have that yet, so instead we'll do some casting to work around it
def get_error_location_string(fname: str, lineno: str) -> str:
return '{}:{}:'.format(fname, lineno)
def _log_error(severity: str, *rargs: T.Union[str, AnsiDecorator],
once: bool = False, **kwargs: T.Any) -> None:
from .mesonlib import get_error_location_string
from .environment import build_filename
from .mesonlib import MesonException
from .mesonlib import MesonException, relpath
# The typing requirements here are non-obvious. Lists are invariant,
# therefore T.List[A] and T.List[T.Union[A, B]] are not able to be joined
@ -242,7 +250,7 @@ def _log_error(severity: str, *rargs: T.Union[str, AnsiDecorator],
location = kwargs.pop('location', None)
if location is not None:
location_file = os.path.join(location.subdir, build_filename)
location_file = relpath(location.filename, os.getcwd())
location_str = get_error_location_string(location_file, location.lineno)
# Unions are frankly awful, and we have to T.cast here to get mypy
# to understand that the list concatenation is safe

@ -72,9 +72,9 @@ class BlockParseException(MesonException):
self.colno = colno
class Token:
def __init__(self, tid, subdir, line_start, lineno, colno, bytespan, value):
def __init__(self, tid, filename, line_start, lineno, colno, bytespan, value):
self.tid = tid
self.subdir = subdir
self.filename = filename
self.line_start = line_start
self.lineno = lineno
self.colno = colno
@ -132,7 +132,7 @@ class Lexer:
def getline(self, line_start):
return self.code[line_start:self.code.find('\n', line_start)]
def lex(self, subdir):
def lex(self, filename):
line_start = 0
lineno = 1
loc = 0
@ -205,9 +205,9 @@ This will become a hard error in a future Meson release.""", self.getline(line_s
else:
if match_text in self.future_keywords:
mlog.warning("Identifier '{}' will become a reserved keyword in a future release. Please rename it.".format(match_text),
location=types.SimpleNamespace(subdir=subdir, lineno=lineno))
location=types.SimpleNamespace(filename=filename, lineno=lineno))
value = match_text
yield Token(tid, subdir, curline_start, curline, col, bytespan, value)
yield Token(tid, filename, curline_start, curline, col, bytespan, value)
break
if not matched:
raise ParseException('lexer', self.getline(line_start), lineno, col)
@ -223,7 +223,7 @@ class BaseNode:
class ElementaryNode(BaseNode):
def __init__(self, token):
self.lineno = token.lineno
self.subdir = token.subdir
self.filename = token.filename
self.colno = token.colno
self.value = token.value
self.bytespan = token.bytespan
@ -263,7 +263,7 @@ class BreakNode(ElementaryNode):
class ArrayNode(BaseNode):
def __init__(self, args, lineno, colno, end_lineno, end_colno):
self.subdir = args.subdir
self.filename = args.filename
self.lineno = lineno
self.colno = colno
self.end_lineno = end_lineno
@ -272,7 +272,7 @@ class ArrayNode(BaseNode):
class DictNode(BaseNode):
def __init__(self, args, lineno, colno, end_lineno, end_colno):
self.subdir = args.subdir
self.filename = args.filename
self.lineno = lineno
self.colno = colno
self.end_lineno = end_lineno
@ -281,14 +281,14 @@ class DictNode(BaseNode):
class EmptyNode(BaseNode):
def __init__(self, lineno, colno):
self.subdir = ''
self.filename = ''
self.lineno = lineno
self.colno = colno
self.value = None
class OrNode(BaseNode):
def __init__(self, left, right):
self.subdir = left.subdir
self.filename = left.filename
self.lineno = left.lineno
self.colno = left.colno
self.left = left
@ -296,7 +296,7 @@ class OrNode(BaseNode):
class AndNode(BaseNode):
def __init__(self, left, right):
self.subdir = left.subdir
self.filename = left.filename
self.lineno = left.lineno
self.colno = left.colno
self.left = left
@ -306,14 +306,14 @@ class ComparisonNode(BaseNode):
def __init__(self, ctype, left, right):
self.lineno = left.lineno
self.colno = left.colno
self.subdir = left.subdir
self.filename = left.filename
self.left = left
self.right = right
self.ctype = ctype
class ArithmeticNode(BaseNode):
def __init__(self, operation, left, right):
self.subdir = left.subdir
self.filename = left.filename
self.lineno = left.lineno
self.colno = left.colno
self.left = left
@ -322,14 +322,14 @@ class ArithmeticNode(BaseNode):
class NotNode(BaseNode):
def __init__(self, location_node, value):
self.subdir = location_node.subdir
self.filename = location_node.filename
self.lineno = location_node.lineno
self.colno = location_node.colno
self.value = value
class CodeBlockNode(BaseNode):
def __init__(self, location_node):
self.subdir = location_node.subdir
self.filename = location_node.filename
self.lineno = location_node.lineno
self.colno = location_node.colno
self.lines = []
@ -338,13 +338,13 @@ class IndexNode(BaseNode):
def __init__(self, iobject, index):
self.iobject = iobject
self.index = index
self.subdir = iobject.subdir
self.filename = iobject.filename
self.lineno = iobject.lineno
self.colno = iobject.colno
class MethodNode(BaseNode):
def __init__(self, subdir, lineno, colno, source_object, name, args):
self.subdir = subdir
def __init__(self, filename, lineno, colno, source_object, name, args):
self.filename = filename
self.lineno = lineno
self.colno = colno
self.source_object = source_object
@ -353,8 +353,8 @@ class MethodNode(BaseNode):
self.args = args
class FunctionNode(BaseNode):
def __init__(self, subdir, lineno, colno, end_lineno, end_colno, func_name, args):
self.subdir = subdir
def __init__(self, filename, lineno, colno, end_lineno, end_colno, func_name, args):
self.filename = filename
self.lineno = lineno
self.colno = colno
self.end_lineno = end_lineno
@ -364,8 +364,8 @@ class FunctionNode(BaseNode):
self.args = args
class AssignmentNode(BaseNode):
def __init__(self, subdir, lineno, colno, var_name, value):
self.subdir = subdir
def __init__(self, filename, lineno, colno, var_name, value):
self.filename = filename
self.lineno = lineno
self.colno = colno
self.var_name = var_name
@ -373,8 +373,8 @@ class AssignmentNode(BaseNode):
self.value = value
class PlusAssignmentNode(BaseNode):
def __init__(self, subdir, lineno, colno, var_name, value):
self.subdir = subdir
def __init__(self, filename, lineno, colno, var_name, value):
self.filename = filename
self.lineno = lineno
self.colno = colno
self.var_name = var_name
@ -398,7 +398,7 @@ class IfClauseNode(BaseNode):
class UMinusNode(BaseNode):
def __init__(self, current_location, value):
self.subdir = current_location.subdir
self.filename = current_location.filename
self.lineno = current_location.lineno
self.colno = current_location.colno
self.value = value
@ -411,8 +411,8 @@ class IfNode(BaseNode):
self.block = block
class TernaryNode(BaseNode):
def __init__(self, subdir, lineno, colno, condition, trueblock, falseblock):
self.subdir = subdir
def __init__(self, filename, lineno, colno, condition, trueblock, falseblock):
self.filename = filename
self.lineno = lineno
self.colno = colno
self.condition = condition
@ -423,7 +423,7 @@ class ArgumentNode(BaseNode):
def __init__(self, token):
self.lineno = token.lineno
self.colno = token.colno
self.subdir = token.subdir
self.filename = token.filename
self.arguments = []
self.commas = []
self.kwargs = {}
@ -485,9 +485,9 @@ comparison_map = {'equal': '==',
# 9 plain token
class Parser:
def __init__(self, code, subdir):
def __init__(self, code, filename):
self.lexer = Lexer(code)
self.stream = self.lexer.lex(subdir)
self.stream = self.lexer.lex(filename)
self.current = Token('eof', '', 0, 0, 0, (0, 0), None)
self.getsym()
self.in_ternary = False
@ -531,13 +531,13 @@ class Parser:
value = self.e1()
if not isinstance(left, IdNode):
raise ParseException('Plusassignment target must be an id.', self.getline(), left.lineno, left.colno)
return PlusAssignmentNode(left.subdir, left.lineno, left.colno, left.value, value)
return PlusAssignmentNode(left.filename, left.lineno, left.colno, left.value, value)
elif self.accept('assign'):
value = self.e1()
if not isinstance(left, IdNode):
raise ParseException('Assignment target must be an id.',
self.getline(), left.lineno, left.colno)
return AssignmentNode(left.subdir, left.lineno, left.colno, left.value, value)
return AssignmentNode(left.filename, left.lineno, left.colno, left.value, value)
elif self.accept('questionmark'):
if self.in_ternary:
raise ParseException('Nested ternary operators are not allowed.',
@ -547,7 +547,7 @@ class Parser:
self.expect('colon')
falseblock = self.e1()
self.in_ternary = False
return TernaryNode(left.subdir, left.lineno, left.colno, left, trueblock, falseblock)
return TernaryNode(left.filename, left.lineno, left.colno, left, trueblock, falseblock)
return left
def e2(self):
@ -626,7 +626,7 @@ class Parser:
if not isinstance(left, IdNode):
raise ParseException('Function call must be applied to plain id',
self.getline(), left.lineno, left.colno)
left = FunctionNode(left.subdir, left.lineno, left.colno, self.current.lineno, self.current.colno, left.value, args)
left = FunctionNode(left.filename, left.lineno, left.colno, self.current.lineno, self.current.colno, left.value, args)
go_again = True
while go_again:
go_again = False
@ -718,7 +718,7 @@ class Parser:
self.expect('lparen')
args = self.args()
self.expect('rparen')
method = MethodNode(methodname.subdir, methodname.lineno, methodname.colno, source_object, methodname.value, args)
method = MethodNode(methodname.filename, methodname.lineno, methodname.colno, source_object, methodname.value, args)
if self.accept('dot'):
return self.method_call(method)
return method

@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import os, re
import re
import functools
import typing as T
@ -140,13 +140,14 @@ class OptionInterpreter:
def process(self, option_file):
try:
with open(option_file, 'r', encoding='utf8') as f:
ast = mparser.Parser(f.read(), '').parse()
ast = mparser.Parser(f.read(), option_file).parse()
except mesonlib.MesonException as me:
me.file = option_file
raise me
if not isinstance(ast, mparser.CodeBlockNode):
e = OptionException('Option file is malformed.')
e.lineno = ast.lineno()
e.file = option_file
raise e
for cur in ast.lines:
try:
@ -154,7 +155,7 @@ class OptionInterpreter:
except Exception as e:
e.lineno = cur.lineno
e.colno = cur.colno
e.file = os.path.join('meson_options.txt')
e.file = option_file
raise e
def reduce_single(self, arg):

@ -651,8 +651,8 @@ class Rewriter:
mlog.log(' -- Source', mlog.green(i), 'is already defined for the target --> skipping')
continue
mlog.log(' -- Adding source', mlog.green(i), 'at',
mlog.yellow('{}:{}'.format(os.path.join(node.subdir, environment.build_filename), node.lineno)))
token = Token('string', node.subdir, 0, 0, 0, None, i)
mlog.yellow('{}:{}'.format(node.filename, node.lineno)))
token = Token('string', node.filename, 0, 0, 0, None, i)
to_append += [StringNode(token)]
# Append to the AST at the right place
@ -695,7 +695,7 @@ class Rewriter:
arg_node = root
assert(arg_node is not None)
mlog.log(' -- Removing source', mlog.green(i), 'from',
mlog.yellow('{}:{}'.format(os.path.join(string_node.subdir, environment.build_filename), string_node.lineno)))
mlog.yellow('{}:{}'.format(string_node.filename, string_node.lineno)))
arg_node.arguments.remove(string_node)
# Mark the node as modified
@ -712,23 +712,24 @@ class Rewriter:
id_base = re.sub(r'[- ]', '_', cmd['target'])
target_id = id_base + '_exe' if cmd['target_type'] == 'executable' else '_lib'
source_id = id_base + '_sources'
filename = os.path.join(cmd['subdir'], environment.build_filename)
# Build src list
src_arg_node = ArgumentNode(Token('string', cmd['subdir'], 0, 0, 0, None, ''))
src_arg_node = ArgumentNode(Token('string', filename, 0, 0, 0, None, ''))
src_arr_node = ArrayNode(src_arg_node, 0, 0, 0, 0)
src_far_node = ArgumentNode(Token('string', cmd['subdir'], 0, 0, 0, None, ''))
src_fun_node = FunctionNode(cmd['subdir'], 0, 0, 0, 0, 'files', src_far_node)
src_ass_node = AssignmentNode(cmd['subdir'], 0, 0, source_id, src_fun_node)
src_arg_node.arguments = [StringNode(Token('string', cmd['subdir'], 0, 0, 0, None, x)) for x in cmd['sources']]
src_far_node = ArgumentNode(Token('string', filename, 0, 0, 0, None, ''))
src_fun_node = FunctionNode(filename, 0, 0, 0, 0, 'files', src_far_node)
src_ass_node = AssignmentNode(filename, 0, 0, source_id, src_fun_node)
src_arg_node.arguments = [StringNode(Token('string', filename, 0, 0, 0, None, x)) for x in cmd['sources']]
src_far_node.arguments = [src_arr_node]
# Build target
tgt_arg_node = ArgumentNode(Token('string', cmd['subdir'], 0, 0, 0, None, ''))
tgt_fun_node = FunctionNode(cmd['subdir'], 0, 0, 0, 0, cmd['target_type'], tgt_arg_node)
tgt_ass_node = AssignmentNode(cmd['subdir'], 0, 0, target_id, tgt_fun_node)
tgt_arg_node = ArgumentNode(Token('string', filename, 0, 0, 0, None, ''))
tgt_fun_node = FunctionNode(filename, 0, 0, 0, 0, cmd['target_type'], tgt_arg_node)
tgt_ass_node = AssignmentNode(filename, 0, 0, target_id, tgt_fun_node)
tgt_arg_node.arguments = [
StringNode(Token('string', cmd['subdir'], 0, 0, 0, None, cmd['target'])),
IdNode(Token('string', cmd['subdir'], 0, 0, 0, None, source_id))
StringNode(Token('string', filename, 0, 0, 0, None, cmd['target'])),
IdNode(Token('string', filename, 0, 0, 0, None, source_id))
]
src_ass_node.accept(AstIndentationGenerator())
@ -741,7 +742,7 @@ class Rewriter:
to_remove = target['node']
self.to_remove_nodes += [to_remove]
mlog.log(' -- Removing target', mlog.green(cmd['target']), 'at',
mlog.yellow('{}:{}'.format(os.path.join(to_remove.subdir, environment.build_filename), to_remove.lineno)))
mlog.yellow('{}:{}'.format(to_remove.filename, to_remove.lineno)))
elif cmd['operation'] == 'info':
# T.List all sources in the target
@ -776,8 +777,8 @@ class Rewriter:
self.functions[cmd['type']](cmd)
def apply_changes(self):
assert(all(hasattr(x, 'lineno') and hasattr(x, 'colno') and hasattr(x, 'subdir') for x in self.modefied_nodes))
assert(all(hasattr(x, 'lineno') and hasattr(x, 'colno') and hasattr(x, 'subdir') for x in self.to_remove_nodes))
assert(all(hasattr(x, 'lineno') and hasattr(x, 'colno') and hasattr(x, 'filename') for x in self.modefied_nodes))
assert(all(hasattr(x, 'lineno') and hasattr(x, 'colno') and hasattr(x, 'filename') for x in self.to_remove_nodes))
assert(all(isinstance(x, (ArrayNode, FunctionNode)) for x in self.modefied_nodes))
assert(all(isinstance(x, (ArrayNode, AssignmentNode, FunctionNode)) for x in self.to_remove_nodes))
# Sort based on line and column in reversed order
@ -796,7 +797,7 @@ class Rewriter:
printer.post_process()
new_data = printer.result.strip()
data = {
'file': os.path.join(i['node'].subdir, environment.build_filename),
'file': i['node'].filename,
'str': new_data,
'node': i['node'],
'action': i['action']

@ -56,7 +56,7 @@ from mesonbuild.mesonlib import (
BuildDirLock, LibType, MachineChoice, PerMachine, Version, is_windows,
is_osx, is_cygwin, is_dragonflybsd, is_openbsd, is_haiku, is_sunos,
windows_proof_rmtree, python_command, version_compare, split_args,
quote_arg
quote_arg, relpath
)
from mesonbuild.environment import detect_ninja
from mesonbuild.mesonlib import MesonException, EnvironmentException
@ -1512,7 +1512,8 @@ class BasePlatformTests(unittest.TestCase):
extra_args=None,
default_args=True,
inprocess=False,
override_envvars=None):
override_envvars=None,
workdir=None):
self.assertPathExists(srcdir)
if extra_args is None:
extra_args = []
@ -1553,7 +1554,7 @@ class BasePlatformTests(unittest.TestCase):
mesonbuild.mlog.log_file = None
else:
try:
out = self._run(self.setup_command + args + extra_args, override_envvars=override_envvars)
out = self._run(self.setup_command + args + extra_args, override_envvars=override_envvars, workdir=workdir)
except unittest.SkipTest:
raise unittest.SkipTest('Project requested skipping: ' + srcdir)
except Exception:
@ -3171,6 +3172,50 @@ int main(int argc, char **argv) {
]:
self.assertRegex(out, re.escape(expected))
for wd in [
self.src_root,
self.builddir,
os.getcwd(),
]:
self.new_builddir()
out = self.init(tdir, workdir=wd)
expected = os.path.join(relpath(tdir, self.src_root), 'meson.build')
relwd = relpath(self.src_root, wd)
if relwd != '.':
expected = os.path.join(relwd, expected)
expected = '\n' + expected + ':'
self.assertIn(expected, out)
def test_error_location_path(self):
'''Test locations in meson errors contain correct paths'''
# this list contains errors from all the different steps in the
# lexer/parser/interpreter we have tests for.
for (t, f) in [
('10 out of bounds', 'meson.build'),
('18 wrong plusassign', 'meson.build'),
('61 bad option argument', 'meson_options.txt'),
('100 subdir parse error', os.path.join('subdir', 'meson.build')),
('101 invalid option file', 'meson_options.txt'),
]:
tdir = os.path.join(self.src_root, 'test cases', 'failing', t)
for wd in [
self.src_root,
self.builddir,
os.getcwd(),
]:
try:
self.init(tdir, workdir=wd)
except subprocess.CalledProcessError as e:
expected = os.path.join('test cases', 'failing', t, f)
relwd = relpath(self.src_root, wd)
if relwd != '.':
expected = os.path.join(relwd, expected)
expected = '\n' + expected + ':'
self.assertIn(expected, e.output)
else:
self.fail('configure unexpectedly succeeded')
def test_permitted_method_kwargs(self):
tdir = os.path.join(self.unit_test_dir, '25 non-permitted kwargs')
out = self.init(tdir)

@ -0,0 +1,2 @@
project('subdir false plusassign', 'c')
subdir('subdir')
Loading…
Cancel
Save