diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py
index ac184f094..7e3f936d5 100644
--- a/mesonbuild/backend/backends.py
+++ b/mesonbuild/backend/backends.py
@@ -197,15 +197,20 @@ class Backend():
 
     def serialise_executable(self, exe, cmd_args, workdir, env={},
                              capture=None):
-        import uuid
+        import hashlib
         # Can't just use exe.name here; it will likely be run more than once
         if isinstance(exe, (dependencies.ExternalProgram,
                             build.BuildTarget, build.CustomTarget)):
             basename = exe.name
         else:
             basename = os.path.basename(exe)
-        scratch_file = 'meson_exe_{0}_{1}.dat'.format(basename,
-                                                      str(uuid.uuid4())[:8])
+        # Take a digest of the cmd args, env, workdir, and capture. This avoids
+        # collisions and also makes the name deterministic over regenerations
+        # which avoids a rebuild by Ninja because the cmdline stays the same.
+        data = bytes(str(sorted(env.items())) + str(cmd_args) + str(workdir) + str(capture),
+                     encoding='utf-8')
+        digest = hashlib.sha1(data).hexdigest()
+        scratch_file = 'meson_exe_{0}_{1}.dat'.format(basename, digest)
         exe_data = os.path.join(self.environment.get_scratch_dir(), scratch_file)
         with open(exe_data, 'wb') as f:
             if isinstance(exe, dependencies.ExternalProgram):
diff --git a/run_unittests.py b/run_unittests.py
index 8f1f1554e..179bed690 100755
--- a/run_unittests.py
+++ b/run_unittests.py
@@ -84,6 +84,7 @@ class LinuxlikeTests(unittest.TestCase):
                 '--prefix', self.prefix,
                 '--libdir', self.libdir]
         self._run(self.meson_command + args)
+        self.privatedir = os.path.join(self.builddir, 'meson-private')
 
     def build(self):
         self._run(self.ninja_command)
@@ -101,6 +102,9 @@ class LinuxlikeTests(unittest.TestCase):
     def setconf(self, arg):
         self._run(self.mconf_command + [arg, self.builddir])
 
+    def wipe(self):
+        shutil.rmtree(self.builddir)
+
     def get_compdb(self):
         with open(os.path.join(self.builddir, 'compile_commands.json')) as ifile:
             return json.load(ifile)
@@ -183,7 +187,7 @@ class LinuxlikeTests(unittest.TestCase):
         self.init(testdir)
         env = FakeEnvironment()
         kwargs = {'required': True, 'silent': True}
-        os.environ['PKG_CONFIG_LIBDIR'] = os.path.join(self.builddir, 'meson-private')
+        os.environ['PKG_CONFIG_LIBDIR'] = self.privatedir
         simple_dep = PkgConfigDependency('libfoo', env, kwargs)
         self.assertTrue(simple_dep.found())
         self.assertEqual(simple_dep.get_version(), '1.0')
@@ -374,6 +378,16 @@ class LinuxlikeTests(unittest.TestCase):
         self.uninstall()
         self.assertFalse(os.path.exists(exename))
 
+    def test_custom_target_exe_data_deterministic(self):
+        testdir = os.path.join(self.common_test_dir, '117 custom target capture')
+        self.init(testdir)
+        meson_exe_dat1 = glob(os.path.join(self.privatedir, 'meson_exe*.dat'))
+        self.wipe()
+        self.init(testdir)
+        meson_exe_dat2 = glob(os.path.join(self.privatedir, 'meson_exe*.dat'))
+        self.assertListEqual(meson_exe_dat1, meson_exe_dat2)
+
+
 class RewriterTests(unittest.TestCase):
 
     def setUp(self):