diff --git a/mesonbuild/msubprojects.py b/mesonbuild/msubprojects.py index 21f0853e6..f8c0b924c 100755 --- a/mesonbuild/msubprojects.py +++ b/mesonbuild/msubprojects.py @@ -394,19 +394,22 @@ class Runner: def update(self) -> bool: self.log(f'Updating {self.wrap.name}...') + success = False if self.wrap.type == 'file': - return self.update_file() + success = self.update_file() elif self.wrap.type == 'git': - return self.update_git() + success = self.update_git() elif self.wrap.type == 'hg': - return self.update_hg() + success = self.update_hg() elif self.wrap.type == 'svn': - return self.update_svn() + success = self.update_svn() elif self.wrap.type is None: self.log(' -> Cannot update subproject with no wrap file') else: self.log(' -> Cannot update', self.wrap.type, 'subproject') - return True + if success: + self.wrap.update_hash_cache(self.wrap_resolver.dirname) + return success def checkout(self) -> bool: options = T.cast('CheckoutArguments', self.options) diff --git a/mesonbuild/wrap/wrap.py b/mesonbuild/wrap/wrap.py index 34249d54d..25e96e663 100644 --- a/mesonbuild/wrap/wrap.py +++ b/mesonbuild/wrap/wrap.py @@ -132,7 +132,9 @@ class PackageDefinition: self.redirected = False if self.has_wrap: self.parse_wrap() - self.directory = self.values.get('directory', self.name) + self.directory = self.values.get('directory', self.name) + with open(fname, 'r', encoding='utf-8') as file: + self.wrapfile_hash = hashlib.sha256(file.read().encode('utf-8')).hexdigest() if os.path.dirname(self.directory): raise WrapException('Directory key must be a name and not a path') if self.type and self.type not in ALL_TYPES: @@ -221,6 +223,14 @@ class PackageDefinition: except KeyError: raise WrapException(f'Missing key {key!r} in {self.basename}') + def get_hashfile(self, subproject_directory: str) -> str: + return os.path.join(subproject_directory, '.meson-subproject-wrap-hash.txt') + + def update_hash_cache(self, subproject_directory: str) -> None: + if self.has_wrap: + with open(self.get_hashfile(subproject_directory), 'w', encoding='utf-8') as file: + file.write(self.wrapfile_hash + '\n') + def get_directory(subdir_root: str, packagename: str) -> str: fname = os.path.join(subdir_root, packagename + '.wrap') if os.path.isfile(fname): @@ -367,6 +377,7 @@ class Resolver: # The directory is there and has meson.build? Great, use it. if method == 'meson' and os.path.exists(meson_file): + self.validate() return rel_path if method == 'cmake' and os.path.exists(cmake_file): return rel_path @@ -403,6 +414,11 @@ class Resolver: if method == 'cmake' and not os.path.exists(cmake_file): raise WrapException('Subproject exists but has no CMakeLists.txt file') + # At this point, the subproject has been successfully resolved for the + # first time so save off the hash of the entire wrap file for future + # reference. + self.wrap.update_hash_cache(self.dirname) + return rel_path def check_can_download(self) -> None: @@ -504,6 +520,26 @@ class Resolver: if push_url: verbose_git(['remote', 'set-url', '--push', 'origin', push_url], self.dirname, check=True) + def validate(self) -> None: + # This check is only for subprojects with wraps. + if not self.wrap.has_wrap: + return + + # Retrieve original hash, if it exists. + hashfile = self.wrap.get_hashfile(self.dirname) + if os.path.isfile(hashfile): + with open(hashfile, 'r', encoding='utf-8') as file: + expected_hash = file.read().strip() + else: + # If stored hash doesn't exist then don't warn. + return + + actual_hash = self.wrap.wrapfile_hash + + # Compare hashes and warn the user if they don't match. + if expected_hash != actual_hash: + mlog.warning(f'Subproject {self.wrap.name}\'s revision may be out of date; its wrap file has changed since it was first configured') + def is_git_full_commit_id(self, revno: str) -> bool: result = False if len(revno) in (40, 64): # 40 for sha1, 64 for upcoming sha256 diff --git a/unittests/allplatformstests.py b/unittests/allplatformstests.py index 8e0f9340a..7f0dd441c 100644 --- a/unittests/allplatformstests.py +++ b/unittests/allplatformstests.py @@ -3733,10 +3733,26 @@ class AllPlatformTests(BasePlatformTests): patch_directory = wrap_git_builddef revision = master '''.format(upstream_uri))) - self.init(srcdir) + out = self.init(srcdir) self.build() self.run_tests() + # Make sure the warning does not occur on the first init. + out_of_date_warning = 'revision may be out of date' + self.assertNotIn(out_of_date_warning, out) + + # Change the wrap's revisions, reconfigure, and make sure it does + # warn on the reconfigure. + with open(os.path.join(srcdir, 'subprojects', 'wrap_git.wrap'), 'w', encoding='utf-8') as f: + f.write(textwrap.dedent(''' + [wrap-git] + url = {} + patch_directory = wrap_git_builddef + revision = not-master + '''.format(upstream_uri))) + out = self.init(srcdir, extra_args='--reconfigure') + self.assertIn(out_of_date_warning, out) + def test_extract_objects_custom_target_no_warning(self): testdir = os.path.join(self.common_test_dir, '22 object extraction')