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
implement exports literal in jvm_target #4329
Changes from all commits
2bb4c99
2cec593
6fbe699
9601235
98cc992
548ca1e
59b00fb
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 |
---|---|---|
|
@@ -13,23 +13,33 @@ | |
from pants.util.contextutil import open_zip | ||
|
||
|
||
def _resolve_aliases(target): | ||
def _resolve_strict_dependencies(target): | ||
for declared in target.dependencies: | ||
if type(declared) in (AliasTarget, Target): | ||
# Is an alias. Recurse to expand. | ||
for r in _resolve_aliases(declared): | ||
for r in _resolve_strict_dependencies(declared): | ||
yield r | ||
else: | ||
yield declared | ||
|
||
for export in _resolve_exports(declared): | ||
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. A function named 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. done. |
||
yield export | ||
|
||
|
||
def _resolve_exports(target): | ||
for export in getattr(target, 'exports', []): | ||
for exp in _resolve_exports(export): | ||
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. So exports are transitive? That should be documented on the In fact, this whole functionality should be written up on the docsite. 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. Hm. I feel like they shouldn't be transitive. If the goal is to make the exports of a library explicit at the library's declaration, then I think exports being transitive is confusing. For example, in order to find all the exported targets of 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. Just to clarify between the two scenarios:
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. Ah the bazel discussion actually means that it is transitively for 2), so it will definitely be transitive for 1) 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. Hm. After reading that thread, I think I changed my mind. I hadn't thought it through.
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. I agree. Exports should be transitive. Sorry. I think I wasn't clear enough in that last comment. 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. No, it was my bad. I didn't realize you post the comment already until I post mine and refresh the page. 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. Just to clarify: If A has B in its dependencies and B has C in its exports then would we want C to be on A's classpath? Or does A have to have B in its exports? I guess the latter, otherwise anything depending on A won't get C on its classpath? 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. I think it ought to work like this: A has C on its compile classpath. Given an X depending on A, X won't have C on its compile classpath because A doesn't export B or C. If A exports B, then C will be on X's classpath.
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. @benjyw A target's "exports" only has effect on dependents of the target, not on target itself. Nick's description is the exact behavior I implemented in this patch. |
||
yield exp | ||
yield export | ||
|
||
|
||
def strict_dependencies(target, dep_context): | ||
"""Compute the 'strict' compile target dependencies for this target. | ||
|
||
Results the declared dependencies of a target after alias expansion, with the addition | ||
of compiler plugins and their transitive deps, since compiletime is actually runtime for them. | ||
""" | ||
for declared in _resolve_aliases(target): | ||
for declared in _resolve_strict_dependencies(target): | ||
if isinstance(declared, dep_context.compiler_plugin_types): | ||
for r in declared.closure(bfs=True, **dep_context.target_closure_kwargs): | ||
yield r | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
// Copyright 2017 Pants project contributors (see CONTRIBUTORS.md). | ||
// Licensed under the Apache License, Version 2.0 (see LICENSE). | ||
|
||
package org.pantsbuild.testproject.exports; | ||
|
||
public class A { | ||
public void foo_a() { | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
// Copyright 2017 Pants project contributors (see CONTRIBUTORS.md). | ||
// Licensed under the Apache License, Version 2.0 (see LICENSE). | ||
|
||
package org.pantsbuild.testproject.exports; | ||
|
||
public class B extends A { | ||
public void foo_b() { | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
java_library( | ||
name='A', | ||
sources = ['A.java'], | ||
) | ||
|
||
java_library( | ||
name='B', | ||
sources = ['B.java'], | ||
dependencies=[':A'], | ||
exports=[':A'], | ||
strict_deps=True, | ||
) | ||
|
||
java_library( | ||
name='C', | ||
sources = ['C.java'], | ||
dependencies=[':B'], | ||
exports=[':B'], | ||
strict_deps=True, | ||
) | ||
|
||
java_library( | ||
name='D', | ||
sources = ['D.java'], | ||
dependencies=[':C'], | ||
strict_deps=True, | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
// Copyright 2017 Pants project contributors (see CONTRIBUTORS.md). | ||
// Licensed under the Apache License, Version 2.0 (see LICENSE). | ||
|
||
package org.pantsbuild.testproject.exports; | ||
|
||
public class C extends B { | ||
public void foo_c() { | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
// Copyright 2017 Pants project contributors (see CONTRIBUTORS.md). | ||
// Licensed under the Apache License, Version 2.0 (see LICENSE). | ||
|
||
package org.pantsbuild.testproject.exports; | ||
|
||
public class D { | ||
public void foo_d() { | ||
C c = new C(); | ||
c.foo_c(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
// Copyright 2017 Pants project contributors (see CONTRIBUTORS.md). | ||
// Licensed under the Apache License, Version 2.0 (see LICENSE). | ||
|
||
package org.pantsbuild.testproject.exports; | ||
|
||
class A { | ||
def foo_a() { | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
// Copyright 2017 Pants project contributors (see CONTRIBUTORS.md). | ||
// Licensed under the Apache License, Version 2.0 (see LICENSE). | ||
|
||
package org.pantsbuild.testproject.exports; | ||
|
||
class B extends A { | ||
def foo_b() { | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
scala_library( | ||
name='A', | ||
sources = ['A.scala'], | ||
) | ||
|
||
scala_library( | ||
name='B', | ||
sources = ['B.scala'], | ||
dependencies=[':A'], | ||
exports=[':A'], | ||
strict_deps=True, | ||
) | ||
|
||
scala_library( | ||
name='C', | ||
sources = ['C.scala'], | ||
dependencies=[':B'], | ||
exports=[':B'], | ||
strict_deps=True, | ||
) | ||
|
||
scala_library( | ||
name='D', | ||
sources = ['D.scala'], | ||
dependencies=[':C'], | ||
strict_deps=True, | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
// Copyright 2017 Pants project contributors (see CONTRIBUTORS.md). | ||
// Licensed under the Apache License, Version 2.0 (see LICENSE). | ||
|
||
package org.pantsbuild.testproject.exports; | ||
|
||
class C extends B { | ||
def foo_c() { | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
// Copyright 2017 Pants project contributors (see CONTRIBUTORS.md). | ||
// Licensed under the Apache License, Version 2.0 (see LICENSE). | ||
|
||
package org.pantsbuild.testproject.exports; | ||
|
||
class D { | ||
def foo_d() { | ||
val c = new C(); | ||
c.foo_c(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
// Copyright 2017 Pants project contributors (see CONTRIBUTORS.md). | ||
// Licensed under the Apache License, Version 2.0 (see LICENSE). | ||
|
||
package org.pantsbuild.testproject.non_exports; | ||
|
||
class A { | ||
def foo_a() { | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
// Copyright 2017 Pants project contributors (see CONTRIBUTORS.md). | ||
// Licensed under the Apache License, Version 2.0 (see LICENSE). | ||
|
||
package org.pantsbuild.testproject.non_exports; | ||
|
||
class B extends A { | ||
def foo_b() { | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
scala_library( | ||
name='A', | ||
sources = ['A.scala'], | ||
) | ||
|
||
scala_library( | ||
name='B', | ||
sources = ['B.scala'], | ||
dependencies=[':A'], | ||
strict_deps=True, | ||
) | ||
|
||
scala_library( | ||
name='C', | ||
sources = ['C.scala'], | ||
dependencies=[':B'], | ||
strict_deps=True, | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
// Copyright 2017 Pants project contributors (see CONTRIBUTORS.md). | ||
// Licensed under the Apache License, Version 2.0 (see LICENSE). | ||
|
||
package org.pantsbuild.testproject.non_exports; | ||
|
||
class C { | ||
def foo_c() { | ||
val b = new B(); | ||
b.foo_b(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
# coding=utf-8 | ||
# Copyright 2017 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 shutil | ||
|
||
from pants.base.build_environment import get_buildroot | ||
from pants_test.pants_run_integration_test import PantsRunIntegrationTest | ||
|
||
|
||
class DepExportsIntegrationTest(PantsRunIntegrationTest): | ||
|
||
SRC_PREFIX = 'testprojects/tests' | ||
SRC_TYPES = ['java', 'scala'] | ||
SRC_PACKAGE = 'org/pantsbuild/testproject/exports' | ||
|
||
@classmethod | ||
def hermetic(cls): | ||
return True | ||
|
||
def test_compilation(self): | ||
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. Might be worth adding a test case demonstrating that these do indeed fail to compile without the 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. added a case where exports is needed but not added. Thanks for the suggestion! |
||
for lang in self.SRC_TYPES: | ||
path = os.path.join(self.SRC_PREFIX, lang, self.SRC_PACKAGE) | ||
pants_run = self.run_pants(['list', '{}::'.format(path)]) | ||
self.assert_success(pants_run) | ||
target_list = pants_run.stdout_data.strip().split('\n') | ||
for target in target_list: | ||
pants_run = self.run_pants(['compile', '--compile-scalafmt-skip', target]) | ||
self.assert_success(pants_run) | ||
|
||
def modify_exports_and_compile(self, target, modify_file): | ||
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. Rather than cloning the sources, you might consider just defining a target that is expected to fail to compile: just need to add it to the list of testprojects targets that are expected to fail here. 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. I cloned here because I want to modify the source file to test cache invalidation. I don't want to corrupt the repo, thus I copied it to a temp dir. But I will add a failure case. Benjy also mentioned it. Are you saying I don't have to run the failure test here but just add to the list you linked? 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. What I was saying is that if you want to create a But if you're trying to test invalidation, you do need to create the clone, so carry on. 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. I added a fail target, and added it to the list you linked. |
||
with self.temporary_sourcedir() as tmp_src: | ||
src_dir = os.path.relpath(os.path.join(tmp_src, os.path.basename(self.SRC_PACKAGE)), get_buildroot()) | ||
target_dir, target_name = target.rsplit(':', 1) | ||
shutil.copytree(target_dir, src_dir) | ||
with self.temporary_workdir() as workdir: | ||
cmd = ['compile', '--compile-scalafmt-skip', '{}:{}'.format(src_dir, target_name)] | ||
pants_run = self.run_pants_with_workdir(command=cmd, workdir=workdir) | ||
self.assert_success(pants_run) | ||
|
||
with open(os.path.join(src_dir, modify_file), 'ab') as fh: | ||
fh.write('\n') | ||
|
||
pants_run = self.run_pants_with_workdir(command=cmd, workdir=workdir) | ||
self.assert_success(pants_run) | ||
self.assertTrue('{}:{}'.format(src_dir, target_name) in pants_run.stdout_data) | ||
|
||
def test_invalidation(self): | ||
for lang in self.SRC_TYPES: | ||
path = os.path.join(self.SRC_PREFIX, lang, self.SRC_PACKAGE) | ||
target = '{}:D'.format(path) | ||
self.modify_exports_and_compile(target, 'A.{}'.format(lang)) | ||
self.modify_exports_and_compile(target, 'B.{}'.format(lang)) | ||
|
||
def test_non_exports(self): | ||
pants_run = self.run_pants(['compile', '--compile-scalafmt-skip', | ||
'testprojects/tests/scala/org/pantsbuild/testproject/non_exports:C']) | ||
self.assert_failure(pants_run) | ||
self.assertIn('FAILURE: Compilation failure: Failed jobs: ' | ||
'compile(testprojects/tests/scala/org/pantsbuild/testproject/non_exports:C)', | ||
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.
I wonder if this could even go on
Target
itself? There's nothing intrinsically JVM-y about the concept, that I can see. The same is true forstrict_deps
itself, of course, but I wouldn't expect you to move that in this change.Or is there some reason why it makes more sense for this to be only on JVM targets?
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.
The drive for "exports" literal was a common issue in both javac and scalac. So far we have only seen the issue with java and scala targets. Also bazel only has this under java_library rule (not saying we should do whatever bazel does, but I feel limiting the usage of exports is reasonable).
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.
Fair enough. It wouldn't be hard to expand later.