Make MB clobber build directories when switching between GYP and GN.

GYP and GN put generated files in different places inside the build
directory. This means that if you do a GYP build and then a GN build
into the same directory, you may get unpredictable results depending
on which generated files are picked up at what times.

In order to avoid this, we modify MB to keep track of whether a
given build directory has build files generated by GYP or GN; if there
is a mismatch (like when we flip a bot from GYP to GN), we clobber the
build directory and start from scratch to be safe.

In addition, the first time we enable MB on a bot, we will not know
what the existing build directory contains; to be safe, we do a clobber
in this situation as well.

[email protected]
BUG=

Review URL: https://codereview.chromium.org/1338123002

Cr-Commit-Position: refs/heads/master@{#348705}
diff --git a/tools/mb/mb.py b/tools/mb/mb.py
index f1a223e..57054e9 100755
--- a/tools/mb/mb.py
+++ b/tools/mb/mb.py
@@ -135,6 +135,9 @@
 
   def CmdGen(self):
     vals = self.GetConfig()
+
+    self.ClobberIfNeeded(vals)
+
     if vals['type'] == 'gn':
       return self.RunGNGen(vals)
     if vals['type'] == 'gyp':
@@ -290,7 +293,7 @@
       'type': None,
       'gn_args': [],
       'gyp_config': [],
-      'gyp_defines': [],
+      'gyp_defines': '',
       'gyp_crosscompile': False,
     }
 
@@ -328,6 +331,33 @@
         self.FlattenMixins(mixin_vals['mixins'], vals, visited)
     return vals
 
+  def ClobberIfNeeded(self, vals):
+    path = self.args.path[0]
+    build_dir = self.ToAbsPath(path)
+    mb_type_path = os.path.join(build_dir, 'mb_type')
+    needs_clobber = False
+    new_mb_type = vals['type']
+    if self.Exists(build_dir):
+      if self.Exists(mb_type_path):
+        old_mb_type = self.ReadFile(mb_type_path)
+        if old_mb_type != new_mb_type:
+          self.Print("Build type mismatch: was %s, will be %s, clobbering %s" %
+                     (old_mb_type, new_mb_type, path))
+          needs_clobber = True
+      else:
+        # There is no 'mb_type' file in the build directory, so this probably
+        # means that the prior build(s) were not done through mb, and we
+        # have no idea if this was a GYP build or a GN build. Clobber it
+        # to be safe.
+        self.Print("%s/mb_type missing, clobbering to be safe" % path)
+        needs_clobber = True
+
+    if needs_clobber:
+      self.RemoveDirectory(build_dir)
+
+    self.MaybeMakeDirectory(build_dir)
+    self.WriteFile(mb_type_path, new_mb_type)
+
   def RunGNGen(self, vals):
     path = self.args.path[0]
 
@@ -821,6 +851,17 @@
     # This function largely exists so it can be overriden for testing.
     os.remove(path)
 
+  def RemoveDirectory(self, abs_path):
+    if sys.platform == 'win32':
+      # In other places in chromium, we often have to retry this command
+      # because we're worried about other processes still holding on to
+      # file handles, but when MB is invoked, it will be early enough in the
+      # build that their should be no other processes to interfere. We
+      # can change this if need be.
+      self.Run(['cmd.exe', '/c', 'rmdir', '/q', '/s', abs_path])
+    else:
+      shutil.rmtree(abs_path, ignore_errors=True)
+
   def TempFile(self, mode='w'):
     # This function largely exists so it can be overriden for testing.
     return tempfile.NamedTemporaryFile(mode=mode, delete=False)