Repository: mesonbuild/meson

Issue:
Ctrl-C during build can leave builddir in unrecoverable state
Steps to reproduce:

1. Make sure meson --regenerate needs to be run
2. Run ninja
3. While ninja runs meson --regenerate, press Ctrl-C at exactly the right time
4. Run ninja again
5. See this:  "Something went terribly wrong. Please file a bug."

After trying to track down what that error even means (the error message could really be more informative), it turns out my coredata.dat was missing.

It would be nice if Ctrl-C during a build would never leave the builddir in a non-recoverable state.


Discussion:
Happens to me all the time.
Using a temporary build.ninja file and atomic replace would easily solve the problem.
> Using a temporary build.ninja file and atomic replace would easily solve the problem.

We already do this, see `generate()` in `backends/ninjabackend.py`.  The issue seems to be something else.
A strong argument is coredata.py, function `save`.
I confirm that the build.ninja file is intact.
When doing a test today, i got : 
```
Found ninja-1.8.2.git at /usr/bin/ninja
^CTraceback (most recent call last):
  File "/usr/bin/meson", line 4, in <module>
    __import__('pkg_resources').run_script('meson==0.47.0.dev1', 'meson')
  File "/usr/lib/python3.6/site-packages/pkg_resources/__init__.py", line 658, in run_script
    self.require(requires)[0].run_script(script_name, ns)
  File "/usr/lib/python3.6/site-packages/pkg_resources/__init__.py", line 1438, in run_script
    exec(code, namespace, namespace)
  File "/usr/lib/python3.6/site-packages/meson-0.47.0.dev1-py3.6.egg/EGG-INFO/scripts/meson", line 26, in <module>
    sys.exit(main())
  File "/usr/lib/python3.6/site-packages/meson-0.47.0.dev1-py3.6.egg/EGG-INFO/scripts/meson", line 23, in main
    return mesonmain.run(sys.argv[1:], launcher)
  File "/usr/lib/python3.6/site-packages/meson-0.47.0.dev1-py3.6.egg/mesonbuild/mesonmain.py", line 352, in run
    app.generate()
  File "/usr/lib/python3.6/site-packages/meson-0.47.0.dev1-py3.6.egg/mesonbuild/mesonmain.py", line 132, in generate
    self._generate(env)
  File "/usr/lib/python3.6/site-packages/meson-0.47.0.dev1-py3.6.egg/mesonbuild/mesonmain.py", line 189, in _generate
    g.generate(intr)
  File "/usr/lib/python3.6/site-packages/meson-0.47.0.dev1-py3.6.egg/mesonbuild/backend/ninjabackend.py", line 220, in generate
    self.generate_target(t, outfile)
  File "/usr/lib/python3.6/site-packages/meson-0.47.0.dev1-py3.6.egg/mesonbuild/backend/ninjabackend.py", line 476, in generate_target
    elem.write(outfile)
  File "/usr/lib/python3.6/site-packages/meson-0.47.0.dev1-py3.6.egg/mesonbuild/backend/ninjabackend.py", line 120, in write
    for i in elems:
KeyboardInterrupt
ninja: error: rebuilding 'build.ninja': interrupted by user
```
and then to get the files modified today : 
```
$ find . -mtime -1 -ls                                                                                                                                                                                                             [16:50:49]
   803178      4 drwxr-xr-x  35  felix    felix        4096 mai 15 16:49 .
   801577     72 -rw-r--r--   1  felix    felix       71554 mai 15 16:49 ./meson-logs/meson-log.txt
   803204      4 drwxr-xr-x   2  felix    felix        4096 mai 15 16:49 ./etc
   803216      4 drwxr-xr-x   3  felix    felix        4096 mai 15 16:49 ./libgimpmodule
   801324      0 lrwxrwxrwx   1  felix    felix          26 mai 15 16:49 ./libgimpmodule/libgimpmodule-3.0.so.0 -> libgimpmodule-3.0.so.0.0.0
   801325      0 lrwxrwxrwx   1  felix    felix          22 mai 15 16:49 ./libgimpmodule/libgimpmodule-3.0.so -> libgimpmodule-3.0.so.0
   803217      4 drwxr-xr-x   4  felix    felix        4096 mai 15 16:49 ./libgimpthumb
   801326      0 lrwxrwxrwx   1  felix    felix          25 mai 15 16:49 ./libgimpthumb/libgimpthumb-3.0.so.0 -> libgimpthumb-3.0.so.0.0.0
   801327      0 lrwxrwxrwx   1  felix    felix          21 mai 15 16:49 ./libgimpthumb/libgimpthumb-3.0.so -> libgimpthumb-3.0.so.0
   803203      4 drwxr-xr-x   2  felix    felix        4096 mai 15 16:49 ./desktop
   803213      4 drwxr-xr-x   4  felix    felix        4096 mai 15 16:49 ./libgimpcolor
   799349      0 lrwxrwxrwx   1  felix    felix          25 mai 15 16:49 ./libgimpcolor/libgimpcolor-3.0.so.0 -> libgimpcolor-3.0.so.0.0.0
   799407      0 lrwxrwxrwx   1  felix    felix          21 mai 15 16:49 ./libgimpcolor/libgimpcolor-3.0.so -> libgimpcolor-3.0.so.0
   803190      4 drwxr-xr-x   2  felix    felix        4096 mai 15 16:49 ./cursors
   803179      4 drwxr-xr-x   2  felix    felix        4096 mai 15 16:49 ./meson-private
   801607      4 -rw-r--r--   1  felix    felix         648 mai 15 16:49 ./meson-private/gimp-3.0.pc
   801608      4 -rw-r--r--   1  felix    felix         319 mai 15 16:49 ./meson-private/gimpthumb-3.0.pc
   801613      4 -rw-r--r--   1  felix    felix        1499 mai 15 16:49 ./meson-private/meson_exe_gimp-mkenums_2401bae0f990441b88529927c24c72c5fa4dd61e.dat
   801631      4 -rw-r--r--   1  felix    felix        1568 mai 15 16:49 ./meson-private/meson_exe_gimp-mkenums_4912b054e2b3cb16052545d6cea02b8577b887e1.dat
   801625      4 -rw-r--r--   1  felix    felix         911 mai 15 16:49 ./meson-private/meson_exe_gimp-mkenums_7bf97f7151604e56f72ea7d7f2bbf598b8ad0201.dat
   801578      0 -rw-r--r--   1  felix    felix           0 mai 15 16:49 ./meson-private/meson.lock
   801609      4 -rw-r--r--   1  felix    felix         417 mai 15 16:49 ./meson-private/gimpui-3.0.pc
   801620      4 -rw-r--r--   1  felix    felix        1508 mai 15 16:49 ./meson-private/meson_exe_gimp-mkenums_330998687017fe9aa74d6f18f15c5c48c54d10c1.dat
   801612      4 -rw-r--r--   1  felix    felix        1482 mai 15 16:49 ./meson-private/meson_exe_gimp-mkenums_c476306a7a09330cb637802bca45a36830e968f5.dat
   801628      4 -rw-r--r--   1  felix    felix        1514 mai 15 16:49 ./meson-private/meson_exe_gimp-mkenums_b6a4ada126fbe6a22a31c885a2bf4a7d91acd531.dat
   801632      4 -rw-r--r--   1  felix    felix         362 mai 15 16:49 ./meson-private/meson_exe_cat_8143c3cb4c21a0d9159e13f22d17e2e452576d12.dat
   803219      4 drwxr-xr-x   4  felix    felix        4096 mai 15 16:49 ./libgimp
   801338      0 lrwxrwxrwx   1  felix    felix          18 mai 15 16:49 ./libgimp/libgimpui-3.0.so -> libgimpui-3.0.so.0
   801333      0 lrwxrwxrwx   1  felix    felix          16 mai 15 16:49 ./libgimp/libgimp-3.0.so -> libgimp-3.0.so.0
   801332      0 lrwxrwxrwx   1  felix    felix          20 mai 15 16:49 ./libgimp/libgimp-3.0.so.0 -> libgimp-3.0.so.0.0.0
   801336      0 lrwxrwxrwx   1  felix    felix          22 mai 15 16:49 ./libgimp/libgimpui-3.0.so.0 -> libgimpui-3.0.so.0.0.0
   803215      4 drwxr-xr-x   3  felix    felix        4096 mai 15 16:49 ./libgimpconfig
   801286      0 lrwxrwxrwx   1  felix    felix          26 mai 15 16:49 ./libgimpconfig/libgimpconfig-3.0.so.0 -> libgimpconfig-3.0.so.0.0.0
   801323      0 lrwxrwxrwx   1  felix    felix          22 mai 15 16:49 ./libgimpconfig/libgimpconfig-3.0.so -> libgimpconfig-3.0.so.0
   803214      4 drwxr-xr-x   3  felix    felix        4096 mai 15 16:49 ./libgimpmath
   801284      0 lrwxrwxrwx   1  felix    felix          24 mai 15 16:49 ./libgimpmath/libgimpmath-3.0.so.0 -> libgimpmath-3.0.so.0.0.0
   801285      0 lrwxrwxrwx   1  felix    felix          20 mai 15 16:49 ./libgimpmath/libgimpmath-3.0.so -> libgimpmath-3.0.so.0
   799336    492 -rw-r--r--   1  felix    felix      501338 mai 15 16:49 ./build.ninja~
   803218      4 drwxr-xr-x   4  felix    felix        4096 mai 15 16:49 ./libgimpwidgets
   801329      0 lrwxrwxrwx   1  felix    felix          23 mai 15 16:49 ./libgimpwidgets/libgimpwidgets-3.0.so -> libgimpwidgets-3.0.so.0
   801328      0 lrwxrwxrwx   1  felix    felix          27 mai 15 16:49 ./libgimpwidgets/libgimpwidgets-3.0.so.0 -> libgimpwidgets-3.0.so.0.0.0
   803251      4 drwxr-xr-x   3  felix    felix        4096 mai 15 16:49 ./plug-ins/pagecurl
   803209      4 drwxr-xr-x   2  felix    felix        4096 mai 15 16:49 ./icons/Symbolic-Inverted
   803206      4 drwxr-xr-x   2  felix    felix        4096 mai 15 16:49 ./icons/Color
   803208      4 drwxr-xr-x   2  felix    felix        4096 mai 15 16:49 ./icons/Symbolic
   803207      4 drwxr-xr-x   2  felix    felix        4096 mai 15 16:49 ./icons/Legacy
   811011      4 drwxr-xr-x   2  felix    felix        4096 mai 15 16:49 ./docs
   803181      4 drwxr-xr-x   4  felix    felix        4096 mai 15 16:49 ./libgimpbase
   799337      0 lrwxrwxrwx   1  felix    felix          24 mai 15 16:49 ./libgimpbase/libgimpbase-3.0.so.0 -> libgimpbase-3.0.so.0.0.0
   799345      0 lrwxrwxrwx   1  felix    felix          20 mai 15 16:49 ./libgimpbase/libgimpbase-3.0.so -> libgimpbase-3.0.so.0
```


