diff --git a/mesonbuild/scripts/depfixer.py b/mesonbuild/scripts/depfixer.py index 8adc35c4a9ca..3e7fd179ac18 100644 --- a/mesonbuild/scripts/depfixer.py +++ b/mesonbuild/scripts/depfixer.py @@ -4,6 +4,7 @@ from __future__ import annotations +import datetime import sys import os import stat @@ -532,7 +533,20 @@ def fix_jar(fname: str) -> None: # special-casing for the manifest file, so we can re-add it as a normal # archive member. This puts the manifest at the end of the jar rather # than the beginning, but the spec doesn't forbid that. - subprocess.check_call(['jar', 'ufM', fname, 'META-INF/MANIFEST.MF']) + source_date_epoch = os.environ.get('SOURCE_DATE_EPOCH') + if source_date_epoch: + # We want to adjust mtime for deterministic .jar file results: + source_date_epoch = max(int(source_date_epoch), 315529202) # zip cannot represent earlier timestamps + formatted_date = datetime.datetime.fromtimestamp(source_date_epoch, datetime.timezone.utc).isoformat() + # One cannot mix --date with old style bunched options and other jar + # variants seem to not understand new-style options, + # so we need some duplication here for maximum compatibility + cmd = ['jar', f'--date={formatted_date}', '-u', '-M', '-f'] + else: + cmd = ['jar', 'ufM'] + + cmd.extend([fname, 'META-INF/MANIFEST.MF']) + subprocess.check_call(cmd) def fix_rpath(fname: str, rpath_dirs_to_remove: T.Set[bytes], new_rpath: T.Union[str, bytes], final_path: str, install_name_mappings: T.Dict[str, str], verbose: bool = True) -> None: global INSTALL_NAME_TOOL # pylint: disable=global-statement diff --git a/unittests/baseplatformtests.py b/unittests/baseplatformtests.py index 71c14260801f..e67604eabcf3 100644 --- a/unittests/baseplatformtests.py +++ b/unittests/baseplatformtests.py @@ -91,6 +91,7 @@ def setUpClass(cls) -> None: cls.unit_test_dir = os.path.join(src_root, 'test cases/unit') cls.rewrite_test_dir = os.path.join(src_root, 'test cases/rewrite') cls.linuxlike_test_dir = os.path.join(src_root, 'test cases/linuxlike') + cls.java_test_dir = os.path.join(src_root, 'test cases/java') cls.objc_test_dir = os.path.join(src_root, 'test cases/objc') cls.objcpp_test_dir = os.path.join(src_root, 'test cases/objcpp') cls.darwin_test_dir = os.path.join(src_root, 'test cases/darwin') diff --git a/unittests/linuxliketests.py b/unittests/linuxliketests.py index 75329af6090e..28416e079b53 100644 --- a/unittests/linuxliketests.py +++ b/unittests/linuxliketests.py @@ -9,6 +9,7 @@ import os import shutil import hashlib +import zipfile from unittest import mock, skipUnless, SkipTest from glob import glob from pathlib import Path @@ -2013,6 +2014,20 @@ def test_rust_staticlib_rlib_deps(self): for param in linker['parameters']: self.assertNotIn('liblib.rlib', param) + def test_jar_manifest_mtime(self): + ''' + Test that jar creation can use SOURCE_DATE_EPOCH for mtime + ''' + testdir = os.path.join(self.java_test_dir, '1 basic') + self.init(testdir) + self.build() + self.install(override_envvars={'SOURCE_DATE_EPOCH': '1770700000'}) + jarfile = f'{self.installdir}/usr/bin/myprog.jar' + # check mtime in created .jar file + with zipfile.ZipFile(jarfile) as zip: + dt = zip.getinfo('META-INF/MANIFEST.MF').date_time + self.assertEqual(dt, (2026, 2, 10, 5, 6, 40)) + def test_sanitizers(self): testdir = os.path.join(self.unit_test_dir, '129 sanitizers')