From 51c889ddbc8e83a73ef4d1f2556609bae2a046ce Mon Sep 17 00:00:00 2001 From: Benjamin Gilbert Date: Tue, 13 Dec 2022 21:16:14 -0500 Subject: depfixer: don't extract MANIFEST.MF verbosely Avoids non-actionable output when installing a jar: inflated: META-INF/MANIFEST.MF Fixes: c70a051e93 ("java: remove manifest classpath from installed jar") --- mesonbuild/scripts/depfixer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mesonbuild/scripts/depfixer.py b/mesonbuild/scripts/depfixer.py index 17432c189..702afeb80 100644 --- a/mesonbuild/scripts/depfixer.py +++ b/mesonbuild/scripts/depfixer.py @@ -457,7 +457,7 @@ def fix_darwin(fname: str, new_rpath: str, final_path: str, install_name_mapping raise SystemExit(err) def fix_jar(fname: str) -> None: - subprocess.check_call(['jar', 'xfv', fname, 'META-INF/MANIFEST.MF']) + subprocess.check_call(['jar', 'xf', fname, 'META-INF/MANIFEST.MF']) with open('META-INF/MANIFEST.MF', 'r+', encoding='utf-8') as f: lines = f.readlines() f.seek(0) -- cgit v1.2.3 From 35e230e48ca42f4ccb872d1d01f9280f8015b417 Mon Sep 17 00:00:00 2001 From: Benjamin Gilbert Date: Tue, 13 Dec 2022 21:12:41 -0500 Subject: depfixer: silence fix_jar() and make it do something fix_jar() tries to remove an existing Class-Path entry from the jar manifest by postprocessing the manifest and passing it to `jar -um`. However, `jar -um` can only add/replace manifest entries, not remove them, and it also complains loudly when replacing an entry: Dec 13, 2022 7:11:19 PM java.util.jar.Attributes read WARNING: Duplicate name in Manifest: Manifest-Version. Ensure that the manifest does not have duplicate entries, and that blank lines separate individual sections in both your manifest and in the META-INF/MANIFEST.MF entry in the jar file. Thus fix_jar() produces one such warning for each entry in the manifest and accomplishes nothing else. Use jar -uM instead. This completely removes the manifest from the jar and allows adding it back as a normal zip member, fixing fix_jar() and avoiding the warnings. Fixes: https://github.com/mesonbuild/meson/issues/10491 Fixes: c70a051e93 ("java: remove manifest classpath from installed jar") --- mesonbuild/scripts/depfixer.py | 7 ++++++- .../unit/110 classpath/com/mesonbuild/Simple.java | 7 +++++++ test cases/unit/110 classpath/meson.build | 14 ++++++++++++++ unittests/helpers.py | 17 +++++++++++++++++ unittests/machinefiletests.py | 18 ++++++++++++++++++ 5 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 test cases/unit/110 classpath/com/mesonbuild/Simple.java create mode 100644 test cases/unit/110 classpath/meson.build diff --git a/mesonbuild/scripts/depfixer.py b/mesonbuild/scripts/depfixer.py index 702afeb80..8d9c90fe3 100644 --- a/mesonbuild/scripts/depfixer.py +++ b/mesonbuild/scripts/depfixer.py @@ -465,7 +465,12 @@ def fix_jar(fname: str) -> None: if not line.startswith('Class-Path:'): f.write(line) f.truncate() - subprocess.check_call(['jar', 'ufm', fname, 'META-INF/MANIFEST.MF']) + # jar -um doesn't allow removing existing attributes. Use -uM instead, + # which a) removes the existing manifest from the jar and b) disables + # 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']) 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/test cases/unit/110 classpath/com/mesonbuild/Simple.java b/test cases/unit/110 classpath/com/mesonbuild/Simple.java new file mode 100644 index 000000000..325a49a21 --- /dev/null +++ b/test cases/unit/110 classpath/com/mesonbuild/Simple.java @@ -0,0 +1,7 @@ +package com.mesonbuild; + +class Simple { + public static void main(String [] args) { + System.out.println("Java is working.\n"); + } +} diff --git a/test cases/unit/110 classpath/meson.build b/test cases/unit/110 classpath/meson.build new file mode 100644 index 000000000..e6b9b840a --- /dev/null +++ b/test cases/unit/110 classpath/meson.build @@ -0,0 +1,14 @@ +project('simplejava', 'java') + +one = jar('one', 'com/mesonbuild/Simple.java', + main_class : 'com.mesonbuild.Simple', + install : true, + install_dir : get_option('bindir'), +) + +two = jar('two', 'com/mesonbuild/Simple.java', + main_class : 'com.mesonbuild.Simple', + install : true, + install_dir : get_option('bindir'), + link_with : one, +) diff --git a/unittests/helpers.py b/unittests/helpers.py index ac9d98073..d3d156056 100644 --- a/unittests/helpers.py +++ b/unittests/helpers.py @@ -5,6 +5,7 @@ import unittest import functools import re import typing as T +import zipfile from pathlib import Path from contextlib import contextmanager @@ -176,6 +177,22 @@ def get_rpath(fname: str) -> T.Optional[str]: return None return final +def get_classpath(fname: str) -> T.Optional[str]: + with zipfile.ZipFile(fname) as zip: + with zip.open('META-INF/MANIFEST.MF') as member: + contents = member.read().decode().strip() + lines = [] + for line in contents.splitlines(): + if line.startswith(' '): + # continuation line + lines[-1] += line[1:] + else: + lines.append(line) + manifest = { + k.lower(): v.strip() for k, v in [l.split(':', 1) for l in lines] + } + return manifest.get('class-path') + def get_path_without_cmd(cmd: str, path: str) -> str: pathsep = os.pathsep paths = OrderedSet([Path(p).resolve() for p in path.split(pathsep)]) diff --git a/unittests/machinefiletests.py b/unittests/machinefiletests.py index 7d75ebc34..bf109b247 100644 --- a/unittests/machinefiletests.py +++ b/unittests/machinefiletests.py @@ -42,6 +42,7 @@ import mesonbuild.modules.pkgconfig from run_tests import ( + Backend, get_fake_env ) @@ -368,6 +369,23 @@ class NativeFileTests(BasePlatformTests): self._single_implementation_compiler( 'java', 'javac', 'javac 9.99.77', '9.99.77') + @skip_if_not_language('java') + def test_java_classpath(self): + if self.backend is not Backend.ninja: + raise SkipTest('Jar is only supported with Ninja') + testdir = os.path.join(self.unit_test_dir, '110 classpath') + self.init(testdir) + self.build() + one_build_path = get_classpath(os.path.join(self.builddir, 'one.jar')) + self.assertIsNone(one_build_path) + two_build_path = get_classpath(os.path.join(self.builddir, 'two.jar')) + self.assertEqual(two_build_path, 'one.jar') + self.install() + one_install_path = get_classpath(os.path.join(self.installdir, 'usr/bin/one.jar')) + self.assertIsNone(one_install_path) + two_install_path = get_classpath(os.path.join(self.installdir, 'usr/bin/two.jar')) + self.assertIsNone(two_install_path) + @skip_if_not_language('swift') def test_swift_compiler(self): wrapper = self.helper_create_binary_wrapper( -- cgit v1.2.3