Okay, a bit more investigating indicated that coredata.dat does not exist if meson exits in generate().

Resolving patch:
diff --git a/mesonbuild/coredata.py b/mesonbuild/coredata.py
--- a/mesonbuild/coredata.py
+++ b/mesonbuild/coredata.py
@@ -354,10 +354,19 @@ def load(build_dir):
 
 def save(obj, build_dir):
     filename = os.path.join(build_dir, 'meson-private', 'coredata.dat')
+    prev_filename = filename + '.prev'
+    tempfilename = filename + '~'
     if obj.version != version:
         raise MesonException('Fatal version mismatch corruption.')
-    with open(filename, 'wb') as f:
+    if os.path.exists(filename):
+        import shutil
+        shutil.copyfile(filename, prev_filename)
+    with open(tempfilename, 'wb') as f:
         pickle.dump(obj, f)
+        f.flush()
+        os.fsync(f.fileno())
+    os.replace(tempfilename, filename)
+    return filename
 
 def get_builtin_options():
     return list(builtin_options.keys())
diff --git a/mesonbuild/environment.py b/mesonbuild/environment.py
--- a/mesonbuild/environment.py
+++ b/mesonbuild/environment.py
@@ -261,7 +261,6 @@ def search_version(text):
 class Environment:
     private_dir = 'meson-private'
     log_dir = 'meson-logs'
-    coredata_file = os.path.join(private_dir, 'coredata.dat')
 
     def __init__(self, source_dir, build_dir, main_script_launcher, options, original_cmd_line_args):
         self.source_dir = source_dir
@@ -335,8 +334,7 @@ def is_cross_build(self):
         return self.cross_info is not None
 
     def dump_coredata(self):
-        coredata.save(self.coredata, self.get_build_dir())
-        return os.path.join(self.get_build_dir(), Environment.coredata_file)
+        return coredata.save(self.coredata, self.get_build_dir())
 
     def get_script_dir(self):
         import mesonbuild.scripts
diff --git a/mesonbuild/mesonmain.py b/mesonbuild/mesonmain.py
--- a/mesonbuild/mesonmain.py
+++ b/mesonbuild/mesonmain.py
@@ -204,7 +204,11 @@ def _generate(self, env):
             g.run_postconf_scripts()
         except:
             if 'cdf' in locals():
-                os.unlink(cdf)
+                old_cdf = cdf + '.prev'
+                if os.path.exists(old_cdf):
+                    os.replace(old_cdf, cdf)
+                else:
+                    os.unlink(cdf)
             raise
 
 def run_script_command(args):


Test patch: