diff --git a/src/python/pants/backend/jvm/targets/jvm_binary.py b/src/python/pants/backend/jvm/targets/jvm_binary.py index c68f307480b..652e1a6acd7 100644 --- a/src/python/pants/backend/jvm/targets/jvm_binary.py +++ b/src/python/pants/backend/jvm/targets/jvm_binary.py @@ -157,7 +157,6 @@ def skip_signatures_and_duplicates_concat_well_known_metadata(cls, default_dup_a has the following special handling: - jar signature metadata is dropped - - ``java.util.ServiceLoader`` provider-configuration files are concatenated in the order - ``java.util.ServiceLoader`` provider-configuration files are concatenated in the order encountered @@ -232,6 +231,39 @@ def value(self): return self._jar_rules +class ManifestEntries(FingerprintedMixin): + """Describes additional items to add to the app manifest.""" + + class ExpectedDictionaryError(Exception): + pass + + def __init__(self, entries=None): + """ + :param entries: Additional headers, value pairs to add to the MANIFEST.MF. + You can just add fixed string header / value pairs. + :type entries: dictionary of string : string + """ + self.payload = Payload() + if entries: + if not isinstance(entries, dict): + raise self.ExpectedDictionaryError("entries must be a dictionary of strings.") + for key in entries.keys(): + if not isinstance(key, string_types): + raise self.ExpectedDictionaryError( + "entries must be dictionary of strings, got key {} type {}" + .format(key, type(key).__name__)) + self.payload.add_fields({ + 'entries' : PrimitiveField(entries or {}), + }) + + def fingerprint(self): + return self.payload.fingerprint() + + @property + def entries(self): + return self.payload.entries + + class JvmBinary(JvmTarget): """Produces a JVM binary optionally identifying a launcher main class. @@ -252,6 +284,7 @@ def __init__(self, source=None, deploy_excludes=None, deploy_jar_rules=None, + manifest_entries=None, **kwargs): """ :param string main: The name of the ``main`` class, e.g., @@ -275,6 +308,8 @@ def __init__(self, code but exclude the conflicting ``jar`` when deploying. :param deploy_jar_rules: `Jar rules <#jar_rules>`_ for packaging this binary in a deploy jar. + :param manifest_entries: dict that specifies entries for `ManifestEntries <#manifest_entries>`_ + for adding to MANIFEST.MF when packaging this binary. :param configurations: Ivy configurations to resolve for this target. This parameter is not intended for general use. :type configurations: tuple of strings @@ -288,12 +323,17 @@ def __init__(self, raise TargetDefinitionException(self, 'deploy_jar_rules must be a JarRules specification. got {}' .format(type(deploy_jar_rules).__name__)) + if manifest_entries and not isinstance(manifest_entries, dict): + raise TargetDefinitionException(self, + 'manifest_entries must be a dict. got {}' + .format(type(manifest_entries).__name__)) sources = [source] if source else None payload = payload or Payload() payload.add_fields({ 'basename' : PrimitiveField(basename or name), 'deploy_excludes' : ExcludesField(self.assert_list(deploy_excludes, expected_type=Exclude)), 'deploy_jar_rules' : FingerprintedField(deploy_jar_rules or JarRules.default()), + 'manifest_entries' : FingerprintedField(ManifestEntries(manifest_entries)), 'main': PrimitiveField(main), }) @@ -318,3 +358,7 @@ def deploy_jar_rules(self): @property def main(self): return self.payload.main + + @property + def manifest_entries(self): + return self.payload.manifest_entries diff --git a/src/python/pants/backend/jvm/tasks/BUILD b/src/python/pants/backend/jvm/tasks/BUILD index f7cb7556d0a..6e9315662f0 100644 --- a/src/python/pants/backend/jvm/tasks/BUILD +++ b/src/python/pants/backend/jvm/tasks/BUILD @@ -224,6 +224,7 @@ python_library( '3rdparty/python/twitter/commons:twitter.common.collections', 'src/python/pants/backend/jvm/subsystems:jar_tool', 'src/python/pants/backend/jvm/targets:java', + 'src/python/pants/base:build_environment', 'src/python/pants/base:exceptions', 'src/python/pants/java/jar:manifest', 'src/python/pants/util:contextutil', diff --git a/src/python/pants/backend/jvm/tasks/jar_create.py b/src/python/pants/backend/jvm/tasks/jar_create.py index a95289051a1..ac31fca1efd 100644 --- a/src/python/pants/backend/jvm/tasks/jar_create.py +++ b/src/python/pants/backend/jvm/tasks/jar_create.py @@ -75,9 +75,9 @@ def execute(self): jar_name = jarname(target) jar_path = os.path.join(self.workdir, jar_name) with self.create_jar(target, jar_path) as jarfile: - jar_builder = self.create_jar_builder(jarfile) - if target in jar_builder.add_target(target): - self.context.products.get('jars').add(target, self.workdir).append(jar_name) + with self.create_jar_builder(jarfile) as jar_builder: + if target in jar_builder.add_target(target): + self.context.products.get('jars').add(target, self.workdir).append(jar_name) @contextmanager def create_jar(self, target, path): diff --git a/src/python/pants/backend/jvm/tasks/jar_task.py b/src/python/pants/backend/jvm/tasks/jar_task.py index a7ce1900c66..3ddc2b772d6 100644 --- a/src/python/pants/backend/jvm/tasks/jar_task.py +++ b/src/python/pants/backend/jvm/tasks/jar_task.py @@ -15,8 +15,9 @@ from pants.backend.jvm.subsystems.jar_tool import JarTool from pants.backend.jvm.targets.java_agent import JavaAgent -from pants.backend.jvm.targets.jvm_binary import Duplicate, JarRules, Skip +from pants.backend.jvm.targets.jvm_binary import Duplicate, JarRules, JvmBinary, Skip from pants.backend.jvm.tasks.nailgun_task import NailgunTask +from pants.base.build_environment import get_buildroot from pants.base.exceptions import TaskError from pants.binary_util import safe_args from pants.java.jar.manifest import Manifest @@ -84,7 +85,7 @@ def materialize(self, scratch_dir): def __init__(self): self._entries = [] self._jars = [] - self._manifest = None + self._manifest_entry = None self._main = None self._classpath = None @@ -143,7 +144,7 @@ def writestr(self, path, contents): def _add_entry(self, entry): if Manifest.PATH == entry.dest: - self._manifest = entry + self._manifest_entry = entry else: self._entries.append(entry) @@ -179,14 +180,27 @@ def as_cli_entry(entry): with safe_args(files, options, delimiter=',') as files_args: with safe_args(jars, options, delimiter=',') as jars_args: - if self._main: + # If you specify --manifest to jar-tool you cannot specify --main. + if self._manifest_entry: + manifest_file = self._manifest_entry.materialize(manifest_stage_dir) + else: + manifest_file = None + + if self._main and manifest_file: + main_arg = None + with open(manifest_file, 'a') as f: + f.write("Main-Class: {}\n".format(self._main)) + else: + main_arg = self._main + + if main_arg: args.append('-main={}'.format(self._main)) if classpath_args: args.append('-classpath={}'.format(','.join(classpath_args))) - if self._manifest: - args.append('-manifest={}'.format(self._manifest.materialize(manifest_stage_dir))) + if manifest_file: + args.append('-manifest={}'.format(manifest_file)) if files_args: args.append('-files={}'.format(','.join(files_args))) @@ -228,6 +242,7 @@ def _action_name(cls, action): def __init__(self, *args, **kwargs): super(JarTask, self).__init__(*args, **kwargs) self.set_distribution(jdk=True) + # TODO(John Sirois): Consider poking a hole for custom jar-tool jvm args - namely for Xmx # control. @@ -281,9 +296,8 @@ class JarBuilder(AbstractClass): """A utility to aid in adding the classes and resources associated with targets to a jar.""" @staticmethod - def _write_agent_manifest(agent, jar): + def _add_agent_manifest(agent, manifest): # TODO(John Sirois): refactor an agent model to support 'Boot-Class-Path' properly. - manifest = Manifest() manifest.addentry(Manifest.MANIFEST_VERSION, '1.0') if agent.premain: manifest.addentry('Premain-Class', agent.premain) @@ -295,7 +309,16 @@ def _write_agent_manifest(agent, jar): manifest.addentry('Can-Retransform-Classes', 'true') if agent.can_set_native_method_prefix: manifest.addentry('Can-Set-Native-Method-Prefix', 'true') - jar.writestr(Manifest.PATH, manifest.contents()) + + @staticmethod + def _add_manifest_entries(jvm_binary_target, manifest): + """Add additional fields to MANIFEST.MF as declared in the ManifestEntries structure. + + :param JvmBinary jvm_binary_target: + :param Manifest manifest: + """ + for header, value in jvm_binary_target.manifest_entries.entries.iteritems(): + manifest.addentry(header, value) @staticmethod def prepare(round_manager): @@ -313,6 +336,8 @@ def prepare(round_manager): def __init__(self, context, jar): self._context = context self._jar = jar + self._manifest = Manifest() + def add_target(self, target, recursive=False): """Adds the classes and resources for a target to an open jar. @@ -355,7 +380,10 @@ def add_products(target_products): add_products(resources_target) if isinstance(tgt, JavaAgent): - self._write_agent_manifest(tgt, self._jar) + self._add_agent_manifest(tgt, self._manifest) + + if isinstance(tgt, JvmBinary): + self._add_manifest_entries(tgt, self._manifest) if recursive: target.walk(add_to_jar) @@ -364,6 +392,16 @@ def add_products(target_products): return targets_added + def commit_manifest(self, jar): + """Updates the manifest in the jar being written to. + + Typically done right before closing the .jar. This gives a chance for all targets to bundle + in their contributions to the manifest. + """ + if not self._manifest.is_empty(): + jar.writestr(Manifest.PATH, self._manifest.contents()) + + @contextmanager def create_jar_builder(self, jar): """Creates a ``JarTask.JarBuilder`` ready for use. @@ -372,4 +410,6 @@ def create_jar_builder(self, jar): :param jar: An opened ``pants.backend.jvm.tasks.jar_task.Jar`. """ - return self.JarBuilder(self.context, jar) + builder = self.JarBuilder(self.context, jar) + yield builder + builder.commit_manifest(jar) diff --git a/src/python/pants/backend/jvm/tasks/jvm_binary_task.py b/src/python/pants/backend/jvm/tasks/jvm_binary_task.py index 50787d01be4..2404372a0fe 100644 --- a/src/python/pants/backend/jvm/tasks/jvm_binary_task.py +++ b/src/python/pants/backend/jvm/tasks/jvm_binary_task.py @@ -69,16 +69,15 @@ def monolithic_jar(self, binary, path, with_external_deps): compressed=True) as jar: with self.context.new_workunit(name='add-internal-classes'): - self.create_jar_builder(jar).add_target(binary, recursive=True) - - if with_external_deps: - with self.context.new_workunit(name='add-dependency-jars'): - for basedir, external_jar in self.list_external_jar_dependencies(binary): - external_jar_path = os.path.join(basedir, external_jar) - self.context.log.debug(' dumping {}'.format(external_jar_path)) - jar.writejar(external_jar_path) - - yield jar + with self.create_jar_builder(jar) as jar_builder: + jar_builder.add_target(binary, recursive=True) + if with_external_deps: + with self.context.new_workunit(name='add-dependency-jars'): + for basedir, external_jar in self.list_external_jar_dependencies(binary): + external_jar_path = os.path.join(basedir, external_jar) + self.context.log.debug(' dumping {}'.format(external_jar_path)) + jar.writejar(external_jar_path) + yield jar def _mapped_dependencies(self, jardepmap, binary, confs): # TODO(John Sirois): rework product mapping towards well known types diff --git a/src/python/pants/java/jar/manifest.py b/src/python/pants/java/jar/manifest.py index 77eb5639a7a..44124284d04 100644 --- a/src/python/pants/java/jar/manifest.py +++ b/src/python/pants/java/jar/manifest.py @@ -42,8 +42,13 @@ def addentry(self, header, value): raise ValueError('Header name must be 68 characters or less, given {}'.format(header)) if self._contents: self._contents += '\n' - self._contents += '\n'.join(self._wrap('{}: {}'.format(header, value))) + self._contents += '\n'.join(self._wrap('{header}: {value}'.format(header=header, value=value))) def contents(self): padded = self._contents + '\n' return padded.encode('ascii') + + def is_empty(self): + if self._contents.strip(): + return False + return True diff --git a/src/python/pants/scm/BUILD b/src/python/pants/scm/BUILD index fc1031c2997..c1d9a9773af 100644 --- a/src/python/pants/scm/BUILD +++ b/src/python/pants/scm/BUILD @@ -13,6 +13,7 @@ python_library( name = 'scm', sources = ['scm.py'], dependencies = [ + 'src/python/pants/util:contextutil', 'src/python/pants/util:meta', ], ) diff --git a/src/python/pants/scm/git.py b/src/python/pants/scm/git.py index 9077f346211..b81d4eab5c4 100644 --- a/src/python/pants/scm/git.py +++ b/src/python/pants/scm/git.py @@ -10,13 +10,14 @@ import traceback from pants.scm.scm import Scm +from pants.util.contextutil import pushd class Git(Scm): """An Scm implementation backed by git.""" @classmethod - def detect_worktree(cls, binary='git'): + def detect_worktree(cls, binary='git', dir=None): """Detect the git working tree above cwd and return it; else, return None. binary: The path to the git binary to use, 'git' by default. @@ -25,7 +26,11 @@ def detect_worktree(cls, binary='git'): # pants.base.build_environment.get_scm, encapsulate in a true factory method. cmd = [binary, 'rev-parse', '--show-toplevel'] try: - process, out = cls._invoke(cmd) + if dir: + with pushd(dir): + process, out = cls._invoke(cmd) + else: + process, out = cls._invoke(cmd) cls._check_result(cmd, process.returncode, raise_type=Scm.ScmException) except Scm.ScmException: return None diff --git a/testprojects/src/java/org/pantsbuild/testproject/manifest/BUILD b/testprojects/src/java/org/pantsbuild/testproject/manifest/BUILD new file mode 100644 index 00000000000..43fe9ffa251 --- /dev/null +++ b/testprojects/src/java/org/pantsbuild/testproject/manifest/BUILD @@ -0,0 +1,11 @@ +# Copyright 2015 Pants project contributors (see CONTRIBUTORS.md). +# Licensed under the Apache License, Version 2.0 (see LICENSE). + +jvm_binary( + source = 'Manifest.java', + name = 'manifest', + main = 'org.pantsbuild.testproject.manifest.Manifest', + manifest_entries = { + 'Implementation-Version' : '1.2.3', + } +) diff --git a/testprojects/src/java/org/pantsbuild/testproject/manifest/Manifest.java b/testprojects/src/java/org/pantsbuild/testproject/manifest/Manifest.java new file mode 100644 index 00000000000..6cfce9e9095 --- /dev/null +++ b/testprojects/src/java/org/pantsbuild/testproject/manifest/Manifest.java @@ -0,0 +1,11 @@ +// Copyright 2015 Pants project contributors (see CONTRIBUTORS.md). +// Licensed under the Apache License, Version 2.0 (see LICENSE). + +package org.pantsbuild.testproject.manifest; + +public class Manifest { + public static void main(String args[]) { + System.out.println("Hello World! Version: " + + Package.getPackage("org.pantsbuild.testproject.manifest").getImplementationVersion()); + } +} diff --git a/tests/python/pants_test/backend/jvm/targets/test_jvm_binary.py b/tests/python/pants_test/backend/jvm/targets/test_jvm_binary.py index c72b19c196d..4685c1f5636 100644 --- a/tests/python/pants_test/backend/jvm/targets/test_jvm_binary.py +++ b/tests/python/pants_test/backend/jvm/targets/test_jvm_binary.py @@ -10,7 +10,7 @@ from pants.backend.jvm.register import build_file_aliases as register_jvm from pants.backend.jvm.targets.exclude import Exclude -from pants.backend.jvm.targets.jvm_binary import Duplicate, JarRules, Skip +from pants.backend.jvm.targets.jvm_binary import Duplicate, JarRules, ManifestEntries, Skip from pants.base.address import BuildFileAddress from pants.base.exceptions import TargetDefinitionException from pants.base.payload_field import FingerprintedField @@ -76,6 +76,7 @@ def test_simple(self): self.assertEquals([], target.payload.deploy_excludes) self.assertEquals(JarRules.default(), target.deploy_jar_rules) self.assertEquals(JarRules.default(), target.payload.deploy_jar_rules) + self.assertEquals({}, target.payload.manifest_entries.entries); def test_default_base(self): self.add_to_build_file('BUILD', dedent(''' @@ -138,7 +139,7 @@ def test_bad_jar_rules(self): build_file = self.add_to_build_file('BUILD', dedent(''' jvm_binary(name='foo', main='com.example.Foo', - deploy_jar_rules= 'invalid', + deploy_jar_rules='invalid', ) ''')) with self.assertRaisesRegexp(TargetDefinitionException, @@ -146,12 +147,12 @@ def test_bad_jar_rules(self): r'deploy_jar_rules must be a JarRules specification. got str'): self.build_graph.inject_address_closure(BuildFileAddress(build_file, 'foo')) - def _assert_fingerprints_not_equal(self, rules): - for rule in rules: - for other_rule in rules: - if rule == other_rule: + def _assert_fingerprints_not_equal(self, fields): + for field in fields: + for other_field in fields: + if field == other_field: continue - self.assertNotEquals(rule.fingerprint(), other_rule.fingerprint()) + self.assertNotEquals(field.fingerprint(), other_field.fingerprint()) def test_jar_rules_field(self): field1 = FingerprintedField(JarRules(rules=[Duplicate('foo', Duplicate.SKIP)])) @@ -173,3 +174,50 @@ def test_jar_rules_field(self): self.assertEquals(field6.fingerprint(), field6_same.fingerprint()) self.assertEquals(field8.fingerprint(), field8_same.fingerprint()) self._assert_fingerprints_not_equal([field1, field2, field3, field4, field5, field6, field7]) + + def test_manifest_entries(self): + self.add_to_build_file('BUILD', dedent(''' + jvm_binary(name='foo', + main='com.example.Foo', + manifest_entries= { + 'Foo-Field' : 'foo', + } + ) + ''')) + target = self.target('//:foo') + self.assertTrue(isinstance(target.payload.manifest_entries, ManifestEntries)) + entries = target.payload.manifest_entries.entries + self.assertEquals({ 'Foo-Field' : 'foo'}, entries) + + def test_manifest_not_dict(self): + self.add_to_build_file('BUILD', dedent(''' + jvm_binary(name='foo', + main='com.example.Foo', + manifest_entries= 'foo', + ) + ''')) + with self.assertRaisesRegexp(TargetDefinitionException, + r'Invalid target JvmBinary\(BuildFileAddress\(.*BUILD, foo\)\): ' + r'manifest_entries must be a dict. got str'): + self.target('//:foo') + + def test_manifest_bad_key(self): + self.add_to_build_file('BUILD', dedent(''' + jvm_binary(name='foo', + main='com.example.Foo', + manifest_entries= { + jar(org='bad', name='bad', rev='bad') : 'foo', + } + ) + ''')) + with self.assertRaisesRegexp(ManifestEntries.ExpectedDictionaryError, + r'entries must be dictionary of strings, got key bad-bad-bad type JarDependency'): + self.target('//:foo') + + def test_manifest_entries_fingerprint(self): + field1 = ManifestEntries() + field2 = ManifestEntries({'Foo-Field' : 'foo'}) + field2_same = ManifestEntries({'Foo-Field' : 'foo'}) + field3 = ManifestEntries({'Foo-Field' : 'foo', 'Bar-Field' : 'bar'}) + self.assertEquals(field2.fingerprint(), field2_same.fingerprint()) + self._assert_fingerprints_not_equal([field1, field2, field3]) diff --git a/tests/python/pants_test/backend/jvm/tasks/BUILD b/tests/python/pants_test/backend/jvm/tasks/BUILD index 05ca1a29063..087b325ef40 100644 --- a/tests/python/pants_test/backend/jvm/tasks/BUILD +++ b/tests/python/pants_test/backend/jvm/tasks/BUILD @@ -53,6 +53,20 @@ python_tests( ] ) + + +python_tests( + name = 'ivy_resolve', + sources = ['test_ivy_resolve.py'], + dependencies = [ + '3rdparty/python/twitter/commons:twitter.common.collections', + 'src/python/pants/backend/jvm/targets:jvm', + 'src/python/pants/backend/jvm/targets:scala', + 'src/python/pants/backend/jvm/tasks:ivy_resolve', + 'tests/python/pants_test/jvm:nailgun_task_test_base', + ] +) + python_tests( name = 'junit_run', sources = ['test_junit_run.py'], @@ -67,6 +81,15 @@ python_tests( ] ) +python_tests( + name = 'jvm_binary_integration', + sources = ['test_jvm_binary_integration.py'], + dependencies = [ + 'src/python/pants/base:build_environment', + 'tests/python/pants_test:int-test', + ], +) + python_tests( name = 'scalastyle', sources = ['test_scalastyle.py'], @@ -79,18 +102,6 @@ python_tests( ] ) -python_tests( - name = 'ivy_resolve', - sources = ['test_ivy_resolve.py'], - dependencies = [ - '3rdparty/python/twitter/commons:twitter.common.collections', - 'src/python/pants/backend/jvm/targets:jvm', - 'src/python/pants/backend/jvm/targets:scala', - 'src/python/pants/backend/jvm/tasks:ivy_resolve', - 'tests/python/pants_test/jvm:nailgun_task_test_base', - ] -) - python_tests( name = 'unpack_jars', sources = ['test_unpack_jars.py'], diff --git a/tests/python/pants_test/backend/jvm/tasks/test_jvm_binary_integration.py b/tests/python/pants_test/backend/jvm/tasks/test_jvm_binary_integration.py new file mode 100644 index 00000000000..49eb021354a --- /dev/null +++ b/tests/python/pants_test/backend/jvm/tasks/test_jvm_binary_integration.py @@ -0,0 +1,31 @@ +# coding=utf-8 +# Copyright 2015 Pants project contributors (see CONTRIBUTORS.md). +# Licensed under the Apache License, Version 2.0 (see LICENSE). + +from __future__ import (absolute_import, division, generators, nested_scopes, print_function, + unicode_literals, with_statement) + +import os +import subprocess + +from pants.base.build_environment import get_buildroot +from pants_test.pants_run_integration_test import PantsRunIntegrationTest + + +class JvmBinaryIntegrationTest(PantsRunIntegrationTest): + + def test_manifest_entries(self): + self.assert_success(self.run_pants(['clean-all'])) + args = ['binary', 'testprojects/src/java/org/pantsbuild/testproject/manifest'] + pants_run = self.run_pants(args, {}) + self.assert_success(pants_run) + + out_path = os.path.join(get_buildroot(), 'dist') + java_run = subprocess.Popen(['java', '-cp', 'manifest.jar', + 'org.pantsbuild.testproject.manifest.Manifest'], + stdout=subprocess.PIPE, + cwd=out_path) + java_retcode = java_run.wait() + java_out = java_run.stdout.read() + self.assertEquals(java_retcode, 0) + self.assertIn('Hello World! Version: 1.2.3', java_out) diff --git a/tests/python/pants_test/java/jar/test_manifest.py b/tests/python/pants_test/java/jar/test_manifest.py index d8f0bb2aa8d..33fca67b99b 100644 --- a/tests/python/pants_test/java/jar/test_manifest.py +++ b/tests/python/pants_test/java/jar/test_manifest.py @@ -12,6 +12,12 @@ class TestManifest(unittest.TestCase): + def test_isempty(self): + manifest = Manifest() + self.assertTrue(manifest.is_empty()) + manifest.addentry('Header', 'value') + self.assertFalse(manifest.is_empty()) + def test_addentry(self): manifest = Manifest() manifest.addentry('Header', 'value') diff --git a/tests/python/pants_test/scm/test_git.py b/tests/python/pants_test/scm/test_git.py index 991d18034c2..8376b3a5d46 100644 --- a/tests/python/pants_test/scm/test_git.py +++ b/tests/python/pants_test/scm/test_git.py @@ -129,6 +129,7 @@ def test_integration(self): tip_sha = self.git.commit_id self.assertTrue(tip_sha) + self.assertRegexpMatches(tip_sha, '^[0-9a-f]{40}$') self.assertTrue(tip_sha in self.git.changelog()) @@ -432,3 +433,12 @@ def test_detect_worktree_working_git(self): fp.write('echo ' + expected_worktree_dir) self.assertEqual(expected_worktree_dir, Git.detect_worktree()) self.assertEqual(expected_worktree_dir, Git.detect_worktree(binary=git)) + + def test_detect_worktree_somewhere_else(self): + with temporary_dir() as somewhere_else: + with pushd(somewhere_else): + loc = Git.detect_worktree(dir=somewhere_else) + self.assertEquals(None, loc) + subprocess.check_call(['git', 'init']) + loc = Git.detect_worktree(dir=somewhere_else) + self.assertEquals(os.path.realpath(somewhere_else), loc) diff --git a/tests/python/pants_test/tasks/test_jar_task.py b/tests/python/pants_test/tasks/test_jar_task.py index ddc1b0cf02f..e58c8910389 100644 --- a/tests/python/pants_test/tasks/test_jar_task.py +++ b/tests/python/pants_test/tasks/test_jar_task.py @@ -7,6 +7,7 @@ import os import re +import subprocess from collections import defaultdict from contextlib import contextmanager from textwrap import dedent @@ -16,7 +17,7 @@ from pants.backend.jvm.tasks.jar_task import JarTask from pants.goal.products import MultipleRootedProducts -from pants.util.contextutil import open_zip, temporary_dir, temporary_file +from pants.util.contextutil import open_zip, pushd, temporary_dir, temporary_file from pants.util.dirutil import safe_mkdir, safe_mkdtemp, safe_rmtree from pants_test.jvm.jar_task_test_base import JarTaskTestBase @@ -203,6 +204,17 @@ def setUp(self): super(JarBuilderTest, self).setUp() self.set_options(max_subprocess_args=100) + + def _add_to_classes_by_target(self, context, tgt, filename): + class_products = context.products.get_data('classes_by_target', + lambda: defaultdict(MultipleRootedProducts)) + java_agent_products = MultipleRootedProducts() + java_agent_products.add_rel_paths(os.path.join(self.build_root, + os.path.dirname(filename)), + [os.path.basename(filename)]) + class_products[tgt] = java_agent_products + + def test_agent_manifest(self): self.add_to_build_file('src/java/pants/agents', dedent(''' java_agent( @@ -218,21 +230,16 @@ def test_agent_manifest(self): context = self.context(target_roots=[java_agent]) jar_task = self.prepare_jar_task(context) - class_products = context.products.get_data('classes_by_target', - lambda: defaultdict(MultipleRootedProducts)) - java_agent_products = MultipleRootedProducts() - self.create_file('.pants.d/javac/classes/FakeAgent.class', '0xCAFEBABE') - java_agent_products.add_rel_paths(os.path.join(self.build_root, '.pants.d/javac/classes'), - ['FakeAgent.class']) - class_products[java_agent] = java_agent_products - + classfile = '.pants.d/javac/classes/FakeAgent.class' + self.create_file(classfile, '0xCAFEBABE') + self._add_to_classes_by_target(context, java_agent, classfile) context.products.safe_create_data('resources_by_target', - lambda: defaultdict(MultipleRootedProducts)) + lambda: defaultdict(MultipleRootedProducts)) with self.jarfile() as existing_jarfile: with jar_task.open_jar(existing_jarfile) as jar: - jar_builder = jar_task.create_jar_builder(jar) - jar_builder.add_target(java_agent) + with jar_task.create_jar_builder(jar) as jar_builder: + jar_builder.add_target(java_agent) with open_zip(existing_jarfile) as jar: self.assert_listing(jar, 'FakeAgent.class') @@ -249,3 +256,39 @@ def test_agent_manifest(self): } self.assertEquals(set(expected_entries.items()), set(expected_entries.items()).intersection(set(all_entries.items()))) + + def test_manifest_items(self): + self.add_to_build_file('src/java/hello', dedent(''' + jvm_binary( + name='hello', + main='hello.Hello', + manifest_entries = { + 'Foo': 'foo-value', + 'Implementation-Version': '1.2.3', + }, + )''').strip()) + binary_target = self.target('src/java/hello:hello') + context = self.context(target_roots=[binary_target]) + + classfile = '.pants.d/javac/classes/hello/Hello.class' + self.create_file(classfile, '0xDEADBEEF') + self._add_to_classes_by_target(context, binary_target, classfile) + context.products.safe_create_data('resources_by_target', + lambda: defaultdict(MultipleRootedProducts)) + + jar_task = self.prepare_jar_task(context) + + with self.jarfile() as existing_jarfile: + with jar_task.open_jar(existing_jarfile) as jar: + with jar_task.create_jar_builder(jar) as jar_builder: + jar_builder.add_target(binary_target) + + with open_zip(existing_jarfile) as jar: + manifest = jar.read('META-INF/MANIFEST.MF').strip() + all_entries = dict(tuple(re.split(r'\s*:\s*', line, 1)) for line in manifest.splitlines()) + expected_entries = { + 'Foo': 'foo-value', + 'Implementation-Version': '1.2.3', + } + self.assertEquals(set(expected_entries.items()), + set(expected_entries.items()).intersection(set(all_entries.items())))