xref: /aosp_15_r20/external/grpc-grpc-java/build.gradle (revision e07d83d3ffcef9ecfc9f7f475418ec639ff0e5fe)
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