-
-
Notifications
You must be signed in to change notification settings - Fork 645
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
Include transitive target dependencies in export-dep-as-jar #8736
Conversation
I pretty strongly prefer this... Any thoughts? |
@illicitonion I also prefer that approach. |
I prefer that approach as well, but I'm not too familiar with the requirements metals has for this. if I have The alternative seems reasonable and somewhat easy to implement, lmk if I can help. |
The only requirement from Metals is being able to faithfully reproduce the compilation outside of Pants. The same is true if we want to experiment with the IntelliJ compiler. I hit on a case where removing |
Regardless of what approach we end up using for computing the classpath of a target, I think it's desirable to include
|
I think there's not really harm in exposing this, but it's worth being aware that strict_deps isn't an inherent concept; for instance, when we add dependency inference, we'll probably remove strict_deps as a concept. So it may be useful for now (though probably export isn't the best API to use for the programatic finding), but may not be long term... Also, at least internally at Twitter, we've found that our strict_deps implementation for scala is error-prone enough that we don't recommend that teams enable it, so displaying prompts in the UI to nudge people to use strict_deps would probably be counter-productive. |
Failing test looks unrelated
|
There is a failing test that complains about not being able to resolve a pants plugin ( |
4c38f34
to
4594aee
Compare
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 agree with @illicitonion that even if we expose the option, we would want to render the strict deps list for the target in export, rather than attempting to compute them in the caller.
@wisechengyi : Thoughts?
@@ -276,6 +276,12 @@ def iter_transitive_jars(jar_lib): | |||
|
|||
if classpath_products: | |||
info['libraries'] = [self._jar_id(lib) for lib in target_libraries] | |||
|
|||
strict_deps = False |
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.
This will not correctly default to the underlying language's default value. Should use
pants/src/python/pants/backend/jvm/subsystems/dependency_context.py
Lines 48 to 63 in fff8dff
def defaulted_property(self, target, option_name): | |
"""Computes a language property setting for the given JvmTarget. | |
:param selector A function that takes a target or platform and returns the boolean value of the | |
property for that target or platform, or None if that target or platform does | |
not directly define the property. | |
If the target does not override the language property, returns true iff the property | |
is true for any of the matched languages for the target. | |
""" | |
if target.has_sources('.java'): | |
matching_subsystem = Java.global_instance() | |
elif target.has_sources('.scala'): | |
matching_subsystem = ScalaPlatform.global_instance() | |
else: | |
return getattr(target, option_name) |
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 tried using defaulted_property
with this diff
diff --git a/src/python/pants/backend/project_info/tasks/export.py b/src/python/pants/backend/project_info/tasks/export.py
index d2390d3..7fd9896 100644
--- a/src/python/pants/backend/project_info/tasks/export.py
+++ b/src/python/pants/backend/project_info/tasks/export.py
@@ -7,6 +7,7 @@ from collections import defaultdict
from twitter.common.collections import OrderedSet
+from pants.backend.jvm.subsystems.dependency_context import DependencyContext
from pants.backend.jvm.subsystems.jvm_platform import JvmPlatform
from pants.backend.jvm.subsystems.resolve_subsystem import JvmResolveSubsystem
from pants.backend.jvm.subsystems.scala_platform import ScalaPlatform
@@ -60,7 +61,7 @@ class ExportTask(ResolveRequirementsTaskBase, IvyTaskMixin, CoursierMixin):
@classmethod
def subsystem_dependencies(cls):
return super().subsystem_dependencies() + (
- DistributionLocator, JvmPlatform, PythonInterpreterCache, ScalaPlatform
+ DependencyContext, DistributionLocator, JvmPlatform, PythonInterpreterCache, ScalaPlatform
)
@staticmethod
@@ -277,7 +278,7 @@ class ExportTask(ResolveRequirementsTaskBase, IvyTaskMixin, CoursierMixin):
if classpath_products:
info['libraries'] = [self._jar_id(lib) for lib in target_libraries]
- strict_deps = False
+ strict_deps = DependencyContext.global_instance().defaulted_property(current_target, 'strict_deps')
if hasattr(current_target, 'strict_deps'):
strict_deps = True == getattr(current_target, 'strict_deps')
info['strict_deps'] = strict_deps
It crashes when I run the tests because the method calls getattr(target, 'strict_deps')
def defaulted_property(self, target, option_name):
"""Computes a language property setting for the given JvmTarget.
:param selector A function that takes a target or platform and returns the boolean value of the
property for that target or platform, or None if that target or platform does
not directly define the property.
If the target does not override the language property, returns true iff the property
is true for any of the matched languages for the target.
"""
if target.has_sources('.java'):
matching_subsystem = Java.global_instance()
elif target.has_sources('.scala'):
matching_subsystem = ScalaPlatform.global_instance()
else:
> return getattr(target, option_name)
E AttributeError: 'Target' object has no attribute 'strict_deps'
What is the correct usage of this method?
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.
It assumes that it is being called on a JvmTarget, which always has that property.
One option would be to add an optional parameter to def defaulted_property(self, ..., default=None):
, which would then be passed to getattr(target, option_name, default)
.
Are you suggesting given
Mark If so, it feels like it's misrepresenting how the target is defined, unless it's super hard in metals to figure out the relations somehow. Stepping back a bit, I feel this is likely optimizing a bit early, if we are able to deliver fast iteration time to users. I'm sure they won't have problem turning off strict deps, unless that's a correctness issue somehow. |
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.
@wisechengyi strict_deps
affects correctness. Removing strict_deps=True
from a BUILD file can introduce a compile error. Consider the following minimized example
// A.scala
package a
class Foo
// B.scala (depends on A.scala)
package b
class Foo
// C.scala (depends on B.scala)
import a._
import b._
new Foo()
The new Foo()
expression resolves to b.Foo
with strict_deps=True
and it fails with a compile error under strict_deps=False
due to an ambiguous reference.
@@ -276,6 +276,12 @@ def iter_transitive_jars(jar_lib): | |||
|
|||
if classpath_products: | |||
info['libraries'] = [self._jar_id(lib) for lib in target_libraries] | |||
|
|||
strict_deps = False |
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 tried using defaulted_property
with this diff
diff --git a/src/python/pants/backend/project_info/tasks/export.py b/src/python/pants/backend/project_info/tasks/export.py
index d2390d3..7fd9896 100644
--- a/src/python/pants/backend/project_info/tasks/export.py
+++ b/src/python/pants/backend/project_info/tasks/export.py
@@ -7,6 +7,7 @@ from collections import defaultdict
from twitter.common.collections import OrderedSet
+from pants.backend.jvm.subsystems.dependency_context import DependencyContext
from pants.backend.jvm.subsystems.jvm_platform import JvmPlatform
from pants.backend.jvm.subsystems.resolve_subsystem import JvmResolveSubsystem
from pants.backend.jvm.subsystems.scala_platform import ScalaPlatform
@@ -60,7 +61,7 @@ class ExportTask(ResolveRequirementsTaskBase, IvyTaskMixin, CoursierMixin):
@classmethod
def subsystem_dependencies(cls):
return super().subsystem_dependencies() + (
- DistributionLocator, JvmPlatform, PythonInterpreterCache, ScalaPlatform
+ DependencyContext, DistributionLocator, JvmPlatform, PythonInterpreterCache, ScalaPlatform
)
@staticmethod
@@ -277,7 +278,7 @@ class ExportTask(ResolveRequirementsTaskBase, IvyTaskMixin, CoursierMixin):
if classpath_products:
info['libraries'] = [self._jar_id(lib) for lib in target_libraries]
- strict_deps = False
+ strict_deps = DependencyContext.global_instance().defaulted_property(current_target, 'strict_deps')
if hasattr(current_target, 'strict_deps'):
strict_deps = True == getattr(current_target, 'strict_deps')
info['strict_deps'] = strict_deps
It crashes when I run the tests because the method calls getattr(target, 'strict_deps')
def defaulted_property(self, target, option_name):
"""Computes a language property setting for the given JvmTarget.
:param selector A function that takes a target or platform and returns the boolean value of the
property for that target or platform, or None if that target or platform does
not directly define the property.
If the target does not override the language property, returns true iff the property
is true for any of the matched languages for the target.
"""
if target.has_sources('.java'):
matching_subsystem = Java.global_instance()
elif target.has_sources('.scala'):
matching_subsystem = ScalaPlatform.global_instance()
else:
> return getattr(target, option_name)
E AttributeError: 'Target' object has no attribute 'strict_deps'
What is the correct usage of this method?
One possible backwards compatible solution could be to introduce a new key |
No. I'm suggesting that I agree with #8736 (comment) |
Oh I see. E.g.
Now, if a -(strict)-> b -> c, what should the output look like? Also to be clear there are unfortunately two Edit: in case the inner |
Thanks for the explanation! That makes sense. However in this case would it be an easier fix if explicit import was used instead of wildcard? Or is there anything else I didn't consider that could make this difficult? Edit: also is it sort of anti-pattern to rely on strict_deps to enforce which |
f49cded
to
22b83a0
Compare
Together with @blorente, we updated this PR to
Please let us know what you think |
Previously, `export-dep-as-jar` did not include the full list of transitive target dependencies. This meant that client tools like the IntelliJ compiler and Bloop export were left to traverse the build graph in order to construct the full classpath of a target. Now, the output of `export-dep-as-jar` includes a new field `transitive_targets` field that is similar to `targets` except that it includes transitive target dependencies respecting `strict_deps`. For example, assuming `c -> b -> a` the value of `transitive_targets` is the following: ```diff ++ new export-dep-as-jar output { "c:c": { "targets": ["b:b"], + "transitive_targets": ["b:b", "a:a"], } } ``` This change allows clients to faithfully reproduce the same classpath as `./pants compile` without exposing `strict_deps` into the `export-dep-as-jar` output.
for dep in transitive_targets: | ||
info['transitive_targets'].append(dep.address.spec) |
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.
-
Did a sample run with this on
dataproducts/aclservice/::
, the resulting json went from 4MB to 14MB, so would recommend putting this behind a flag. I haven't tried with a even larger project, but if json gets to 50MB, then that's likely not ideal. -
Ideally we only need to put non-target-roots into
'transitive_targets'
. Also relating to Borja's work to minimize the # of intellij modules. Do you think whether below makes sense, @blorente?
For example:
Given
A -> B -> C -> D
A -> E -> F
$ ./pants export-dep-as-jar A
{
A: {'transitive_targets', [B, C, D, E, F], is_target_root: True}
B: // dependencies here doesn't matter because it's not target root, only need to show where the jar is
C: // same
D: // same
E: // same
F: // same
}
$ ./pants export-dep-as-jar A B
{
A: { targets: [B], transitive_targets: [E,F], is_target_root: True}, // because B is also a target root
B: { transitive_targets: [C, D], is_target_root: True}
C: // dependencies here doesn't matter because it's not target root, only need to show where the jar is
D: // same
E: // same
F: // same
Although the tricky part could be
A -> B -> C -> D
./pants export-dep-as-jar A C
In this case, we might have to force B into target root (intellij module rather than a library), because "3rdparty library" (B in this case from A's perspective) in intellij cannot depend back onto C.
To be clear, feel free to only do 1), and I'd be okay to ship this to unblock the work.
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.
So... now that you say that (apologies, should have noticed earlier), I think that what I was suggesting was related to #8736 (comment) , but different. Roughly:
- Render the strict dep boolean
- When a target is
strict_deps=True
, include the calculated direct strict dependencies set in the dependencies list rather than the non-strict dependencies (alternatively define a second fieldstrict_dependencies=[...]
). Whenstrict_deps=False
, change nothing: thedependencies
list would be as it is.
This would avoid the exponential blowup (because we'd never render transitive dependencies) and would avoid the strict deps calculation (which is the actual challenging one: unlike the non-strict deps calculation). Sorry, should have noticed earlier.
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.
Not sure if I follow on 2). For example A -> B , B -> C, D
scala_library(
name = A
strict_dep = True
dependencies= [B]
)
scala_library(
name = B
strict_dep = True
dependencies= [C, D]
export=[C]
)
scala_library(
name = C
dependencies = []
)
scala_library(
name = D
dependencies = []
)
What would the output look like?
A: {
strict_dep=True,
dependencies = [B, C], // or should this be [B] only?
}
B: {..}
C: {..}
D: {..}
Hi! I'm going to give this another shot. I'm strongly leaning towards including the This field would be included in the output of each modulizable target, and would contain the (strict_deps-sensitive) flat list of source-dependencies I depend on. Therefore, as a target, my classpath would contain:
@wisechengyi : Since #8812, we expect the amount of modulizable targets to be linear on the number of target roots, so the exponential blowup of output size is not that much of a concern. @olafurpg : Is #8736 (comment) still a concern? I'd personally like to avoid printing the strict_deps field, especially since we don't recommend it internally, but we can discuss it if you think it would be useful. |
I would love to see this picked up! I'm fine either approach we use, whether that's adding a |
Re-opened this in the above PR: #9146 |
Previously,
export-dep-as-jar
did not include the full list oftransitive target dependencies. This meant that client tools like the
IntelliJ compiler and Bloop export were left to traverse the build graph
in order to construct the full classpath of a target.
Now, the output of
export-dep-as-jar
includes a new fieldtransitive_targets
field that is similar totargets
except that itincludes transitive target dependencies respecting
strict_deps
. Forexample, assuming
c -> b -> a
the value oftransitive_targets
is thefollowing:
This change allows clients to faithfully reproduce the same classpath as
./pants compile
without exposingstrict_deps
into theexport-dep-as-jar
output.