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