-
-
Notifications
You must be signed in to change notification settings - Fork 646
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
classmap: a jvm console task that outputs mapping from class products to their targets #4081
Changes from 7 commits
a475a10
da08cad
7801217
138d58b
0a421a9
c3e3031
8aff948
6598c71
33a06a3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
# coding=utf-8 | ||
# Copyright 2016 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) | ||
|
||
from pants.backend.jvm.targets.jar_library import JarLibrary | ||
from pants.backend.jvm.tasks.classpath_util import ClasspathUtil | ||
from pants.task.console_task import ConsoleTask | ||
|
||
|
||
class ClassmapTask(ConsoleTask): | ||
"""Print a mapping from class name to the owning target from target's runtime classpath.""" | ||
|
||
@classmethod | ||
def register_options(cls, register): | ||
super(ClassmapTask, cls).register_options(register) | ||
|
||
register('--internal-only', default=False, type=bool, fingerprint=True, | ||
help='Specifies that only class names of internal dependencies should be included.') | ||
register('--transitive', default=True, type=bool, | ||
help='Score all targets in the build graph transitively.') | ||
|
||
def classname_for_classfile(self, target, classpath_products): | ||
contents = ClasspathUtil.classpath_contents((target,), classpath_products) | ||
for f in contents: | ||
classname = ClasspathUtil.classname_for_rel_classfile(f) | ||
# None for non `.class` files | ||
if classname: | ||
yield classname | ||
|
||
def console_output(self, _): | ||
def should_ignore(target): | ||
if target.address.spec.startswith('//:'): | ||
return True | ||
if self.get_options().internal_only and isinstance(target, JarLibrary): | ||
return True | ||
return False | ||
|
||
classpath_product = self.context.products.get_data('runtime_classpath') | ||
targets = self.context.targets() if self.get_options().transitive else self.context.target_roots | ||
for target in targets: | ||
if not should_ignore(target): | ||
for file in self.classname_for_classfile(target, classpath_product): | ||
yield '{} {}'.format(file, target.address.spec) | ||
|
||
@classmethod | ||
def prepare(cls, options, round_manager): | ||
super(ClassmapTask, cls).prepare(options, round_manager) | ||
round_manager.require_data('runtime_classpath') |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -576,11 +576,6 @@ def compute_classes_by_source(self, compile_contexts): | |
classes_by_src[None] = list(unclaimed_classes) | ||
return classes_by_src_by_context | ||
|
||
def classname_for_classfile(self, compile_context, class_file_name): | ||
assert class_file_name.startswith(compile_context.classes_dir) | ||
rel_classfile_path = class_file_name[len(compile_context.classes_dir) + 1:] | ||
return ClasspathUtil.classname_for_rel_classfile(rel_classfile_path) | ||
|
||
def _register_vts(self, compile_contexts): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this not needed anywhere? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yea, this is not used anywhere
|
||
classes_by_source = self.context.products.get_data('classes_by_source') | ||
product_deps_by_src = self.context.products.get_data('product_deps_by_src') | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
# coding=utf-8 | ||
# Copyright 2016 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) | ||
|
||
from contextlib import contextmanager | ||
|
||
from pants.backend.jvm.targets.jar_dependency import JarDependency | ||
from pants.backend.jvm.targets.jar_library import JarLibrary | ||
from pants.backend.jvm.targets.java_library import JavaLibrary | ||
from pants.backend.jvm.tasks.classmap import ClassmapTask | ||
from pants.build_graph.target import Target | ||
from pants.util.contextutil import open_zip | ||
from pants_test.backend.jvm.tasks.jvm_binary_task_test_base import JvmBinaryTaskTestBase | ||
from pants_test.subsystem.subsystem_util import init_subsystem | ||
from pants_test.tasks.task_test_base import ConsoleTaskTestBase | ||
|
||
|
||
class ClassmapTaskTest(ConsoleTaskTestBase, JvmBinaryTaskTestBase): | ||
@classmethod | ||
def task_type(cls): | ||
return ClassmapTask | ||
|
||
def setUp(self): | ||
super(ClassmapTaskTest, self).setUp() | ||
init_subsystem(Target.Arguments) | ||
|
||
self.target_a = self.make_target('a', target_type=JavaLibrary, sources=['a1.java', 'a2.java']) | ||
|
||
self.jar_artifact = self.create_artifact(org='org.example', name='foo', rev='1.0.0') | ||
with open_zip(self.jar_artifact.pants_path, 'w') as jar: | ||
jar.writestr('foo/Foo.class', '') | ||
self.target_b = self.make_target('b', target_type=JarLibrary, | ||
jars=[JarDependency(org='org.example', name='foo', rev='1.0.0')]) | ||
|
||
self.target_c = self.make_target('c', dependencies=[self.target_a, self.target_b], | ||
target_type=JavaLibrary) | ||
|
||
@contextmanager | ||
def prepare_context(self, options=None): | ||
def idict(*args): | ||
return {a: a for a in args} | ||
|
||
options = options or {} | ||
self.set_options(**options) | ||
|
||
task_context = self.context(target_roots=[self.target_c]) | ||
self.add_to_runtime_classpath(task_context, self.target_a, idict('a1.class', 'a2.class')) | ||
self.add_to_runtime_classpath(task_context, self.target_c, idict('c1.class', 'c2.class')) | ||
|
||
classpath_products = self.ensure_classpath_products(task_context) | ||
classpath_products.add_jars_for_targets(targets=[self.target_b], | ||
conf='default', | ||
resolved_jars=[self.jar_artifact]) | ||
yield task_context | ||
|
||
def test_classmap_none(self): | ||
class_mappings = self.execute_console_task() | ||
self.assertEqual([], class_mappings) | ||
|
||
def test_classmap(self): | ||
with self.prepare_context() as context: | ||
class_mappings = self.execute_console_task_given_context(context) | ||
class_mappings_expected = ['a1 a:a', 'a2 a:a', 'c1 c:c', 'c2 c:c', 'foo.Foo b:b'] | ||
self.assertEqual(class_mappings_expected, sorted(class_mappings)) | ||
|
||
def test_classmap_internal_only(self): | ||
with self.prepare_context(options={'internal_only': True}) as context: | ||
class_mappings = self.execute_console_task_given_context(context) | ||
class_mappings_expected = ['a1 a:a', 'a2 a:a', 'c1 c:c', 'c2 c:c'] | ||
self.assertEqual(class_mappings_expected, sorted(class_mappings)) | ||
|
||
def test_classmap_intransitive(self): | ||
with self.prepare_context(options={'transitive': False}) as context: | ||
class_mappings = self.execute_console_task_given_context(context) | ||
class_mappings_expected = ['c1 c:c', 'c2 c:c'] | ||
self.assertEqual(class_mappings_expected, sorted(class_mappings)) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
# coding=utf-8 | ||
# Copyright 2016 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) | ||
|
||
from pants_test.pants_run_integration_test import PantsRunIntegrationTest | ||
|
||
|
||
class ClassmapTaskIntegrationTest(PantsRunIntegrationTest): | ||
# A test target with both transitive internal dependency as well as external dependency | ||
TEST_JVM_TARGET = 'testprojects/tests/java/org/pantsbuild/testproject/testjvms:seven' | ||
INTERNAL_MAPPING = ('org.pantsbuild.testproject.testjvms.TestSeven ' | ||
'testprojects/tests/java/org/pantsbuild/testproject/testjvms:seven') | ||
INTERNAL_TRANSITIVE_MAPPING = ('org.pantsbuild.testproject.testjvms.TestBase ' | ||
'testprojects/tests/java/org/pantsbuild/testproject/testjvms:base') | ||
EXTERNAL_MAPPING = ('org.junit.ClassRule 3rdparty:junit') | ||
|
||
def test_classmap_none(self): | ||
pants_run = self.do_command('classmap', success=True) | ||
self.assertEqual(len(pants_run.stdout_data.strip().split()), 0) | ||
|
||
def test_classmap(self): | ||
pants_run = self.do_command('classmap', self.TEST_JVM_TARGET, success=True) | ||
self.assertIn(self.INTERNAL_MAPPING, pants_run.stdout_data) | ||
self.assertIn(self.INTERNAL_TRANSITIVE_MAPPING, pants_run.stdout_data) | ||
self.assertIn(self.EXTERNAL_MAPPING, pants_run.stdout_data) | ||
|
||
def test_classmap_internal_only(self): | ||
pants_run = self.do_command('classmap', '--internal-only', self.TEST_JVM_TARGET, success=True) | ||
self.assertIn(self.INTERNAL_MAPPING, pants_run.stdout_data) | ||
self.assertIn(self.INTERNAL_TRANSITIVE_MAPPING, pants_run.stdout_data) | ||
self.assertNotIn(self.EXTERNAL_MAPPING, pants_run.stdout_data) | ||
|
||
def test_classmap_intransitive(self): | ||
pants_run = self.do_command('classmap', '--no-transitive', self.TEST_JVM_TARGET, success=True) | ||
self.assertIn(self.INTERNAL_MAPPING, pants_run.stdout_data) | ||
self.assertNotIn(self.INTERNAL_TRANSITIVE_MAPPING, pants_run.stdout_data) | ||
self.assertNotIn(self.EXTERNAL_MAPPING, pants_run.stdout_data) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this necessary? Seems easy enough to filter out any particular target before calling the goal.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed the
//:
check, agree easy to filter, either before (if calling with--internal-only
) or after on the output.The extra benefit is to be honest allow us to inspect all dependencies including those from build tools.