Repository: mesonbuild/meson

Issue:
Detect when people try to run meson from a subdir in the source tree and print a friendly error
I think it's hard to understand what "First statement must be a call to project" means. I question if the sentence is proper English, but I'm not a native speaker, so it would be nice if someone could confirm this.

Whom are you calling upon to project something, and what will you project? Or, if it's not in the context of calling upon someone or projecting something, which function are you calling? `project` is not a function and it's not obvious how `project` should be called.

Could the error message be reworded?


Discussion:
> project is not a function and it's not obvious how project should be called.

I don't understand what you mean. `project` is a function: http://mesonbuild.com/Reference-manual.html#project

We should definitely make the error message clearer though. Perhaps we should point to that link.
I was trying to compile just `swaybar` which is part of the [Sway](https://github.com/swaywm/sway) window manager.

Being new to Meson, I naively checked out the `sway` sources, entered the `swaybar` directory, typed in `meson build` and got:

    ERROR: First statement must be a call to project

In the context of the commandline, there is no `project` function immediately available, so this was incomprehensible, to me.

I believe the real problem was that I needed to build the entire project (`cd ..; meson build; ninja -C build`) and not anything that had to do with the `project` function.

This issue may mainly be with me, since I'm new to meson, but perhaps another error message for this situation could also be helpful for others. Consider it a feature request in the pile of feature requests.

Thanks for developing meson, appart from the above issue I think it looks really promising and I believe it's an "up and coming" build system.
Can't we do like git an walk the directories upwards until we find a `meson.build` file with `project` in it?
Yes, that's what I was thinking too. We probably want to look upwards till we find a directory without a `meson.build`, and the previous dir was probably the source root. Then we tell the user to run meson from that directory.

In some cases there will indeed not be a meson.build with `project()` anywhere in the parent directories (f.ex., for git submodules that are dropped in as subdirs and not subprojects), and in that case a more useful error message than `use project()` would be appropriate.
I found this issue via Google just now. I am using meson on windows.

On Linux it is all fine; on meson I get this error "First statement must be a call to project".

I was doing this line:

    meson --prefix=/usr --libdir=/usr/lib BUILD_DIRECTORY

This works fine on Linux. On windows, when I try to compile atk (from the gtk stack),
it gives me the above error. So I would be super happy if this error becomes more
descriptive. Right now I have NO idea why that error is shown ... it would be better
if the error messages are a bit more refined and informative in general.

Resolving patch:
diff --git a/mesonbuild/interpreterbase/interpreterbase.py b/mesonbuild/interpreterbase/interpreterbase.py
--- a/mesonbuild/interpreterbase/interpreterbase.py
+++ b/mesonbuild/interpreterbase/interpreterbase.py
@@ -45,7 +45,7 @@
 from .helpers import check_stringlist, default_resolve_key, flatten, resolve_second_level_holders
 from ._unholder import _unholder
 
-import os, copy, re
+import os, copy, re, pathlib
 import typing as T
 
 if T.TYPE_CHECKING:
@@ -123,7 +123,19 @@ def sanity_check_ast(self) -> None:
             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')
+            p = pathlib.Path(self.source_root).resolve()
+            found = p
+            for parent in p.parents:
+                if (parent / 'meson.build').is_file():
+                    found = parent
+                else:
+                    break
+
+            error = 'first statement must be a call to project()'
+            if found != p:
+                raise InvalidCode(f'Not the project root: {error}\n\nDid you mean to run meson from the directory: "{found}"?')
+            else:
+                raise InvalidCode(f'Invalid source tree: {error}')
 
     def run(self) -> None:
         # Evaluate everything after the first line, which is project() because


Test patch:
diff --git a/test cases/failing/1 project not first/test.json b/test cases/failing/1 project not first/test.json
--- a/test cases/failing/1 project not first/test.json	
+++ b/test cases/failing/1 project not first/test.json	
@@ -1,7 +1,7 @@
 {
   "stdout": [
     {
-      "line": "ERROR: First statement must be a call to project"
+      "line": "ERROR: Invalid source tree: first statement must be a call to project()"
     }
   ]
 }