Repository: scipy/scipy
Pull request: BUG: Fix meson-info file errors and add more informative exception
Fixes #16020 

Problem statement:
BUG: dev.py FileNotFoundError
### Describe your issue.

Hi, trying to setup a dev environment on a new laptop and have been following the new quickstart guide. I am to build scipy using the `dev.py` interface and am running into the following error:
```
(scipy-meson) [jakeb@jake-vostro157510 scipy]$ python dev.py --build-only
Traceback (most recent call last):
  File "/home/jakeb/development/scipy/dev.py", line 635, in <module>
    main(argv=sys.argv[1:])
  File "/home/jakeb/development/scipy/dev.py", line 222, in main
    site_dir = build_project(args)
  File "/home/jakeb/development/scipy/dev.py", line 572, in build_project
    setup_build(args, env)
  File "/home/jakeb/development/scipy/dev.py", line 421, in setup_build
    with open(build_options_file) as f:
FileNotFoundError: [Errno 2] No such file or directory: 'build/meson-info/intro-buildoptions.json'
```

I am using conda 4.11.0

### Reproducing Code Example

```python
N/A
```


### Error message

```shell
N/A
```


### SciPy/NumPy/Python version information

N/A

Hints from issue discussion:
Hi,
Can you please try removing the `build` directory and use `python dev.py --build-only` again?
So what happened here most likely is that you built with `runtests.py` or `python setup.py ...` @j-bowhay, which created a `build/` directory but not with Meson. So now the script is confused. Can you confirm that?

@Smit-create we may want to special-case this issue, if the directory we use is `build/` but that file is missing, let's raise a more informative exception? Something like _"you're using Meson to build in the `build/` directory, but it looks like that directory is not empty and was not originally created by Meson". Please remove `build/` and try again."_
No if dev.py fails before having the necessary information, this happens quite often and I typically do `git clean -xdf`. Until it reaches the step after pkg-config checks if fails then it leaves it in this dirty state. 
Ah makes sense, that should be fixed - and is easy to fix. We just need checking for a file that the meson configure stage leaves behind instead of the one that is checked for now. Rerunning `python dev.py` multiple times should work even if the configure stage fails early on:
```bash
$ python dev.py 
The Meson build system
Version: 0.62.0
Source dir: /home/rgommers/code/scipy
Build dir: /home/rgommers/code/scipy/build
Build type: native build
Project name: SciPy
Project version: 1.9.0.dev0
C compiler for the host machine: ccache cc (gcc 11.2.0 "cc (GCC) 11.2.0")
C linker for the host machine: cc ld.bfd 2.38
C++ compiler for the host machine: ccache c++ (gcc 11.2.0 "c++ (GCC) 11.2.0")
C++ linker for the host machine: c++ ld.bfd 2.38
Host machine cpu family: x86_64
Host machine cpu: x86_64
Compiler for C supports arguments -Wno-unused-but-set-variable: YES 
Library m found: YES
Fortran compiler for the host machine: gfortran (gcc 11.2.0 "GNU Fortran (GCC) 11.2.0")
Fortran linker for the host machine: gfortran ld.bfd 2.38
Program cython found: YES (/home/rgommers/anaconda3/bin/cython)
Program pythran found: NO

meson.build:44:0: ERROR: Program 'pythran' not found or not executable

A full log can be found at /home/rgommers/code/scipy/build/meson-logs/meson-log.txt
Meson build setup failed! ({0} elapsed)
(base) rgommers@machine:~/code/scipy (main)$ python dev.py 
Traceback (most recent call last):
  File "dev.py", line 635, in <module>
    main(argv=sys.argv[1:])
  File "dev.py", line 222, in main
    site_dir = build_project(args)
  File "dev.py", line 572, in build_project
    setup_build(args, env)
  File "dev.py", line 421, in setup_build
    with open(build_options_file) as f:
FileNotFoundError: [Errno 2] No such file or directory: 'build/meson-info/intro-buildoptions.json'
(base) rgommers@machine:~/code/scipy (main)$ ls build/
meson-info  meson-logs  meson-private
(base) rgommers@machine:~/code/scipy (main)$ ls build/meson-info/
meson-info.json
```

We need both that, _and_ the improved error message.
@Smit-create indeed deleting the build directory and running again worked thanks
> We need both that, _and_ the improved error message.

Yeah, will fix that.

Commit message: BUG: Fix meson-info file errors and add more informative exception

Patch to review:
diff --git a/dev.py b/dev.py
index 3101d3e42ca9..b944b6c6cc3f 100644
--- a/dev.py
+++ b/dev.py
@@ -413,23 +413,32 @@ def setup_build(args, env):
     run_dir = os.getcwd()
     if build_dir.exists() and not (build_dir / 'meson-info').exists():
         if list(build_dir.iterdir()):
-            raise RuntimeError("Can't build into non-empty directory "
-                               f"'{build_dir.absolute()}'")
+            raise RuntimeError(
+                "You're using Meson to build in the `build` directory, "
+                "but it looks like that directory is not empty and "
+                "was not originally created by Meson. "
+                f"Please remove '{build_dir.absolute()}' and try again."
+            )
     if os.path.exists(build_dir):
         build_options_file = (build_dir / "meson-info"
                               / "intro-buildoptions.json")
-        with open(build_options_file) as f:
-            build_options = json.load(f)
-        installdir = None
-        for option in build_options:
-            if option["name"] == "prefix":
-                installdir = option["value"]
-                break
-        if installdir != PATH_INSTALLED:
+        if build_options_file.exists():
+            with open(build_options_file) as f:
+                build_options = json.load(f)
+            installdir = None
+            for option in build_options:
+                if option["name"] == "prefix":
+                    installdir = option["value"]
+                    break
+            if installdir != PATH_INSTALLED:
+                run_dir = os.path.join(run_dir, build_dir)
+                cmd = ["meson", "--reconfigure", "--prefix", PATH_INSTALLED]
+            else:
+                return
+        else:
             run_dir = os.path.join(run_dir, build_dir)
             cmd = ["meson", "--reconfigure", "--prefix", PATH_INSTALLED]
-        else:
-            return
+
     if args.werror:
         cmd += ["--werror"]
     if args.gcov:


Code review comments:
diff_hunk: @@ -413,23 +413,32 @@ def setup_build(args, env):
     run_dir = os.getcwd()
     if build_dir.exists() and not (build_dir / 'meson-info').exists():
         if list(build_dir.iterdir()):
-            raise RuntimeError("Can't build into non-empty directory "
-                               f"'{build_dir.absolute()}'")
+            raise RuntimeError(
+                "You're using Meson to build in the `build` directory, "
path: dev.py
text: @user1:
```suggestion
                f"You're using Meson to build in the `{build_dir.absolute()}` directory, "
```