Replace failOnVersionConflict() with custom requireUpperBoundDeps

failOnVersionConflict has never been good for us. It is equivalent to
Maven dependencyConvergence which we discourage our users to use because
it is too tempermental and _creates_ version skew issues over time.
However, we had no real alternative for determining if our deps would be
misinterpeted by Maven.

failOnVersionConflict has been a constant drain and makes it really hard
to do seemingly-trivial upgrades. As evidenced by protobuf/build.gradle
in this change, it also caused _us_ to introduce a version downgrade.

This introduces our own custom requireUpperBoundDeps implementation so
that we can get back to simple dependency upgrades _and_ increase our
confidence in a consistent dependency tree.
This commit is contained in:
Eric Anderson 2021-06-11 14:01:18 -07:00 committed by GitHub
parent aa18b2c228
commit 5642e01243
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 114 additions and 157 deletions

View File

@ -21,10 +21,10 @@ dependencies {
project(':grpc-protobuf'), project(':grpc-protobuf'),
project(':grpc-stub'), project(':grpc-stub'),
libraries.protobuf, libraries.protobuf,
libraries.conscrypt libraries.conscrypt,
libraries.guava,
libraries.google_auth_oauth2_http
def nettyDependency = implementation project(':grpc-netty') def nettyDependency = implementation project(':grpc-netty')
googleOauth2Dependency 'implementation'
guavaDependency 'implementation'
compileOnly libraries.javax_annotation compileOnly libraries.javax_annotation
shadow configurations.implementation.getDependencies().minus(nettyDependency) shadow configurations.implementation.getDependencies().minus(nettyDependency)

View File

@ -63,12 +63,12 @@ dependencies {
project(':grpc-stub'), project(':grpc-stub'),
project(':grpc-testing'), project(':grpc-testing'),
libraries.junit, libraries.junit,
libraries.truth libraries.truth,
libraries.opencensus_contrib_grpc_metrics
implementation (libraries.google_auth_oauth2_http) { implementation (libraries.google_auth_oauth2_http) {
exclude group: 'org.apache.httpcomponents' exclude group: 'org.apache.httpcomponents'
} }
censusGrpcMetricDependency 'implementation'
compileOnly libraries.javax_annotation compileOnly libraries.javax_annotation

View File

@ -31,7 +31,7 @@ repositories {
dependencies { dependencies {
api project(':grpc-core') api project(':grpc-core')
guavaDependency 'implementation' implementation libraries.guava
testImplementation project('::grpc-okhttp') testImplementation project('::grpc-okhttp')
testImplementation libraries.androidx_test testImplementation libraries.androidx_test
testImplementation libraries.junit testImplementation libraries.junit

View File

@ -13,7 +13,8 @@ evaluationDependsOn(project(':grpc-context').path)
dependencies { dependencies {
api project(':grpc-context'), api project(':grpc-context'),
libraries.jsr305 libraries.jsr305
guavaDependency 'implementation' implementation libraries.guava,
libraries.errorprone
testImplementation project(':grpc-context').sourceSets.test.output, testImplementation project(':grpc-context').sourceSets.test.output,
project(':grpc-testing'), project(':grpc-testing'),

View File

@ -10,9 +10,9 @@ description = "gRPC: Auth"
dependencies { dependencies {
api project(':grpc-api'), api project(':grpc-api'),
libraries.google_auth_credentials libraries.google_auth_credentials
guavaDependency 'implementation' implementation libraries.guava
testImplementation project(':grpc-testing') testImplementation project(':grpc-testing'),
googleOauth2Dependency 'testImplementation' libraries.google_auth_oauth2_http
signature "org.codehaus.mojo.signature:java17:1.0@signature" signature "org.codehaus.mojo.signature:java17:1.0@signature"
signature "net.sf.androidscents.signature:android-api-level-14:4.0_r4@signature" signature "net.sf.androidscents.signature:android-api-level-14:4.0_r4@signature"
} }

View File

@ -47,9 +47,9 @@ repositories {
dependencies { dependencies {
api project(':grpc-core') api project(':grpc-core')
guavaDependency 'implementation'
implementation libraries.androidx_annotation implementation libraries.androidx_annotation
implementation libraries.androidx_core implementation libraries.androidx_core
implementation libraries.guava
testImplementation libraries.androidx_core testImplementation libraries.androidx_core
testImplementation libraries.androidx_test testImplementation libraries.androidx_test
testImplementation libraries.junit testImplementation libraries.junit

View File

@ -205,68 +205,6 @@ subprojects {
jetty_alpn_agent: 'org.mortbay.jetty.alpn:jetty-alpn-agent:2.0.10' jetty_alpn_agent: 'org.mortbay.jetty.alpn:jetty-alpn-agent:2.0.10'
] ]
// A util function to config guava dependency with transitive dependencies
// properly resolved for the failOnVersionConflict strategy.
guavaDependency = { configurationName ->
dependencies."$configurationName"(libraries.guava) {
exclude group: 'com.google.code.findbugs', module: 'jsr305'
exclude group: 'com.google.errorprone', module: 'error_prone_annotations'
exclude group: 'org.codehaus.mojo', module: 'animal-sniffer-annotations'
}
dependencies."$configurationName" libraries.errorprone
dependencies.runtimeOnly libraries.animalsniffer_annotations
dependencies.runtimeOnly libraries.jsr305
}
// A util function to config opencensus_api dependency with transitive
// dependencies properly resolved for the failOnVersionConflict strategy.
censusApiDependency = { configurationName ->
dependencies."$configurationName"(libraries.opencensus_api) {
exclude group: 'com.google.code.findbugs', module: 'jsr305'
exclude group: 'com.google.guava', module: 'guava'
// we'll always be more up-to-date
exclude group: 'io.grpc', module: 'grpc-context'
}
dependencies.runtimeOnly project(':grpc-context')
dependencies.runtimeOnly libraries.jsr305
guavaDependency 'runtimeOnly'
}
// A util function to config opencensus_contrib_grpc_metrics dependency
// with transitive dependencies properly resolved for the failOnVersionConflict
// strategy.
censusGrpcMetricDependency = { configurationName ->
dependencies."$configurationName"(libraries.opencensus_contrib_grpc_metrics) {
exclude group: 'com.google.code.findbugs', module: 'jsr305'
exclude group: 'com.google.guava', module: 'guava'
// we'll always be more up-to-date
exclude group: 'io.grpc', module: 'grpc-context'
}
dependencies.runtimeOnly project(':grpc-context')
dependencies.runtimeOnly libraries.jsr305
guavaDependency 'runtimeOnly'
}
googleOauth2Dependency = { configurationName ->
dependencies."$configurationName"(libraries.google_auth_oauth2_http) {
exclude group: 'com.google.guava', module: 'guava'
exclude group: 'io.grpc', module: 'grpc-context'
exclude group: 'io.opencensus', module: 'opencensus-api'
}
dependencies.runtimeOnly project(':grpc-context')
censusApiDependency 'runtimeOnly'
guavaDependency 'runtimeOnly'
}
// A util function to config perfmark dependency with transitive
// dependencies properly resolved for the failOnVersionConflict strategy.
perfmarkDependency = { configurationName ->
dependencies."$configurationName"(libraries.perfmark) {
exclude group: 'com.google.errorprone', module: 'error_prone_annotations'
}
dependencies.runtimeOnly libraries.errorprone
}
appendToProperty = { Property<String> property, String value, String separator -> appendToProperty = { Property<String> property, String value, String separator ->
if (property.present) { if (property.present) {
property.set(property.get() + separator + value) property.set(property.get() + separator + value)
@ -276,25 +214,6 @@ subprojects {
} }
} }
configurations {
// Detect Maven Enforcer's dependencyConvergence failures. We only
// care for artifacts used as libraries by others.
if (isAndroid && !(project.name in ['grpc-android-interop-testing'])) {
releaseRuntimeClasspath {
resolutionStrategy.failOnVersionConflict()
}
}
if (!isAndroid && !(project.name in [
'grpc-benchmarks',
'grpc-interop-testing',
'grpc-gae-interop-testing-jdk8',
])) {
runtimeClasspath {
resolutionStrategy.failOnVersionConflict()
}
}
}
// Disable JavaDoc doclint on Java 8. It's annoying. // Disable JavaDoc doclint on Java 8. It's annoying.
if (JavaVersion.current().isJava8Compatible()) { if (JavaVersion.current().isJava8Compatible()) {
allprojects { allprojects {
@ -406,6 +325,19 @@ subprojects {
} }
} }
plugins.withId("java-library") {
// Detect Maven Enforcer's dependencyConvergence failures. We only care
// for artifacts used as libraries by others with Maven.
tasks.register('checkUpperBoundDeps') {
doLast {
requireUpperBoundDepsMatch(configurations.runtimeClasspath, project)
}
}
tasks.named('compileJava') {
dependsOn checkUpperBoundDeps
}
}
plugins.withId("me.champeau.gradle.jmh") { plugins.withId("me.champeau.gradle.jmh") {
dependencies { dependencies {
jmh 'org.openjdk.jmh:jmh-core:1.19', jmh 'org.openjdk.jmh:jmh-core:1.19',
@ -582,3 +514,56 @@ subprojects {
} }
} }
} }
class DepAndParents {
DependencyResult dep
List<String> parents
}
/**
* Make sure that Maven would select the same versions as Gradle selected.
* This is essentially the same as if we used Maven Enforcer's
* requireUpperBoundDeps for our artifacts.
*/
def requireUpperBoundDepsMatch(Configuration conf, Project project) {
// artifact name => version
Map<String,String> golden = conf.resolvedConfiguration.resolvedArtifacts.collectEntries {
ResolvedArtifact it ->
ModuleVersionIdentifier id = it.moduleVersion.id
[id.group + ":" + id.name, id.version]
}
// Breadth-first search like Maven for dependency resolution
Queue<DepAndParents> queue = new ArrayDeque<>()
conf.incoming.resolutionResult.root.dependencies.each {
queue.add(new DepAndParents(dep: it, parents: [project.displayName]))
}
Set<String> found = new HashSet<>()
while (!queue.isEmpty()) {
DepAndParents depAndParents = queue.remove()
ResolvedDependencyResult result = (ResolvedDependencyResult) depAndParents.dep
ModuleVersionIdentifier id = result.selected.moduleVersion
String artifact = id.group + ":" + id.name
if (found.contains(artifact))
continue
found.add(artifact)
String version
if (result.requested instanceof ProjectComponentSelector) {
ProjectComponentSelector selector = (ProjectComponentSelector) result.requested
version = project.findProject(selector.projectPath).version
} else {
version = ((ModuleComponentSelector) result.requested).version
}
String goldenVersion = golden[artifact]
if (goldenVersion != version && "[$goldenVersion]" != version) {
throw new RuntimeException(
"Maven version skew: $artifact ($version != $goldenVersion) "
+ "Bad version dependency path: " + depAndParents.parents
+ " Run './gradlew $project.path:dependencies --configuration $conf.name' "
+ "to diagnose")
}
result.selected.dependencies.each {
queue.add(new DepAndParents(
dep: it, parents: depAndParents.parents + [artifact + ":" + version]))
}
}
}

View File

@ -46,8 +46,6 @@ export LDFLAGS=-L/tmp/protobuf/lib
export CXXFLAGS="-I/tmp/protobuf/include" export CXXFLAGS="-I/tmp/protobuf/include"
./gradlew clean $GRADLE_FLAGS ./gradlew clean $GRADLE_FLAGS
# Ensure dependency convergence
./gradlew :grpc-all:dependencies $GRADLE_FLAGS
if [[ -z "${SKIP_TESTS:-}" ]]; then if [[ -z "${SKIP_TESTS:-}" ]]; then
# Ensure all *.proto changes include *.java generated code # Ensure all *.proto changes include *.java generated code

View File

@ -9,9 +9,9 @@ evaluationDependsOn(project(':grpc-api').path)
dependencies { dependencies {
api project(':grpc-api') api project(':grpc-api')
guavaDependency 'implementation' implementation libraries.guava,
censusApiDependency 'implementation' libraries.opencensus_api,
censusGrpcMetricDependency 'implementation' libraries.opencensus_contrib_grpc_metrics
testImplementation project(':grpc-api').sourceSets.test.output, testImplementation project(':grpc-api').sourceSets.test.output,
project(':grpc-context').sourceSets.test.output, project(':grpc-context').sourceSets.test.output,

View File

@ -27,9 +27,9 @@ dependencies {
implementation libraries.gson, implementation libraries.gson,
libraries.android_annotations, libraries.android_annotations,
libraries.animalsniffer_annotations, libraries.animalsniffer_annotations,
libraries.errorprone libraries.errorprone,
guavaDependency 'implementation' libraries.guava,
perfmarkDependency 'implementation' libraries.perfmark
testImplementation project(':grpc-context').sourceSets.test.output, testImplementation project(':grpc-context').sourceSets.test.output,
project(':grpc-api').sourceSets.test.output, project(':grpc-api').sourceSets.test.output,
project(':grpc-testing'), project(':grpc-testing'),

View File

@ -35,7 +35,7 @@ android {
dependencies { dependencies {
api project(':grpc-core'), api project(':grpc-core'),
libraries.cronet_api libraries.cronet_api
guavaDependency 'implementation' implementation libraries.guava
testImplementation project(':grpc-testing') testImplementation project(':grpc-testing')
testImplementation libraries.cronet_embedded testImplementation libraries.cronet_embedded

View File

@ -49,11 +49,6 @@
<groupId>io.grpc</groupId> <groupId>io.grpc</groupId>
<artifactId>grpc-auth</artifactId> <artifactId>grpc-auth</artifactId>
</dependency> </dependency>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java-util</artifactId>
<version>${protobuf.version}</version>
</dependency>
<dependency> <dependency>
<groupId>org.apache.tomcat</groupId> <groupId>org.apache.tomcat</groupId>
<artifactId>annotations-api</artifactId> <artifactId>annotations-api</artifactId>

View File

@ -14,13 +14,9 @@ dependencies {
implementation project(':grpc-core'), implementation project(':grpc-core'),
project(':grpc-protobuf'), project(':grpc-protobuf'),
project(':grpc-stub'), project(':grpc-stub'),
libraries.protobuf libraries.protobuf,
implementation (libraries.protobuf_util) { libraries.protobuf_util,
// prefer our own versions instead of protobuf-util's dependency libraries.guava
exclude group: 'com.google.guava', module: 'guava'
exclude group: 'com.google.errorprone', module: 'error_prone_annotations'
}
guavaDependency 'implementation'
runtimeOnly libraries.errorprone runtimeOnly libraries.errorprone
compileOnly libraries.javax_annotation compileOnly libraries.javax_annotation
testImplementation libraries.truth, testImplementation libraries.truth,

View File

@ -28,9 +28,9 @@ dependencies {
project(':grpc-testing'), project(':grpc-testing'),
project(path: ':grpc-xds', configuration: 'shadow'), project(path: ':grpc-xds', configuration: 'shadow'),
libraries.junit, libraries.junit,
libraries.truth libraries.truth,
censusGrpcMetricDependency 'implementation' libraries.opencensus_contrib_grpc_metrics,
googleOauth2Dependency 'implementation' libraries.google_auth_oauth2_http
compileOnly libraries.javax_annotation compileOnly libraries.javax_annotation
// TODO(sergiitk): replace with com.google.cloud:google-cloud-logging // TODO(sergiitk): replace with com.google.cloud:google-cloud-logging
// Used instead of google-cloud-logging because it's failing // Used instead of google-cloud-logging because it's failing

View File

@ -18,9 +18,10 @@ evaluationDependsOn(project(':grpc-core').path)
dependencies { dependencies {
api project(':grpc-core'), api project(':grpc-core'),
libraries.netty libraries.netty
implementation libraries.netty_proxy_handler implementation libraries.netty_proxy_handler,
guavaDependency 'implementation' libraries.guava,
perfmarkDependency 'implementation' libraries.errorprone,
libraries.perfmark
// Tests depend on base class defined by core module. // Tests depend on base class defined by core module.
testImplementation project(':grpc-core').sourceSets.test.output, testImplementation project(':grpc-core').sourceSets.test.output,

View File

@ -11,14 +11,11 @@ description = "gRPC: OkHttp"
evaluationDependsOn(project(':grpc-core').path) evaluationDependsOn(project(':grpc-core').path)
dependencies { dependencies {
api project(':grpc-core') api project(':grpc-core'),
api (libraries.okhttp) { libraries.okhttp
// prefer our own versions instead of okhttp's dependency implementation libraries.okio,
exclude group: 'com.squareup.okio', module: 'okio' libraries.guava,
} libraries.perfmark
implementation libraries.okio
guavaDependency 'implementation'
perfmarkDependency 'implementation'
// Tests depend on base class defined by core module. // Tests depend on base class defined by core module.
testImplementation project(':grpc-core').sourceSets.test.output, testImplementation project(':grpc-core').sourceSets.test.output,
project(':grpc-api').sourceSets.test.output, project(':grpc-api').sourceSets.test.output,

View File

@ -12,8 +12,8 @@ description = 'gRPC: Protobuf Lite'
dependencies { dependencies {
api project(':grpc-api'), api project(':grpc-api'),
libraries.protobuf_lite libraries.protobuf_lite
implementation libraries.jsr305 implementation libraries.jsr305,
guavaDependency 'implementation' libraries.guava
testImplementation project(':grpc-core') testImplementation project(':grpc-core')

View File

@ -13,14 +13,12 @@ dependencies {
api project(':grpc-api'), api project(':grpc-api'),
libraries.jsr305, libraries.jsr305,
libraries.protobuf libraries.protobuf
guavaDependency 'implementation' implementation libraries.guava
api (libraries.google_api_protos) { api (libraries.google_api_protos) {
// 'com.google.api:api-common' transitively depends on auto-value, which breaks our // 'com.google.api:api-common' transitively depends on auto-value, which breaks our
// annotations. // annotations.
exclude group: 'com.google.api', module: 'api-common' exclude group: 'com.google.api', module: 'api-common'
// Prefer our more up-to-date protobuf over 3.2.0
exclude group: 'com.google.protobuf', module: 'protobuf-java'
} }
api (project(':grpc-protobuf-lite')) { api (project(':grpc-protobuf-lite')) {

View File

@ -13,8 +13,8 @@ evaluationDependsOn(project(':grpc-core').path)
dependencies { dependencies {
implementation project(':grpc-core'), implementation project(':grpc-core'),
project(':grpc-protobuf'), project(':grpc-protobuf'),
project(':grpc-stub') project(':grpc-stub'),
guavaDependency 'implementation' libraries.guava
compileOnly libraries.javax_annotation compileOnly libraries.javax_annotation
testImplementation libraries.truth, testImplementation libraries.truth,
project(':grpc-grpclb'), project(':grpc-grpclb'),

View File

@ -22,12 +22,8 @@ dependencies {
api project(':grpc-protobuf'), api project(':grpc-protobuf'),
project(':grpc-stub'), project(':grpc-stub'),
project(':grpc-core') project(':grpc-core')
implementation (libraries.protobuf_util) { implementation libraries.protobuf_util,
// prefer our own versions instead of protobuf-util's dependency libraries.guava
exclude group: 'com.google.guava', module: 'guava'
exclude group: 'com.google.errorprone', module: 'error_prone_annotations'
}
guavaDependency 'implementation'
runtimeOnly libraries.errorprone runtimeOnly libraries.errorprone
compileOnly libraries.javax_annotation compileOnly libraries.javax_annotation

View File

@ -8,8 +8,8 @@ plugins {
description = "gRPC: Stub" description = "gRPC: Stub"
dependencies { dependencies {
api project(':grpc-api') api project(':grpc-api'),
guavaDependency 'api' libraries.guava
testImplementation libraries.truth, testImplementation libraries.truth,
project(':grpc-testing') project(':grpc-testing')
signature "org.codehaus.mojo.signature:java17:1.0@signature" signature "org.codehaus.mojo.signature:java17:1.0@signature"

View File

@ -13,8 +13,8 @@ dependencies {
api project(':grpc-core'), api project(':grpc-core'),
project(':grpc-stub'), project(':grpc-stub'),
libraries.junit libraries.junit
implementation libraries.opencensus_api
censusApiDependency 'implementation' runtimeOnly project(":grpc-context") // Pull in newer version than census-api
testImplementation (libraries.mockito) { testImplementation (libraries.mockito) {
// prefer our own versions instead of mockito's dependency // prefer our own versions instead of mockito's dependency

View File

@ -36,21 +36,11 @@ dependencies {
libraries.gson, libraries.gson,
libraries.re2j, libraries.re2j,
libraries.bouncycastle, libraries.bouncycastle,
libraries.autovalue_annotation libraries.autovalue_annotation,
libraries.opencensus_proto,
libraries.protobuf_util
def nettyDependency = implementation project(':grpc-netty') def nettyDependency = implementation project(':grpc-netty')
implementation (libraries.opencensus_proto) {
// prefer our own versions instead of opencensus_proto's
exclude group: 'com.google.protobuf', module: 'protobuf-java'
exclude group: 'io.grpc', module: 'grpc-protobuf'
exclude group: 'io.grpc', module: 'grpc-stub'
}
implementation (libraries.protobuf_util) {
// prefer our own versions instead of protobuf-util's dependency
exclude group: 'com.google.guava', module: 'guava'
exclude group: 'com.google.errorprone', module: 'error_prone_annotations'
}
testImplementation project(':grpc-core').sourceSets.test.output testImplementation project(':grpc-core').sourceSets.test.output
annotationProcessor libraries.autovalue annotationProcessor libraries.autovalue