Skip to content

Commit

Permalink
Record the compile classpath used to compile jvm targets.
Browse files Browse the repository at this point in the history
This change makes `jvm_compile` write text files storing the
classpath for each jvm target it compiles to:

    .pants.d/compile/zinc/<sha>/<target.id>/<sha>/classes/compile_classpath/<target.id>.txt

Since these files are written to the classes/ directory, they end
up being bundled into jvm binaries. This can be useful to determine
how an artifact was built after the fact.

This behavior is enabled by default, and can be disabled with:

    --no-compile-zinc-capture-classpath

Testing Done:
Added tests to tests/python/pants_test/backend/jvm/tasks/jvm_compile/java/test_zinc_compile_integration.py.

CI went green here: https://travis-ci.org/pantsbuild/pants/builds/116469638

Bugs closed: 3050

Reviewed at https://rbcommons.com/s/twitter/r/3576/
  • Loading branch information
gmalmquist committed Mar 17, 2016
1 parent 1299dde commit ff02fbd
Show file tree
Hide file tree
Showing 5 changed files with 58 additions and 8 deletions.
17 changes: 15 additions & 2 deletions src/python/pants/backend/jvm/tasks/jvm_compile/jvm_compile.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@
unicode_literals, with_statement)

import functools
import hashlib
import itertools
import os
from collections import defaultdict
from multiprocessing import cpu_count
Expand Down Expand Up @@ -153,6 +151,11 @@ def register_options(cls, register):
fingerprint=True,
help='Capture compilation output to per-target logs.')

register('--capture-classpath', advanced=True, action='store_true', default=True,
fingerprint=True,
help='Capture classpath to per-target newline-delimited text files. These files will '
'be packaged into any jar artifacts that are created from the jvm targets.')

@classmethod
def prepare(cls, options, round_manager):
super(JvmCompile, cls).prepare(options, round_manager)
Expand Down Expand Up @@ -431,6 +434,14 @@ def do_compile(self,
except ExecutionFailure as e:
raise TaskError("Compilation failure: {}".format(e))

def _record_compile_classpath(self, classpath, targets, outdir):
text = '\n'.join(classpath)
for target in targets:
path = os.path.join(outdir, 'compile_classpath', '{}.txt'.format(target.id))
safe_mkdir(os.path.dirname(path), clean=False)
with open(path, 'w') as f:
f.write(text)

def _compile_vts(self, vts, sources, analysis_file, upstream_analysis, classpath, outdir,
log_file, progress_message, settings, fatal_warnings, counter):
"""Compiles sources for the given vts into the given output dir.
Expand Down Expand Up @@ -467,6 +478,8 @@ def _compile_vts(self, vts, sources, analysis_file, upstream_analysis, classpath
# change triggering the error is reverted, we won't rebuild to restore the missing
# classfiles. So we force-invalidate here, to be on the safe side.
vts.force_invalidate()
if self.get_options().capture_classpath:
self._record_compile_classpath(classpath, vts.targets, outdir)
self.compile(self._args, classpath, sources, outdir, upstream_analysis, analysis_file,
log_file, settings, fatal_warnings)

Expand Down
15 changes: 10 additions & 5 deletions src/python/pants/build_graph/target.py
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,15 @@ def maybe_readable_identify(cls, targets):
"""
return cls.maybe_readable_combine_ids([target.id for target in targets])

@classmethod
def compute_target_id(cls, address):
"""Computes a target id from the given address."""
id_candidate = address.path_safe_spec
if len(id_candidate) >= 200:
# two dots + 79 char head + 79 char tail + 40 char sha1
return '{}.{}.{}'.format(id_candidate[:79], sha1(id_candidate).hexdigest(), id_candidate[-79:])
return id_candidate

@staticmethod
def combine_ids(ids):
"""Generates a combined id for a set of ids.
Expand Down Expand Up @@ -564,11 +573,7 @@ def id(self):
:API: public
"""
id_candidate = self.address.path_safe_spec
if len(id_candidate) >= 200:
# two dots + 79 char head + 79 char tail + 40 char sha1
return '{}.{}.{}'.format(id_candidate[:79], sha1(id_candidate).hexdigest(), id_candidate[-79:])
return id_candidate
return self.compute_target_id(self.address)

@property
def identifier(self):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ python_tests(
name='zinc_compile_integration',
sources=['test_zinc_compile_integration.py'],
dependencies=[
'src/python/pants/build_graph',
'tests/python/pants_test/backend/jvm/tasks/jvm_compile:base_compile_integration_test',
],
tags = {'integration'},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@
from __future__ import (absolute_import, division, generators, nested_scopes, print_function,
unicode_literals, with_statement)

import os
import re

from pants.build_graph.address import Address
from pants.build_graph.target import Target
from pants_test.backend.jvm.tasks.jvm_compile.base_compile_integration_test import BaseCompileIT


Expand Down Expand Up @@ -146,3 +151,25 @@ def test_combination(target, default_fatal_warnings, expect_success):
test_combination('fatal', default_fatal_warnings=False, expect_success=False)
test_combination('nonfatal', default_fatal_warnings=True, expect_success=True)
test_combination('nonfatal', default_fatal_warnings=False, expect_success=True)

def test_record_classpath(self):
target_spec = 'testprojects/src/java/org/pantsbuild/testproject/printversion:printversion'
target_id = Target.compute_target_id(Address.parse(target_spec))
classpath_filename = '{}.txt'.format(target_id)
with self.do_test_compile(target_spec,
expected_files=[classpath_filename, 'PrintVersion.class'],
extra_args=['--compile-zinc-capture-classpath']) as found:
found_classpath_file = self.get_only(found, classpath_filename)
self.assertTrue(found_classpath_file
.endswith(os.path.join('compile_classpath', classpath_filename)))
with open(found_classpath_file, 'r') as f:
self.assertIn(target_id, f.read())

def test_no_record_classpath(self):
target_spec = 'testprojects/src/java/org/pantsbuild/testproject/printversion:printversion'
target_id = Target.compute_target_id(Address.parse(target_spec))
classpath_filename = '{}.txt'.format(target_id)
with self.do_test_compile(target_spec,
expected_files=['PrintVersion.class'],
extra_args=['--no-compile-zinc-capture-classpath']) as found:
self.assertFalse(classpath_filename in found)
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,11 @@ def test_manifest_entries_bundle(self):
def test_deploy_excludes(self):
jar_filename = os.path.join('dist', 'deployexcludes.jar')
safe_delete(jar_filename)
command = ['binary', 'testprojects/src/java/org/pantsbuild/testproject/deployexcludes']
command = [
'--no-compile-zinc-capture-classpath',
'binary',
'testprojects/src/java/org/pantsbuild/testproject/deployexcludes',
]
with self.pants_results(command) as pants_run:
self.assert_success(pants_run)
# The resulting binary should not contain any guava classes
Expand Down

0 comments on commit ff02fbd

Please sign in to comment.