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):