Skip to content

Commit

Permalink
Fine-tune the error message when trying to build outside the project …
Browse files Browse the repository at this point in the history
…root

We try to backtrack through the filesystem to find the correct directory
to build in, and suggest this as a possible diagnostic. However, our
current heuristic relies on parsing the raw file with string matching to
see if it starts with `project(`, and this may or may not actually work.

Instead, do a bit of recursion and parse each candidate with mparser,
then check if the first node of *that* file is a project() function.

This makes us resilient to a common case: where the root meson.build is
entirely valid, but, the first line is a comment containing e.g. SPDX
license headers and a simple string comparison simply does not cut it.

Fixes the bad error message from mesonbuild#12441, which was supposed to provide
more guidance but did not.
  • Loading branch information
eli-schwartz committed Nov 5, 2023
1 parent 74712f2 commit fb1c6e3
Show file tree
Hide file tree
Showing 2 changed files with 27 additions and 9 deletions.
28 changes: 19 additions & 9 deletions mesonbuild/interpreterbase/interpreterbase.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,20 +132,30 @@ def parse_project(self) -> None:
self.evaluate_codeblock(self.ast, end=1)

def sanity_check_ast(self) -> None:
if not isinstance(self.ast, mparser.CodeBlockNode):
raise InvalidCode('AST is of invalid type. Possibly a bug in the parser.')
if not self.ast.lines:
raise InvalidCode('No statements in code.')
first = self.ast.lines[0]
if not isinstance(first, mparser.FunctionNode) or first.func_name.value != 'project':
def _is_project(ast: mparser.CodeBlockNode) -> object:
if not isinstance(ast, mparser.CodeBlockNode):
raise InvalidCode('AST is of invalid type. Possibly a bug in the parser.')
if not ast.lines:
raise InvalidCode('No statements in code.')
first = ast.lines[0]
return isinstance(first, mparser.FunctionNode) and first.func_name.value == 'project'

if not _is_project(self.ast):
p = pathlib.Path(self.source_root).resolve()
found = p
for parent in p.parents:
if (parent / 'meson.build').is_file():
with open(parent / 'meson.build', encoding='utf-8') as f:
if f.readline().startswith('project('):
found = parent
break
code = f.read()

try:
ast = mparser.Parser(code, 'empty').parse()
except mparser.ParseException:
continue

if _is_project(ast):
found = parent
break
else:
break

Expand Down
8 changes: 8 additions & 0 deletions unittests/platformagnostictests.py
Original file line number Diff line number Diff line change
Expand Up @@ -278,3 +278,11 @@ def test_cmake_openssl_not_found_bug(self):
self.meson_native_files.append(os.path.join(testdir, 'nativefile.ini'))
out = self.init(testdir, allow_fail=True)
self.assertNotIn('Unhandled python exception', out)

def test_error_configuring_subdir(self):
testdir = os.path.join(self.common_test_dir, '152 index customtarget')
out = self.init(os.path.join(testdir, 'subdir'), allow_fail=True)

self.assertIn('first statement must be a call to project()', out)
# provide guidance diagnostics by finding a file whose first AST statement is project()
self.assertIn(f'Did you mean to run meson from the directory: "{testdir}"?', out)

0 comments on commit fb1c6e3

Please sign in to comment.