plugins {
    id "com.android.application" apply false // Necessary for Android plugin to find its classes
    id "com.android.library" apply false
    id "com.google.osdetector" apply false
    id "me.champeau.gradle.japicmp" apply false
    id "net.ltgt.errorprone" apply false
}

import net.ltgt.gradle.errorprone.CheckSeverity

subprojects {
    apply plugin: "checkstyle"
    apply plugin: "idea"
    apply plugin: "signing"
    apply plugin: "jacoco"

    apply plugin: "com.google.osdetector"
    apply plugin: "net.ltgt.errorprone"

    group = "io.grpc"
    version = "1.36.3" // CURRENT_GRPC_VERSION

    repositories {
        maven { // The google mirror is less flaky than mavenCentral()
            url "https://maven-central.storage-download.googleapis.com/maven2/" }
        mavenCentral()
        mavenLocal()
    }

    tasks.withType(JavaCompile) {
        it.options.compilerArgs += [
            "-Xlint:all",
            "-Xlint:-options",
            "-Xlint:-path",
            "-Xlint:-try"
        ]
        it.options.encoding = "UTF-8"
        if (rootProject.hasProperty('failOnWarnings') && rootProject.failOnWarnings.toBoolean()) {
            it.options.compilerArgs += ["-Werror"]
        }
    }

    tasks.withType(GenerateModuleMetadata) {
        // Module metadata, introduced in Gradle 6.0, conflicts with our publishing task for
        // grpc-alts and grpc-compiler.
        enabled = false
    }

    def isAndroid = project.name in [
            'grpc-android', 'grpc-android-interop-testing', 'grpc-cronet']

    ext {
        def exeSuffix = osdetector.os == 'windows' ? ".exe" : ""
        protocPluginBaseName = 'protoc-gen-grpc-java'
        javaPluginPath = "$rootDir/compiler/build/exe/java_plugin/$protocPluginBaseName$exeSuffix"

        nettyVersion = '4.1.52.Final'
        guavaVersion = '30.0-android'
        googleauthVersion = '0.22.2'
        protobufVersion = '3.16.3'
        protocVersion = protobufVersion
        opencensusVersion = '0.28.0'
        autovalueVersion = '1.7.4'

        configureProtoCompilation = {
            String generatedSourcePath = "${projectDir}/src/generated"
            project.protobuf {
                protoc {
                    if (project.hasProperty('protoc')) {
                        path = project.protoc
                    } else {
                        artifact = "com.google.protobuf:protoc:${protocVersion}"
                    }
                }
                generateProtoTasks {
                    all().each { task ->
                        // Recompile protos when build.gradle has been changed, because
                        // it's possible the version of protoc has been changed.
                        task.inputs.file "${rootProject.projectDir}/build.gradle"
                        if (isAndroid) {
                            task.builtins {
                                java { option 'lite' }
                            }
                        }
                    }
                }
            }
            if (rootProject.childProjects.containsKey('grpc-compiler')) {
                // Only when the codegen is built along with the project, will we be able to run
                // the grpc code generator.
                task syncGeneratedSources { }
                project.protobuf {
                    plugins { grpc { path = javaPluginPath } }
                    generateProtoTasks {
                        all().each { task ->
                            String variantOrSourceSet = isAndroid ? task.variant.name : task.sourceSet.name
                            def syncTask = project.tasks.register("syncGeneratedSources${variantOrSourceSet}", Sync) {
                                from "$buildDir/generated/source/proto/${variantOrSourceSet}/grpc"
                                into "$generatedSourcePath/${variantOrSourceSet}/grpc"
                            }
                            syncGeneratedSources.dependsOn syncTask

                            task.dependsOn ':grpc-compiler:java_pluginExecutable'
                            // Recompile protos when the codegen has been changed
                            task.inputs.file javaPluginPath
                            task.plugins { grpc { option 'noversion' } }
                            if (isAndroid) {
                                task.plugins {
                                    grpc {
                                        option 'lite'
                                    }
                                }
                            }
                        }
                    }
                }
                // Re-sync as part of a normal build, to avoid forgetting to run the sync
                assemble.dependsOn syncGeneratedSources
            } else {
                // Otherwise, we just use the checked-in generated code.
                if (isAndroid) {
                    project.android.sourceSets {
                        debug { java { srcDir "${generatedSourcePath}/debug/grpc" } }
                        release { java { srcDir "${generatedSourcePath}/release/grpc" } }
                    }
                } else {
                    project.sourceSets {
                        main { java { srcDir "${generatedSourcePath}/main/grpc" } }
                        test { java { srcDir "${generatedSourcePath}/test/grpc" } }
                    }
                }
            }

            tasks.withType(JavaCompile) {
                appendToProperty(
                    it.options.errorprone.excludedPaths,
                    ".*/src/generated/[^/]+/java/.*" +
                        "|.*/build/generated/source/proto/[^/]+/java/.*",
                    "|")
            }
        }

        libraries = [
            android_annotations: "com.google.android:annotations:4.1.1.4",
            animalsniffer_annotations: "org.codehaus.mojo:animal-sniffer-annotations:1.19",
            autovalue: "com.google.auto.value:auto-value:${autovalueVersion}",
            autovalue_annotation: "com.google.auto.value:auto-value-annotations:${autovalueVersion}",
            errorprone: "com.google.errorprone:error_prone_annotations:2.4.0",
            cronet_api: 'org.chromium.net:cronet-api:76.3809.111',
            cronet_embedded: 'org.chromium.net:cronet-embedded:66.3359.158',
            gson: "com.google.code.gson:gson:2.8.6",
            guava: "com.google.guava:guava:${guavaVersion}",
            javax_annotation: 'org.apache.tomcat:annotations-api:6.0.53',
            jsr305: 'com.google.code.findbugs:jsr305:3.0.2',
            google_api_protos: 'com.google.api.grpc:proto-google-common-protos:2.0.1',
            google_auth_credentials: "com.google.auth:google-auth-library-credentials:${googleauthVersion}",
            google_auth_oauth2_http: "com.google.auth:google-auth-library-oauth2-http:${googleauthVersion}",
            okhttp: 'com.squareup.okhttp:okhttp:2.7.4',
            okio: 'com.squareup.okio:okio:1.17.5',
            opencensus_api: "io.opencensus:opencensus-api:${opencensusVersion}",
            opencensus_contrib_grpc_metrics: "io.opencensus:opencensus-contrib-grpc-metrics:${opencensusVersion}",
            opencensus_impl: "io.opencensus:opencensus-impl:${opencensusVersion}",
            opencensus_impl_lite: "io.opencensus:opencensus-impl-lite:${opencensusVersion}",
            opencensus_proto: "io.opencensus:opencensus-proto:0.2.0",
            instrumentation_api: 'com.google.instrumentation:instrumentation-api:0.4.3',
            perfmark: 'io.perfmark:perfmark-api:0.23.0',
            protobuf: "com.google.protobuf:protobuf-java:${protobufVersion}",
            protobuf_lite: "com.google.protobuf:protobuf-javalite:${protobufVersion}",
            protobuf_util: "com.google.protobuf:protobuf-java-util:${protobufVersion}",

            netty: "io.netty:netty-codec-http2:[${nettyVersion}]",
            netty_epoll: "io.netty:netty-transport-native-epoll:${nettyVersion}:linux-x86_64",
            netty_proxy_handler: "io.netty:netty-handler-proxy:${nettyVersion}",

            // Keep the following references of tcnative version in sync whenever it's updated
            // SECURITY.md (multiple occurrences)
            // examples/example-tls/build.gradle
            // examples/example-tls/pom.xml
            netty_tcnative: 'io.netty:netty-tcnative-boringssl-static:2.0.34.Final',

            conscrypt: 'org.conscrypt:conscrypt-openjdk-uber:2.5.1',
            re2j: 'com.google.re2j:re2j:1.5',

            bouncycastle: 'org.bouncycastle:bcpkix-jdk15on:1.67',

            // Test dependencies.
            junit: 'junit:junit:4.12',
            mockito: 'org.mockito:mockito-core:3.3.3',
            truth: 'com.google.truth:truth:1.0.1',
            guava_testlib: "com.google.guava:guava-testlib:${guavaVersion}",
            androidx_test: "androidx.test:core:1.3.0",
            robolectric: "org.robolectric:robolectric:4.3.1",

            // Benchmark dependencies
            hdrhistogram: 'org.hdrhistogram:HdrHistogram:2.1.12',
            math: 'org.apache.commons:commons-math3:3.6.1',

            // Jetty ALPN dependencies
            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)
            } else {
                property.set(value)
            }
        }
    }

    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 {
            tasks.withType(Javadoc) {
                options.addStringOption('Xdoclint:none', '-quiet')
            }
        }
    }

    jacoco { toolVersion = "0.8.2" }

    checkstyle {
        configDirectory = file("$rootDir/buildscripts")
        toolVersion = "6.17"
        ignoreFailures = false
        if (rootProject.hasProperty("checkstyle.ignoreFailures")) {
            ignoreFailures = rootProject.properties["checkstyle.ignoreFailures"].toBoolean()
        }
    }

    if (rootProject.properties.get('errorProne', true)) {
        dependencies {
            errorprone 'com.google.errorprone:error_prone_core:2.4.0'
            errorproneJavac 'com.google.errorprone:javac:9+181-r4173-1'
        }
    } else {
        // Disable Error Prone
        allprojects {
            afterEvaluate { project ->
                project.tasks.withType(JavaCompile) {
                    options.errorprone.enabled = false
                }
            }
        }
    }

    plugins.withId("java") {
        sourceCompatibility = 1.7
        targetCompatibility = 1.7

        dependencies {
            testImplementation libraries.junit,
                    libraries.mockito,
                    libraries.truth
        }

        compileTestJava {
            // serialVersionUID is basically guaranteed to be useless in our tests
            options.compilerArgs += [
                "-Xlint:-serial"
            ]
        }

        jar.manifest {
            attributes('Implementation-Title': name,
            'Implementation-Version': version,
            'Built-By': System.getProperty('user.name'),
            'Built-JDK': System.getProperty('java.version'),
            'Source-Compatibility': sourceCompatibility,
            'Target-Compatibility': targetCompatibility)
        }

        javadoc.options {
            encoding = 'UTF-8'
            use = true
            links 'https://docs.oracle.com/javase/8/docs/api/'
            source = "8"
        }

        checkstyleMain {
            source = fileTree(dir: "$projectDir/src/main", include: "**/*.java")
        }

        checkstyleTest {
            source = fileTree(dir: "$projectDir/src/test", include: "**/*.java")
        }

        // At a test failure, log the stack trace to the console so that we don't
        // have to open the HTML in a browser.
        test {
            testLogging {
                exceptionFormat = 'full'
                showExceptions true
                showCauses true
                showStackTraces true
            }
            maxHeapSize = '1500m'
        }

        if (rootProject.properties.get('errorProne', true)) {
            dependencies {
                annotationProcessor 'com.google.guava:guava-beta-checker:1.0'
            }
        }

        compileJava {
            // This project targets Java 7 (no method references)
            options.errorprone.check("UnnecessaryAnonymousClass", CheckSeverity.OFF)
            // This project targets Java 7 (no time.Duration class)
            options.errorprone.check("PreferJavaTimeOverload", CheckSeverity.OFF)
            // The warning fails to provide a source location
            options.errorprone.check("MissingSummary", CheckSeverity.OFF)
        }
        compileTestJava {
            // LinkedList doesn't hurt much in tests and has lots of usages
            options.errorprone.check("JdkObsolete", CheckSeverity.OFF)
            options.errorprone.check("UnnecessaryAnonymousClass", CheckSeverity.OFF)
            options.errorprone.check("PreferJavaTimeOverload", CheckSeverity.OFF)
        }
    }

    plugins.withId("me.champeau.gradle.jmh") {
        dependencies {
            jmh 'org.openjdk.jmh:jmh-core:1.19',
                    'org.openjdk.jmh:jmh-generator-bytecode:1.19'
        }
        // invoke jmh on a single benchmark class like so:
        //   ./gradlew -PjmhIncludeSingleClass=StatsTraceContextBenchmark clean :grpc-core:jmh
        jmh {
            warmupIterations = 10
            iterations = 10
            fork = 1
            // None of our benchmarks need the tests, and we have pseudo-circular
            // dependencies that break when including them. (context's testCompile
            // depends on core; core's testCompile depends on testing)
            includeTests = false
            if (project.hasProperty('jmhIncludeSingleClass')) {
                include = [
                    project.property('jmhIncludeSingleClass')
                ]
            }
        }
    }

    plugins.withId("maven-publish") {
        publishing {
            publications {
                // do not use mavenJava, as java plugin will modify it via "magic"
                maven(MavenPublication) {
                    pom {
                        name = project.group + ":" + project.name
                        url = 'https://github.com/grpc/grpc-java'
                        afterEvaluate {
                            // description is not available until evaluated.
                            description = project.description
                        }

                        scm {
                            connection = 'scm:git:https://github.com/grpc/grpc-java.git'
                            developerConnection = 'scm:git:git@github.com:grpc/grpc-java.git'
                            url = 'https://github.com/grpc/grpc-java'
                        }

                        licenses {
                            license {
                                name = 'Apache 2.0'
                                url = 'https://opensource.org/licenses/Apache-2.0'
                            }
                        }

                        developers {
                            developer {
                                id = "grpc.io"
                                name = "gRPC Contributors"
                                email = "grpc-io@googlegroups.com"
                                url = "https://grpc.io/"
                                organization = "gRPC Authors"
                                organizationUrl = "https://www.google.com"
                            }
                        }

                        withXml {
                            if (!(project.name in
                            [
                                "grpc-stub",
                                "grpc-protobuf",
                                "grpc-protobuf-lite",
                            ])) {
                                asNode().dependencies.'*'.findAll() { dep ->
                                    dep.artifactId.text() in ['grpc-api', 'grpc-core']
                                }.each() { core ->
                                    core.version*.value = "[" + core.version.text() + "]"
                                }
                            }
                        }
                    }
                }
            }
            repositories {
                maven {
                    if (rootProject.hasProperty('repositoryDir')) {
                        url = new File(rootProject.repositoryDir).toURI()
                    } else {
                        String stagingUrl
                        if (rootProject.hasProperty('repositoryId')) {
                            stagingUrl = 'https://oss.sonatype.org/service/local/staging/deployByRepositoryId/' +
                                    rootProject.repositoryId
                        } else {
                            stagingUrl = 'https://oss.sonatype.org/service/local/staging/deploy/maven2/'
                        }
                        credentials {
                            if (rootProject.hasProperty('ossrhUsername') && rootProject.hasProperty('ossrhPassword')) {
                                username = rootProject.ossrhUsername
                                password = rootProject.ossrhPassword
                            }
                        }
                        def releaseUrl = stagingUrl
                        def snapshotUrl = 'https://oss.sonatype.org/content/repositories/snapshots/'
                        url = version.endsWith('SNAPSHOT') ? snapshotUrl : releaseUrl
                    }
                }
            }
        }

        signing {
            required false
            sign publishing.publications.maven
        }

        plugins.withId("java") {
            java {
                withJavadocJar()
                withSourcesJar()
            }

            publishing {
                publications {
                    maven {
                        if (project.name != 'grpc-netty-shaded') {
                            from components.java
                        }
                    }
                }
            }
        }
    }

    // Run with: ./gradlew japicmp --continue
    plugins.withId("me.champeau.gradle.japicmp") {
        def baselineGrpcVersion = '1.6.1'

        // Get the baseline version's jar for this subproject
        File baselineArtifact = null
        // Use a detached configuration, otherwise the current version's jar will take precedence
        // over the baseline jar.
        // A necessary hack, the intuitive thing does NOT work:
        // https://discuss.gradle.org/t/is-the-default-configuration-leaking-into-independent-configurations/2088/6
        def oldGroup = project.group
        try {
            project.group = 'virtual_group_for_japicmp'
            String depModule = "io.grpc:${project.name}:${baselineGrpcVersion}@jar"
            String depJar = "${project.name}-${baselineGrpcVersion}.jar"
            Configuration configuration = configurations.detachedConfiguration(
                    dependencies.create(depModule)
                    )
            baselineArtifact = files(configuration.files).filter {
                it.name.equals(depJar)
            }.singleFile
        } finally {
            project.group = oldGroup
        }

        // Add a japicmp task that compares the current .jar with baseline .jar
        task japicmp(type: me.champeau.gradle.japicmp.JapicmpTask, dependsOn: jar) {
            oldClasspath = files(baselineArtifact)
            newClasspath = files(jar.archivePath)
            onlyBinaryIncompatibleModified = false
            // Be quiet about things that did not change
            onlyModified = true
            // This task should fail if there are incompatible changes
            failOnModification = true
            ignoreMissingClasses = true
            htmlOutputFile = file("$buildDir/reports/japi.html")

            packageExcludes = ['io.grpc.internal']

            // Also break on source incompatible changes, not just binary.
            // Eg adding abstract method to public class.
            // TODO(zpencer): enable after japicmp-gradle-plugin/pull/14
            // breakOnSourceIncompatibility = true

            // Ignore any classes or methods marked @ExperimentalApi
            // TODO(zpencer): enable after japicmp-gradle-plugin/pull/15
            // annotationExcludes = ['@io.grpc.ExperimentalApi']
        }
    }
}
