Skip to content

Commit

Permalink
Replace failOnVersionConflict() with custom requireUpperBoundDeps
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
ejona86 committed Jun 11, 2021
1 parent aa18b2c commit 5642e01
Show file tree
Hide file tree
Showing 23 changed files with 114 additions and 157 deletions.
6 changes: 3 additions & 3 deletions alts/build.gradle
Expand Up @@ -21,10 +21,10 @@ dependencies {
project(':grpc-protobuf'),
project(':grpc-stub'),
libraries.protobuf,
libraries.conscrypt
libraries.conscrypt,
libraries.guava,
libraries.google_auth_oauth2_http
def nettyDependency = implementation project(':grpc-netty')
googleOauth2Dependency 'implementation'
guavaDependency 'implementation'
compileOnly libraries.javax_annotation

shadow configurations.implementation.getDependencies().minus(nettyDependency)
Expand Down
4 changes: 2 additions & 2 deletions android-interop-testing/build.gradle
Expand Up @@ -63,12 +63,12 @@ dependencies {
project(':grpc-stub'),
project(':grpc-testing'),
libraries.junit,
libraries.truth
libraries.truth,
libraries.opencensus_contrib_grpc_metrics

implementation (libraries.google_auth_oauth2_http) {
exclude group: 'org.apache.httpcomponents'
}
censusGrpcMetricDependency 'implementation'

compileOnly libraries.javax_annotation

Expand Down
2 changes: 1 addition & 1 deletion android/build.gradle
Expand Up @@ -31,7 +31,7 @@ repositories {

dependencies {
api project(':grpc-core')
guavaDependency 'implementation'
implementation libraries.guava
testImplementation project('::grpc-okhttp')
testImplementation libraries.androidx_test
testImplementation libraries.junit
Expand Down
3 changes: 2 additions & 1 deletion api/build.gradle
Expand Up @@ -13,7 +13,8 @@ evaluationDependsOn(project(':grpc-context').path)
dependencies {
api project(':grpc-context'),
libraries.jsr305
guavaDependency 'implementation'
implementation libraries.guava,
libraries.errorprone

testImplementation project(':grpc-context').sourceSets.test.output,
project(':grpc-testing'),
Expand Down
6 changes: 3 additions & 3 deletions auth/build.gradle
Expand Up @@ -10,9 +10,9 @@ description = "gRPC: Auth"
dependencies {
api project(':grpc-api'),
libraries.google_auth_credentials
guavaDependency 'implementation'
testImplementation project(':grpc-testing')
googleOauth2Dependency 'testImplementation'
implementation libraries.guava
testImplementation project(':grpc-testing'),
libraries.google_auth_oauth2_http
signature "org.codehaus.mojo.signature:java17:1.0@signature"
signature "net.sf.androidscents.signature:android-api-level-14:4.0_r4@signature"
}
2 changes: 1 addition & 1 deletion binder/build.gradle
Expand Up @@ -47,9 +47,9 @@ repositories {
dependencies {
api project(':grpc-core')

guavaDependency 'implementation'
implementation libraries.androidx_annotation
implementation libraries.androidx_core
implementation libraries.guava
testImplementation libraries.androidx_core
testImplementation libraries.androidx_test
testImplementation libraries.junit
Expand Down
147 changes: 66 additions & 81 deletions build.gradle
Expand Up @@ -205,68 +205,6 @@ subprojects {
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 ->
if (property.present) {
property.set(property.get() + separator + value)
Expand All @@ -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.
if (JavaVersion.current().isJava8Compatible()) {
allprojects {
Expand Down Expand Up @@ -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") {
dependencies {
jmh 'org.openjdk.jmh:jmh-core:1.19',
Expand Down Expand Up @@ -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]))
}
}
}
2 changes: 0 additions & 2 deletions buildscripts/kokoro/unix.sh
Expand Up @@ -46,8 +46,6 @@ export LDFLAGS=-L/tmp/protobuf/lib
export CXXFLAGS="-I/tmp/protobuf/include"

./gradlew clean $GRADLE_FLAGS
# Ensure dependency convergence
./gradlew :grpc-all:dependencies $GRADLE_FLAGS

if [[ -z "${SKIP_TESTS:-}" ]]; then
# Ensure all *.proto changes include *.java generated code
Expand Down
6 changes: 3 additions & 3 deletions census/build.gradle
Expand Up @@ -9,9 +9,9 @@ evaluationDependsOn(project(':grpc-api').path)

dependencies {
api project(':grpc-api')
guavaDependency 'implementation'
censusApiDependency 'implementation'
censusGrpcMetricDependency 'implementation'
implementation libraries.guava,
libraries.opencensus_api,
libraries.opencensus_contrib_grpc_metrics

testImplementation project(':grpc-api').sourceSets.test.output,
project(':grpc-context').sourceSets.test.output,
Expand Down
6 changes: 3 additions & 3 deletions core/build.gradle
Expand Up @@ -27,9 +27,9 @@ dependencies {
implementation libraries.gson,
libraries.android_annotations,
libraries.animalsniffer_annotations,
libraries.errorprone
guavaDependency 'implementation'
perfmarkDependency 'implementation'
libraries.errorprone,
libraries.guava,
libraries.perfmark
testImplementation project(':grpc-context').sourceSets.test.output,
project(':grpc-api').sourceSets.test.output,
project(':grpc-testing'),
Expand Down
2 changes: 1 addition & 1 deletion cronet/build.gradle
Expand Up @@ -35,7 +35,7 @@ android {
dependencies {
api project(':grpc-core'),
libraries.cronet_api
guavaDependency 'implementation'
implementation libraries.guava
testImplementation project(':grpc-testing')

testImplementation libraries.cronet_embedded
Expand Down
5 changes: 0 additions & 5 deletions examples/example-gauth/pom.xml
Expand Up @@ -49,11 +49,6 @@
<groupId>io.grpc</groupId>
<artifactId>grpc-auth</artifactId>
</dependency>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java-util</artifactId>
<version>${protobuf.version}</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>annotations-api</artifactId>
Expand Down
10 changes: 3 additions & 7 deletions grpclb/build.gradle
Expand Up @@ -14,13 +14,9 @@ dependencies {
implementation project(':grpc-core'),
project(':grpc-protobuf'),
project(':grpc-stub'),
libraries.protobuf
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'
}
guavaDependency 'implementation'
libraries.protobuf,
libraries.protobuf_util,
libraries.guava
runtimeOnly libraries.errorprone
compileOnly libraries.javax_annotation
testImplementation libraries.truth,
Expand Down
6 changes: 3 additions & 3 deletions interop-testing/build.gradle
Expand Up @@ -28,9 +28,9 @@ dependencies {
project(':grpc-testing'),
project(path: ':grpc-xds', configuration: 'shadow'),
libraries.junit,
libraries.truth
censusGrpcMetricDependency 'implementation'
googleOauth2Dependency 'implementation'
libraries.truth,
libraries.opencensus_contrib_grpc_metrics,
libraries.google_auth_oauth2_http
compileOnly libraries.javax_annotation
// TODO(sergiitk): replace with com.google.cloud:google-cloud-logging
// Used instead of google-cloud-logging because it's failing
Expand Down
7 changes: 4 additions & 3 deletions netty/build.gradle
Expand Up @@ -18,9 +18,10 @@ evaluationDependsOn(project(':grpc-core').path)
dependencies {
api project(':grpc-core'),
libraries.netty
implementation libraries.netty_proxy_handler
guavaDependency 'implementation'
perfmarkDependency 'implementation'
implementation libraries.netty_proxy_handler,
libraries.guava,
libraries.errorprone,
libraries.perfmark

// Tests depend on base class defined by core module.
testImplementation project(':grpc-core').sourceSets.test.output,
Expand Down
13 changes: 5 additions & 8 deletions okhttp/build.gradle
Expand Up @@ -11,14 +11,11 @@ description = "gRPC: OkHttp"
evaluationDependsOn(project(':grpc-core').path)

dependencies {
api project(':grpc-core')
api (libraries.okhttp) {
// prefer our own versions instead of okhttp's dependency
exclude group: 'com.squareup.okio', module: 'okio'
}
implementation libraries.okio
guavaDependency 'implementation'
perfmarkDependency 'implementation'
api project(':grpc-core'),
libraries.okhttp
implementation libraries.okio,
libraries.guava,
libraries.perfmark
// Tests depend on base class defined by core module.
testImplementation project(':grpc-core').sourceSets.test.output,
project(':grpc-api').sourceSets.test.output,
Expand Down
4 changes: 2 additions & 2 deletions protobuf-lite/build.gradle
Expand Up @@ -12,8 +12,8 @@ description = 'gRPC: Protobuf Lite'
dependencies {
api project(':grpc-api'),
libraries.protobuf_lite
implementation libraries.jsr305
guavaDependency 'implementation'
implementation libraries.jsr305,
libraries.guava

testImplementation project(':grpc-core')

Expand Down
4 changes: 1 addition & 3 deletions protobuf/build.gradle
Expand Up @@ -13,14 +13,12 @@ dependencies {
api project(':grpc-api'),
libraries.jsr305,
libraries.protobuf
guavaDependency 'implementation'
implementation libraries.guava

api (libraries.google_api_protos) {
// 'com.google.api:api-common' transitively depends on auto-value, which breaks our
// annotations.
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')) {
Expand Down

0 comments on commit 5642e01

Please sign in to comment.