1plugins { 2 id 'java' // changes the behavior of TARGET_JVM_VERSION_ATTRIBUTE 3 4 id "com.google.osdetector" apply false 5 id "me.champeau.gradle.japicmp" apply false 6 id "net.ltgt.errorprone" apply false 7 id 'com.google.cloud.tools.jib' apply false 8} 9 10import net.ltgt.gradle.errorprone.CheckSeverity 11import org.gradle.util.GUtil 12 13subprojects { 14 apply plugin: "checkstyle" 15 apply plugin: "idea" 16 apply plugin: "signing" 17 apply plugin: "jacoco" 18 19 apply plugin: "com.google.osdetector" 20 apply plugin: "net.ltgt.errorprone" 21 22 group = "io.grpc" 23 version = "1.56.1-SNAPSHOT" // CURRENT_GRPC_VERSION 24 25 repositories { 26 maven { // The google mirror is less flaky than mavenCentral() 27 url "https://maven-central.storage-download.googleapis.com/maven2/" } 28 mavenCentral() 29 mavenLocal() 30 } 31 32 tasks.withType(JavaCompile).configureEach { 33 it.options.compilerArgs += [ 34 "-Xlint:all", 35 "-Xlint:-options", 36 "-Xlint:-path", 37 "-Xlint:-try" 38 ] 39 it.options.encoding = "UTF-8" 40 if (rootProject.hasProperty('failOnWarnings') && rootProject.failOnWarnings.toBoolean()) { 41 it.options.compilerArgs += ["-Werror"] 42 } 43 } 44 45 tasks.withType(GenerateModuleMetadata).configureEach { 46 // Module metadata, introduced in Gradle 6.0, conflicts with our publishing task for 47 // grpc-alts and grpc-compiler. 48 enabled = false 49 } 50 51 def isAndroid = project.name in [ 52 'grpc-android', 'grpc-android-interop-testing', 'grpc-cronet'] 53 54 ext { 55 def exeSuffix = osdetector.os == 'windows' ? ".exe" : "" 56 protocPluginBaseName = 'protoc-gen-grpc-java' 57 javaPluginPath = "$rootDir/compiler/build/exe/java_plugin/$protocPluginBaseName$exeSuffix" 58 59 configureProtoCompilation = { 60 String generatedSourcePath = "${projectDir}/src/generated" 61 project.protobuf { 62 protoc { 63 if (project.hasProperty('protoc')) { 64 path = project.protoc 65 } else { 66 artifact = libs.protobuf.protoc.get() 67 } 68 } 69 generateProtoTasks { 70 all().each { task -> 71 // Recompile protos when build.gradle has been changed, because 72 // it's possible the version of protoc has been changed. 73 task.inputs.file("${rootProject.projectDir}/build.gradle") 74 .withPathSensitivity(PathSensitivity.RELATIVE) 75 .withPropertyName('root build.gradle') 76 if (isAndroid) { 77 task.builtins { 78 java { option 'lite' } 79 } 80 } 81 } 82 } 83 } 84 if (rootProject.childProjects.containsKey('grpc-compiler')) { 85 // Only when the codegen is built along with the project, will we be able to run 86 // the grpc code generator. 87 def syncGeneratedSources = tasks.register("syncGeneratedSources") { } 88 project.protobuf { 89 plugins { grpc { path = javaPluginPath } } 90 generateProtoTasks { 91 all().each { task -> 92 String variantOrSourceSet = isAndroid ? task.variant.name : task.sourceSet.name 93 def syncTask = project.tasks.register("syncGeneratedSources${variantOrSourceSet}", Sync) { 94 from task 95 into "$generatedSourcePath/$variantOrSourceSet" 96 include "grpc/" 97 } 98 syncGeneratedSources.configure { 99 dependsOn syncTask 100 } 101 102 task.configure { 103 dependsOn ':grpc-compiler:java_pluginExecutable' 104 // Recompile protos when the codegen has been changed 105 inputs.file javaPluginPath 106 plugins { grpc { option 'noversion' } } 107 if (isAndroid) { 108 plugins { 109 grpc { 110 option 'lite' 111 } 112 } 113 } 114 } 115 } 116 } 117 } 118 // Re-sync as part of a normal build, to avoid forgetting to run the sync 119 tasks.named("assemble").configure { 120 dependsOn syncGeneratedSources 121 } 122 } else { 123 // Otherwise, we just use the checked-in generated code. 124 if (isAndroid) { 125 project.android.sourceSets { 126 debug { java { srcDir "${generatedSourcePath}/debug/grpc" } } 127 release { java { srcDir "${generatedSourcePath}/release/grpc" } } 128 } 129 } else { 130 project.sourceSets.each() { sourceSet -> 131 sourceSet.java { srcDir "${generatedSourcePath}/${sourceSet.name}/grpc" } 132 } 133 } 134 } 135 136 tasks.withType(JavaCompile).configureEach { 137 appendToProperty( 138 it.options.errorprone.excludedPaths, 139 ".*/src/generated/[^/]+/java/.*" + 140 "|.*/build/generated/source/proto/[^/]+/java/.*", 141 "|") 142 } 143 } 144 145 libraries = libs 146 147 appendToProperty = { Property<String> property, String value, String separator -> 148 if (property.present) { 149 property.set(property.get() + separator + value) 150 } else { 151 property.set(value) 152 } 153 } 154 } 155 156 // Disable JavaDoc doclint on Java 8. It's annoying. 157 if (JavaVersion.current().isJava8Compatible()) { 158 allprojects { 159 tasks.withType(Javadoc).configureEach { 160 options.addStringOption('Xdoclint:none', '-quiet') 161 } 162 } 163 } 164 165 checkstyle { 166 configDirectory = file("$rootDir/buildscripts") 167 toolVersion = libs.checkstyle.get().version 168 ignoreFailures = false 169 if (rootProject.hasProperty("checkstyle.ignoreFailures")) { 170 ignoreFailures = rootProject.properties["checkstyle.ignoreFailures"].toBoolean() 171 } 172 } 173 174 if (!project.hasProperty('errorProne') || errorProne.toBoolean()) { 175 dependencies { 176 errorprone JavaVersion.current().isJava11Compatible() ? libs.errorprone.core : libs.errorprone.corejava8 177 } 178 } else { 179 // Disable Error Prone 180 tasks.withType(JavaCompile).configureEach { 181 options.errorprone.enabled = false 182 } 183 } 184 185 plugins.withId("java") { 186 sourceCompatibility = 1.8 187 targetCompatibility = 1.8 188 189 dependencies { 190 testImplementation libraries.junit, 191 libraries.mockito.core, 192 libraries.truth 193 } 194 195 tasks.named("compileTestJava").configure { 196 // serialVersionUID is basically guaranteed to be useless in our tests 197 options.compilerArgs += [ 198 "-Xlint:-serial" 199 ] 200 } 201 202 tasks.named("jar").configure { 203 manifest { 204 attributes('Implementation-Title': name, 205 'Implementation-Version': project.version) 206 } 207 } 208 209 tasks.named("javadoc").configure { 210 options { 211 encoding = 'UTF-8' 212 use = true 213 links 'https://docs.oracle.com/javase/8/docs/api/' 214 source = "8" 215 } 216 } 217 218 tasks.named("checkstyleMain").configure { 219 source = fileTree(dir: "$projectDir/src/main", include: "**/*.java") 220 } 221 222 tasks.named("checkstyleTest").configure { 223 source = fileTree(dir: "$projectDir/src/test", include: "**/*.java") 224 } 225 226 // At a test failure, log the stack trace to the console so that we don't 227 // have to open the HTML in a browser. 228 tasks.named("test").configure { 229 testLogging { 230 exceptionFormat = 'full' 231 showExceptions true 232 showCauses true 233 showStackTraces true 234 } 235 maxHeapSize = '1500m' 236 } 237 238 if (!project.hasProperty('errorProne') || errorProne.toBoolean()) { 239 dependencies { 240 annotationProcessor libs.guava.betaChecker 241 } 242 } 243 244 tasks.named("compileJava").configure { 245 // This project targets Java 7 (no time.Duration class) 246 options.errorprone.check("PreferJavaTimeOverload", CheckSeverity.OFF) 247 options.errorprone.check("JavaUtilDate", CheckSeverity.OFF) 248 // The warning fails to provide a source location 249 options.errorprone.check("MissingSummary", CheckSeverity.OFF) 250 } 251 tasks.named("compileTestJava").configure { 252 // LinkedList doesn't hurt much in tests and has lots of usages 253 options.errorprone.check("JdkObsolete", CheckSeverity.OFF) 254 options.errorprone.check("PreferJavaTimeOverload", CheckSeverity.OFF) 255 options.errorprone.check("JavaUtilDate", CheckSeverity.OFF) 256 } 257 258 plugins.withId("ru.vyarus.animalsniffer") { 259 // Only available after java plugin has loaded 260 animalsniffer { 261 toolVersion = libs.animalsniffer.asProvider().get().version 262 } 263 } 264 } 265 266 plugins.withId("java-library") { 267 // Detect Maven Enforcer's dependencyConvergence failures. We only care 268 // for artifacts used as libraries by others with Maven. 269 tasks.register('checkUpperBoundDeps') { 270 inputs.files(configurations.runtimeClasspath).withNormalizer(ClasspathNormalizer) 271 outputs.file("${buildDir}/tmp/${name}") // Fake output for UP-TO-DATE checking 272 doLast { 273 requireUpperBoundDepsMatch(configurations.runtimeClasspath, project) 274 } 275 } 276 tasks.named('compileJava').configure { 277 dependsOn checkUpperBoundDeps 278 } 279 } 280 281 plugins.withId("me.champeau.jmh") { 282 // invoke jmh on a single benchmark class like so: 283 // ./gradlew -PjmhIncludeSingleClass=StatsTraceContextBenchmark clean :grpc-core:jmh 284 tasks.named("compileJmhJava").configure { 285 sourceCompatibility = 1.8 286 targetCompatibility = 1.8 287 } 288 tasks.named("jmh").configure { 289 warmupIterations = 10 290 iterations = 10 291 fork = 1 292 // None of our benchmarks need the tests, and we have pseudo-circular 293 // dependencies that break when including them. (context's testCompile 294 // depends on core; core's testCompile depends on testing) 295 includeTests = false 296 if (project.hasProperty('jmhIncludeSingleClass')) { 297 includes = [ 298 project.property('jmhIncludeSingleClass') 299 ] 300 } 301 } 302 } 303 304 plugins.withId("com.github.johnrengelman.shadow") { 305 tasks.named("shadowJar").configure { 306 // Do a dance to remove Class-Path. This needs to run after the doFirst() from the 307 // shadow plugin that adds Class-Path and before the core jar action. Using doFirst will 308 // have this run before the shadow plugin, and doLast will run after the core jar 309 // action. See #8606. 310 // The shadow plugin adds another doFirst when application is used for setting 311 // Main-Class. Ordering with it doesn't matter. 312 actions.add(plugins.hasPlugin("application") ? 2 : 1, new Action<Task>() { 313 @Override public void execute(Task task) { 314 if (!task.manifest.attributes.remove("Class-Path")) { 315 throw new AssertionError("Did not find Class-Path to remove from manifest") 316 } 317 } 318 }) 319 } 320 } 321 322 plugins.withId("maven-publish") { 323 publishing { 324 publications { 325 // do not use mavenJava, as java plugin will modify it via "magic" 326 maven(MavenPublication) { 327 pom { 328 name = project.group + ":" + project.name 329 url = 'https://github.com/grpc/grpc-java' 330 afterEvaluate { 331 // description is not available until evaluated. 332 description = project.description 333 } 334 335 scm { 336 connection = 'scm:git:https://github.com/grpc/grpc-java.git' 337 developerConnection = 'scm:git:[email protected]:grpc/grpc-java.git' 338 url = 'https://github.com/grpc/grpc-java' 339 } 340 341 licenses { 342 license { 343 name = 'Apache 2.0' 344 url = 'https://opensource.org/licenses/Apache-2.0' 345 } 346 } 347 348 developers { 349 developer { 350 id = "grpc.io" 351 name = "gRPC Contributors" 352 email = "[email protected]" 353 url = "https://grpc.io/" 354 organization = "gRPC Authors" 355 organizationUrl = "https://www.google.com" 356 } 357 } 358 } 359 } 360 } 361 repositories { 362 maven { 363 if (rootProject.hasProperty('repositoryDir')) { 364 url = new File(rootProject.repositoryDir).toURI() 365 } else { 366 String stagingUrl 367 if (rootProject.hasProperty('repositoryId')) { 368 stagingUrl = 'https://oss.sonatype.org/service/local/staging/deployByRepositoryId/' + 369 rootProject.repositoryId 370 } else { 371 stagingUrl = 'https://oss.sonatype.org/service/local/staging/deploy/maven2/' 372 } 373 credentials { 374 if (rootProject.hasProperty('ossrhUsername') && rootProject.hasProperty('ossrhPassword')) { 375 username = rootProject.ossrhUsername 376 password = rootProject.ossrhPassword 377 } 378 } 379 def releaseUrl = stagingUrl 380 def snapshotUrl = 'https://oss.sonatype.org/content/repositories/snapshots/' 381 url = version.endsWith('SNAPSHOT') ? snapshotUrl : releaseUrl 382 } 383 } 384 } 385 } 386 387 signing { 388 required false 389 sign publishing.publications.maven 390 } 391 392 plugins.withId("java") { 393 java { 394 withJavadocJar() 395 withSourcesJar() 396 } 397 398 publishing { 399 publications { 400 maven { 401 if (project.name != 'grpc-netty-shaded') { 402 from components.java 403 } 404 } 405 } 406 } 407 } 408 } 409 410 // Run with: ./gradlew japicmp --continue 411 plugins.withId("me.champeau.gradle.japicmp") { 412 def baselineGrpcVersion = '1.6.1' 413 414 // Get the baseline version's jar for this subproject 415 File baselineArtifact = null 416 // Use a detached configuration, otherwise the current version's jar will take precedence 417 // over the baseline jar. 418 // A necessary hack, the intuitive thing does NOT work: 419 // https://discuss.gradle.org/t/is-the-default-configuration-leaking-into-independent-configurations/2088/6 420 def oldGroup = project.group 421 try { 422 project.group = 'virtual_group_for_japicmp' 423 String depModule = "io.grpc:${project.name}:${baselineGrpcVersion}@jar" 424 String depJar = "${project.name}-${baselineGrpcVersion}.jar" 425 Configuration configuration = configurations.detachedConfiguration( 426 dependencies.create(depModule) 427 ) 428 baselineArtifact = files(configuration.files).filter { 429 it.name.equals(depJar) 430 }.singleFile 431 } finally { 432 project.group = oldGroup 433 } 434 435 // Add a japicmp task that compares the current .jar with baseline .jar 436 tasks.register("japicmp", me.champeau.gradle.japicmp.JapicmpTask) { 437 dependsOn jar 438 oldClasspath = files(baselineArtifact) 439 newClasspath = files(jar.archiveFile) 440 onlyBinaryIncompatibleModified = false 441 // Be quiet about things that did not change 442 onlyModified = true 443 // This task should fail if there are incompatible changes 444 failOnModification = true 445 ignoreMissingClasses = true 446 htmlOutputFile = file("$buildDir/reports/japi.html") 447 448 packageExcludes = ['io.grpc.internal'] 449 450 // Also break on source incompatible changes, not just binary. 451 // Eg adding abstract method to public class. 452 // TODO(zpencer): enable after japicmp-gradle-plugin/pull/14 453 // breakOnSourceIncompatibility = true 454 455 // Ignore any classes or methods marked @ExperimentalApi 456 // TODO(zpencer): enable after japicmp-gradle-plugin/pull/15 457 // annotationExcludes = ['@io.grpc.ExperimentalApi'] 458 } 459 } 460} 461 462class DepAndParents { 463 DependencyResult dep 464 List<String> parents 465} 466 467/** 468 * Make sure that Maven would select the same versions as Gradle selected. 469 * This is essentially the same as if we used Maven Enforcer's 470 * requireUpperBoundDeps for our artifacts. 471 */ 472def requireUpperBoundDepsMatch(Configuration conf, Project project) { 473 // artifact name => version 474 Map<String,String> golden = conf.resolvedConfiguration.resolvedArtifacts.collectEntries { 475 ResolvedArtifact it -> 476 ModuleVersionIdentifier id = it.moduleVersion.id 477 [id.group + ":" + id.name, id.version] 478 } 479 // Breadth-first search like Maven for dependency resolution 480 Queue<DepAndParents> queue = new ArrayDeque<>() 481 conf.incoming.resolutionResult.root.dependencies.each { 482 queue.add(new DepAndParents(dep: it, parents: [project.displayName])) 483 } 484 Set<String> found = new HashSet<>() 485 while (!queue.isEmpty()) { 486 DepAndParents depAndParents = queue.remove() 487 ResolvedDependencyResult result = (ResolvedDependencyResult) depAndParents.dep 488 ModuleVersionIdentifier id = result.selected.moduleVersion 489 String artifact = id.group + ":" + id.name 490 if (found.contains(artifact)) 491 continue 492 found.add(artifact) 493 String version 494 if (result.requested instanceof ProjectComponentSelector) { 495 ProjectComponentSelector selector = (ProjectComponentSelector) result.requested 496 version = project.findProject(selector.projectPath).version 497 } else { 498 version = ((ModuleComponentSelector) result.requested).version 499 } 500 String goldenVersion = golden[artifact] 501 if (goldenVersion != version && "[$goldenVersion]" != version) { 502 throw new RuntimeException( 503 "Maven version skew: $artifact ($version != $goldenVersion) " 504 + "Bad version dependency path: " + depAndParents.parents 505 + " Run './gradlew $project.path:dependencies --configuration $conf.name' " 506 + "to diagnose") 507 } 508 result.selected.dependencies.each { 509 queue.add(new DepAndParents( 510 dep: it, parents: depAndParents.parents + [artifact + ":" + version])) 511 } 512 } 513} 514 515repositories { 516 mavenCentral() 517 google() 518} 519 520def isAcceptableVersion(ModuleComponentIdentifier candidate) { 521 String group = candidate.group 522 String module = candidate.module 523 String version = candidate.version 524 if (group == 'com.google.guava') 525 return true 526 if (group == 'io.netty' && version.contains('Final')) 527 return true 528 if (module == 'android-api-level-19') 529 return true 530 return version ==~ /^[0-9]+(\.[0-9]+)+$/ 531} 532 533configurations { 534 checkForUpdates { 535 attributes.attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 8) 536 resolutionStrategy { 537 componentSelection { 538 all { 539 if (!isAcceptableVersion(it.candidate)) 540 it.reject("Not stable version") 541 } 542 } 543 } 544 } 545} 546 547// Checks every dependency in the version catalog to see if there is a newer 548// version available. The 'checkForUpdates' configuration restricts the 549// versions considered. 550tasks.register('checkForUpdates') { 551 doLast { 552 def updateConf = project.configurations.checkForUpdates 553 updateConf.setVisible(false) 554 updateConf.setTransitive(false) 555 def versionCatalog = project.extensions.getByType(VersionCatalogsExtension).named("libs") 556 versionCatalog.libraryAliases.each { name -> 557 def dep = versionCatalog.findLibrary(name).get().get() 558 559 def oldConf = updateConf.copy() 560 def oldDep = project.dependencies.create( 561 group: dep.group, name: dep.name, version: dep.versionConstraint, classifier: 'pom') 562 oldConf.dependencies.add(oldDep) 563 def oldResolved = oldConf.resolvedConfiguration.resolvedArtifacts.iterator().next() 564 565 def newConf = updateConf.copy() 566 def newDep = project.dependencies.create( 567 group: dep.group, name: dep.name, version: '+', classifier: 'pom') 568 newConf.dependencies.add(newDep) 569 def newResolved = newConf.resolvedConfiguration.resolvedArtifacts.iterator().next() 570 if (oldResolved != newResolved) { 571 def oldId = oldResolved.id.componentIdentifier 572 def newId = newResolved.id.componentIdentifier 573 println("${newId.group}:${newId.module} ${oldId.version} -> ${newId.version}") 574 } 575 } 576 } 577} 578