forked from pantsbuild/pants
/
dependency_context.py
114 lines (88 loc) · 4.81 KB
/
dependency_context.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# coding=utf-8
# Copyright 2018 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 hashlib
from pants.backend.jvm.subsystems.java import Java
from pants.backend.jvm.subsystems.scala_platform import ScalaPlatform
from pants.backend.jvm.targets.annotation_processor import AnnotationProcessor
from pants.backend.jvm.targets.jar_library import JarLibrary
from pants.backend.jvm.targets.javac_plugin import JavacPlugin
from pants.backend.jvm.targets.scalac_plugin import ScalacPlugin
from pants.base.fingerprint_strategy import FingerprintStrategy
from pants.build_graph.dependency_context import DependencyContext as DependencyContextBase
from pants.build_graph.resources import Resources
from pants.build_graph.target_scopes import Scopes
from pants.subsystem.subsystem import Subsystem
class SyntheticTargetNotFound(Exception):
"""Exports were resolved for a thrift target which hasn't had a synthetic target generated yet."""
class DependencyContext(Subsystem, DependencyContextBase):
"""Implements calculating `exports` and exception (compiler-plugin) aware dependencies.
This is a subsystem because in future the compiler plugin types should be injected
via subsystem or option dependencies rather than declared statically.
"""
options_scope = 'jvm-dependency-context'
types_with_closure = (AnnotationProcessor, JavacPlugin, ScalacPlugin)
target_closure_kwargs = dict(include_scopes=Scopes.JVM_COMPILE_SCOPES, respect_intransitive=True)
@classmethod
def subsystem_dependencies(cls):
return super(DependencyContext, cls).subsystem_dependencies() + (Java, ScalaPlatform)
def all_dependencies(self, target):
"""All transitive dependencies of the context's target."""
for dep in target.closure(bfs=True, **self.target_closure_kwargs):
yield dep
def create_fingerprint_strategy(self, classpath_products):
return ResolvedJarAwareFingerprintStrategy(classpath_products, self)
def defaulted_property(self, target, selector):
"""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.
"""
target_property_selected = selector(target)
if target_property_selected is not None:
return target_property_selected
prop = None
if target.has_sources('.java'):
prop = prop or selector(Java.global_instance())
if target.has_sources('.scala'):
prop = prop or selector(ScalaPlatform.global_instance())
return prop
class ResolvedJarAwareFingerprintStrategy(FingerprintStrategy):
"""Task fingerprint strategy that also includes the resolved coordinates of dependent jars."""
def __init__(self, classpath_products, dep_context):
super(ResolvedJarAwareFingerprintStrategy, self).__init__()
self._classpath_products = classpath_products
self._dep_context = dep_context
def compute_fingerprint(self, target):
if isinstance(target, Resources):
# Just do nothing, this kind of dependency shouldn't affect result's hash.
return None
hasher = hashlib.sha1()
hasher.update(target.payload.fingerprint())
if isinstance(target, JarLibrary):
# NB: Collects only the jars for the current jar_library, and hashes them to ensure that both
# the resolved coordinates, and the requested coordinates are used. This ensures that if a
# source file depends on a library with source compatible but binary incompatible signature
# changes between versions, that you won't get runtime errors due to using an artifact built
# against a binary incompatible version resolved for a previous compile.
classpath_entries = self._classpath_products.get_artifact_classpath_entries_for_targets(
[target])
for _, entry in classpath_entries:
hasher.update(str(entry.coordinate))
return hasher.hexdigest()
def direct(self, target):
return self._dep_context.defaulted_property(target, lambda x: x.strict_deps)
def dependencies(self, target):
if self.direct(target):
return target.strict_dependencies(self._dep_context)
return super(ResolvedJarAwareFingerprintStrategy, self).dependencies(target)
def __hash__(self):
# NB: FingerprintStrategy requires a useful override of eq/hash.
return hash(type(self))
def __eq__(self, other):
# NB: See __hash__.
return type(self) == type(other)