mobile/android/geckoview/build.gradle
author Narcis Beleuzu <nbeleuzu@mozilla.com>
Fri, 15 Mar 2019 21:11:47 +0200
changeset 464425 1f911d12fd0737a2107c8b0cc98cf3d62165d7db
parent 464421 9f34d0075a7300366203a37ac0155aa2198fdf7a
child 464458 08d9090316cf9942ce7749159079622418adf671
permissions -rw-r--r--
Backed out 11 changesets (bug 1512274) for TL bustages. CLOSED TREE Backed out changeset 9f34d0075a73 (bug 1512274) Backed out changeset 6ca9fc4956d0 (bug 1512274) Backed out changeset 9a9d3c9c124a (bug 1512274) Backed out changeset 670e07131f14 (bug 1512274) Backed out changeset 161731389a46 (bug 1512274) Backed out changeset c34445b1db88 (bug 1512274) Backed out changeset 60897ab00574 (bug 1512274) Backed out changeset 5d02f27fe174 (bug 1512274) Backed out changeset 380cdbe5557c (bug 1512274) Backed out changeset 58642fa9c95b (bug 1512274) Backed out changeset 3812d6760379 (bug 1512274)

buildDir "${topobjdir}/gradle/build/mobile/android/geckoview"

apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'

apply from: "${topsrcdir}/mobile/android/gradle/product_flavors.gradle"

// The SDK binding generation tasks depend on the JAR creation task of the
// :annotations project.
evaluationDependsOn(':annotations')

// Non-official versions are like "61.0a1", where "a1" is the milestone.
// This simply strips that off, leaving "61.0" in this example.
def getAppVersionWithoutMilestone() {
    return mozconfig.substs.MOZ_APP_VERSION.replaceFirst(/a[0-9]/, "")
}

// This converts MOZ_APP_VERSION into an integer
// version code.
//
// We take something like 58.1.2a1 and come out with 5800102
// This gives us 3 digits for the major number, and 2 digits
// each for the minor and build number. Beta and Release
def computeVersionCode() {
    String appVersion = getAppVersionWithoutMilestone()

    // Split on the dot delimiter, e.g. 58.1.1a1 -> ["58, "1", "1a1"]
    String[] parts = appVersion.split('\\.')

    assert parts.size() == 2 || parts.size() == 3

    // Major
    int code = Integer.parseInt(parts[0]) * 100000

    // Minor
    code += Integer.parseInt(parts[1]) * 100

    // Build
    if (parts.size() == 3) {
        code += Integer.parseInt(parts[2])
    }

    return code;
}

def computeVersionNumber() {
  def appVersion = getAppVersionWithoutMilestone()
  def parts = appVersion.split('\\.')
  return parts[0] + "." + parts[1] + "." + getBuildId()
}

// Mimic Python: open(os.path.join(buildconfig.topobjdir, 'buildid.h')).readline().split()[2]
def getBuildId() {
    return file("${topobjdir}/buildid.h").getText('utf-8').split()[2]
}

android {
    compileSdkVersion project.ext.compileSdkVersion

    useLibrary 'android.test.runner'
    useLibrary 'android.test.base'
    useLibrary 'android.test.mock'

    defaultConfig {
        targetSdkVersion project.ext.targetSdkVersion
        minSdkVersion project.ext.minSdkVersion
        manifestPlaceholders = project.ext.manifestPlaceholders

        versionCode computeVersionCode()
        versionName "${mozconfig.substs.MOZ_APP_VERSION}-${mozconfig.substs.MOZ_UPDATE_CHANNEL}"
        consumerProguardFiles 'proguard-rules.txt'

        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"

        buildConfigField 'String', "GRE_MILESTONE", "\"${mozconfig.substs.GRE_MILESTONE}\""
        buildConfigField 'String', "MOZ_APP_BASENAME", "\"${mozconfig.substs.MOZ_APP_BASENAME}\"";

        // For the benefit of future archaeologists:
        // GRE_BUILDID is exactly the same as MOZ_APP_BUILDID unless you're running
        // on XULRunner, which is never the case on Android.
        buildConfigField 'String', "MOZ_APP_BUILDID", "\"${getBuildId()}\"";
        buildConfigField 'String', "MOZ_APP_ID", "\"${mozconfig.substs.MOZ_APP_ID}\"";
        buildConfigField 'String', "MOZ_APP_NAME", "\"${mozconfig.substs.MOZ_APP_NAME}\"";
        buildConfigField 'String', "MOZ_APP_VENDOR", "\"${mozconfig.substs.MOZ_APP_VENDOR}\"";
        buildConfigField 'String', "MOZ_APP_VERSION", "\"${mozconfig.substs.MOZ_APP_VERSION}\"";
        buildConfigField 'String', "MOZ_APP_DISPLAYNAME", "\"${mozconfig.substs.MOZ_APP_DISPLAYNAME}\"";
        buildConfigField 'String', "MOZ_APP_UA_NAME", "\"${mozconfig.substs.MOZ_APP_UA_NAME}\"";
        buildConfigField 'String', "MOZ_UPDATE_CHANNEL", "\"${mozconfig.substs.MOZ_UPDATE_CHANNEL}\"";

        // MOZILLA_VERSION is oddly quoted from autoconf, but we don't have to handle it specially in Gradle.
        buildConfigField 'String', "MOZILLA_VERSION", "\"${mozconfig.substs.MOZILLA_VERSION}\"";
        buildConfigField 'String', "OMNIJAR_NAME", "\"${mozconfig.substs.OMNIJAR_NAME}\"";

        // Keep in sync with actual user agent in nsHttpHandler::BuildUserAgent
        buildConfigField 'String', "USER_AGENT_GECKOVIEW_MOBILE", "\"Mozilla/5.0 (Android \" + android.os.Build.VERSION.RELEASE + \"; Mobile; rv:\" + ${mozconfig.defines.MOZILLA_UAVERSION} + \") Gecko/\" + ${mozconfig.defines.MOZILLA_UAVERSION} + \" Firefox/\" + ${mozconfig.defines.MOZILLA_UAVERSION}";
        buildConfigField 'String', "USER_AGENT_GECKOVIEW_TABLET", "\"Mozilla/5.0 (Android \" + android.os.Build.VERSION.RELEASE + \"; Tablet; rv:\" + ${mozconfig.defines.MOZILLA_UAVERSION} + \") Gecko/\" + ${mozconfig.defines.MOZILLA_UAVERSION} + \" Firefox/\" + ${mozconfig.defines.MOZILLA_UAVERSION}";

        buildConfigField 'int', 'MIN_SDK_VERSION', mozconfig.substs.MOZ_ANDROID_MIN_SDK_VERSION;

        // Is the underlying compiled C/C++ code compiled with --enable-debug?
        buildConfigField 'boolean', 'DEBUG_BUILD', mozconfig.substs.MOZ_DEBUG ? 'true' : 'false';

        // See this wiki page for more details about channel specific build defines:
        // https://wiki.mozilla.org/Platform/Channel-specific_build_defines
        // This makes no sense for GeckoView and should be removed as soon as possible.
        buildConfigField 'boolean', 'RELEASE_OR_BETA', mozconfig.substs.RELEASE_OR_BETA ? 'true' : 'false';
        // This makes no sense for GeckoView and should be removed as soon as possible.
        buildConfigField 'boolean', 'NIGHTLY_BUILD', mozconfig.substs.NIGHTLY_BUILD ? 'true' : 'false';
        // This makes no sense for GeckoView and should be removed as soon as possible.
        buildConfigField 'boolean', 'MOZ_CRASHREPORTER', mozconfig.substs.MOZ_CRASHREPORTER ? 'true' : 'false';

        // Official corresponds, roughly, to whether this build is performed on
        // Mozilla's continuous integration infrastructure. You should disable
        // developer-only functionality when this flag is set.
        // This makes no sense for GeckoView and should be removed as soon as possible.
        buildConfigField 'boolean', 'MOZILLA_OFFICIAL', mozconfig.substs.MOZILLA_OFFICIAL ? 'true' : 'false';
    }

    project.configureProductFlavors.delegate = it
    project.configureProductFlavors()

    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }

    dexOptions {
        javaMaxHeapSize "4g"
    }

    lintOptions {
        abortOnError false
    }

    sourceSets {
        main {
            java {
                srcDir "${topsrcdir}/mobile/android/geckoview/src/thirdparty/java"

                if (!mozconfig.substs.MOZ_ANDROID_HLS_SUPPORT) {
                    exclude 'com/google/android/exoplayer2/**'
                    exclude 'org/mozilla/gecko/media/GeckoHlsAudioRenderer.java'
                    exclude 'org/mozilla/gecko/media/GeckoHlsPlayer.java'
                    exclude 'org/mozilla/gecko/media/GeckoHlsRendererBase.java'
                    exclude 'org/mozilla/gecko/media/GeckoHlsVideoRenderer.java'
                    exclude 'org/mozilla/gecko/media/Utils.java'
                }

                if (mozconfig.substs.MOZ_WEBRTC) {
                    srcDir "${topsrcdir}/media/webrtc/trunk/webrtc/base/java/src"
                    srcDir "${topsrcdir}/media/webrtc/trunk/webrtc/modules/video_capture/android/java/src"
                }
            }

            assets {
            }
        }
    }
}

tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile) {
    // Translate Kotlin messages like "w: ..." and "e: ..." into
    // "...: warning: ..." and "...: error: ...", to make Treeherder understand.
    def listener = {
        if (it.startsWith('w: ') || it.startsWith('e: ')) {
            def matches = (it =~ /([ew]): (.+): \((\d+), (\d+)\): (.*)/)
            if (!matches) {
                logger.quiet "kotlinc message format has changed!"
                if (it.startsWith('w: ')) {
                    // For warnings, don't continue because we don't want to throw an
                    // exception. For errors, we want the exception so that the new error
                    // message format gets translated properly.
                    return
                }
            }
            def (_, type, file, line, column, message) = matches[0]
            type = (type == 'w') ? 'warning' : 'error'
            // Use logger.lifecycle, which does not go through stderr again.
            logger.lifecycle "$file:$line:$column: $type: $message"
        }
    } as StandardOutputListener

    kotlinOptions {
        allWarningsAsErrors = true
    }

    doFirst {
        logging.addStandardErrorListener(listener)
    }
    doLast {
        logging.removeStandardErrorListener(listener)
    }
}

tasks.withType(Javadoc) {
    // Implement warning-as-error for javadoc.
    def warnings = 0
    def listener = {
        if (it.contains(': warning')) {
            warnings++
        }
    } as StandardOutputListener

    doFirst {
        logging.addStandardErrorListener(listener)
    }
    doLast {
        logging.removeStandardErrorListener(listener)
        if (warnings > 0) {
            throw new GradleException("Treating $warnings javadoc warning(s) as error(s)")
        }
    }
}

dependencies {
    implementation "com.android.support:support-v4:$support_library_version"
    implementation "com.android.support:palette-v7:$support_library_version"

    testImplementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
    testImplementation 'junit:junit:4.12'
    testImplementation 'org.robolectric:robolectric:3.8'
    testImplementation 'org.mockito:mockito-core:1.10.19'

    androidTestImplementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test:rules:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
    androidTestImplementation "com.android.support:support-annotations:$support_library_version"

    androidTestImplementation 'org.eclipse.jetty:jetty-server:7.6.21.v20160908'
}

apply from: "${topsrcdir}/mobile/android/gradle/with_gecko_binaries.gradle"

android.libraryVariants.all { variant ->
    // See the notes in mobile/android/app/build.gradle for details on including
    // Gecko binaries and the Omnijar.
    if ((variant.productFlavors*.name).contains('withGeckoBinaries')) {
        configureVariantWithGeckoBinaries(variant)
    }

    // Javadoc and Sources JAR configuration cribbed from
    // https://github.com/mapbox/mapbox-gl-native/blob/d169ea55c1cfa85cd8bf19f94c5f023569f71810/platform/android/MapboxGLAndroidSDK/build.gradle#L85
    // informed by
    // https://code.tutsplus.com/tutorials/creating-and-publishing-an-android-library--cms-24582,
    // and amended from numerous Stackoverflow posts.
    def name = variant.name
    def javadoc = task "javadoc${name.capitalize()}"(type: Javadoc) {
        description = "Generate Javadoc for build variant $name"
        destinationDir = new File(destinationDir, variant.baseName)
        doFirst {
            classpath = files(variant.javaCompile.classpath.files)
        }

        source = files(variant.javaCompile.source)
        exclude '**/R.java', '**/BuildConfig.java'
        include 'org/mozilla/geckoview/**'
        options.addPathOption('sourcepath', ':').setValue(
            variant.sourceSets.collect({ it.javaDirectories }).flatten() +
            variant.generateBuildConfig.sourceOutputDir +
            variant.aidlCompile.sourceOutputDir)

        // javadoc 8 has a bug that requires the rt.jar file from the JRE to be
        // in the bootclasspath (https://stackoverflow.com/a/30458820).
        options.bootClasspath = [
            file("${System.properties['java.home']}/lib/rt.jar")] + android.bootClasspath
        options.memberLevel = JavadocMemberLevel.PROTECTED
        options.source = 8
        options.links("https://d.android.com/reference/")

        options.docTitle = "GeckoView ${mozconfig.substs.MOZ_APP_VERSION} API"
        options.header = "GeckoView ${mozconfig.substs.MOZ_APP_VERSION} API"
        options.noTimestamp = true
        options.noIndex = true
        options.noQualifiers = ['java.lang']
        options.tags = ['hide:a:']
    }

    def javadocJar = task("javadocJar${name.capitalize()}", type: Jar, dependsOn: javadoc) {
        classifier = 'javadoc'
        from javadoc.destinationDir
    }

    // This task is used by `mach android geckoview-docs`.
    task("javadocCopyJar${name.capitalize()}", type: Copy) {
        from(javadocJar.destinationDir) {
            include 'geckoview-*-javadoc.jar'
            rename { _ -> 'geckoview-javadoc.jar' }
        }
        into javadocJar.destinationDir
        dependsOn javadocJar
    }

    def sourcesJar = task("sourcesJar${name.capitalize()}", type: Jar) {
        classifier 'sources'
        description = "Generate Javadoc for build variant $name"
        destinationDir = new File(destinationDir, variant.baseName)
        from files(variant.javaCompile.source)
    }
}

android.libraryVariants.all { variant ->
    configureLibraryVariantWithJNIWrappers(variant, "Generated")
}

apply plugin: 'maven-publish'

version = computeVersionNumber()

publishing {
    publications {
        android.libraryVariants.all { variant ->
            "${variant.name}"(MavenPublication) {
                pom {
                    groupId = 'org.mozilla.geckoview'

                    if (mozconfig.substs.MOZ_UPDATE_CHANNEL == 'release') {
                        // release artifacts don't specify the channel, for the sake of simplicity
                        artifactId = "geckoview-${mozconfig.substs.ANDROID_CPU_ARCH}"
                    } else {
                        artifactId = "geckoview-${mozconfig.substs.MOZ_UPDATE_CHANNEL}-${mozconfig.substs.ANDROID_CPU_ARCH}"
                    }

                    url = 'https://wiki.mozilla.org/Mobile/GeckoView'

                    licenses {
                        license {
                            name = 'The Mozilla Public License, v. 2.0'
                            url = 'http://mozilla.org/MPL/2.0/'
                            distribution = 'repo'
                        }
                    }

                    scm {
                        connection = 'scm::hg::https://hg.mozilla.org/mozilla-central/'
                        url = 'https://hg.mozilla.org/mozilla-central/'
                    }

                    // Unfortunately Gradle does not provide a way to expose dependencies for custom
                    // project types like Android plugins. So we need to add them manually to the POM
                    // XML here, or use a plugin that achieves the same (like
                    // https://github.com/wupdigital/android-maven-publish). We elect to do this
                    // manually since our dependencies are simple and plugins increase our complexity
                    // surface. This workaround can be removed after this issue is fixed:
                    // https://github.com/gradle/gradle/issues/1842
                    withXml {
                        def dependenciesNode = asNode().appendNode('dependencies')

                        configurations.getByName("implementation").dependencies.each {
                            def dependencyNode = dependenciesNode.appendNode('dependency')
                            dependencyNode.appendNode('groupId', it.group)
                            dependencyNode.appendNode('artifactId', it.name)
                            dependencyNode.appendNode('version', it.version)
                        }
                    }
                }

                artifact tasks["bundle${variant.name.capitalize()}"]

                // Javadoc and sources for developer ergononomics.
                artifact tasks["javadocJar${variant.name.capitalize()}"]
                artifact tasks["sourcesJar${variant.name.capitalize()}"]
            }
        }
    }
    repositories {
        maven {
            url = "${project.buildDir}/maven"
        }
    }
}

// This is all related to the withGeckoBinaries approach; see
// mobile/android/gradle/with_gecko_binaries.gradle.
afterEvaluate {
    // The bundle tasks are only present when the particular configuration is
    // being built, so this task might not exist.  (This is due to the way the
    // Android Gradle plugin defines things during configuration.)
    def bundleWithGeckoBinaries = tasks.findByName('bundleWithGeckoBinariesRelease')
    if (!bundleWithGeckoBinaries) {
        return
    }

    // Remove default configuration, which is the release configuration, when
    // we're actually building withGeckoBinaries.  This makes `gradle install`
    // install the withGeckoBinaries artifacts, not the release artifacts (which
    // are withoutGeckoBinaries and not suitable for distribution.)
    def Configuration archivesConfig = project.getConfigurations().getByName('archives')
    archivesConfig.artifacts.removeAll { it.extension.equals('aar') }

    // For now, ensure Kotlin is only used in tests.
    android.sourceSets.all { sourceSet ->
        if (sourceSet.name.startsWith('test') || sourceSet.name.startsWith('androidTest')) {
            return
        }
        (sourceSet.java.srcDirs + sourceSet.kotlin.srcDirs).each {
            if (!fileTree(it, { include '**/*.kt' }).empty) {
                throw new GradleException("Kotlin used in non-test directory ${it.path}")
            }
        }
    }
}

// Bug 1353055 - Strip 'vars' debugging information to agree with moz.build.
apply from: "${topsrcdir}/mobile/android/gradle/debug_level.gradle"
android.libraryVariants.all configureVariantDebugLevel

// There's nothing specific to the :geckoview project here -- this just needs to
// be somewhere where the Android plugin is available so that we can fish the
// path to "android.jar".
task("generateSDKBindings", type: JavaExec) {
    classpath project(':annotations').jar.archivePath
    classpath project(':annotations').compileJava.classpath

    // To use the lint APIs: "Lint must be invoked with the System property
    // com.android.tools.lint.bindir pointing to the ANDROID_SDK tools
    // directory"
    systemProperties = [
        'com.android.tools.lint.bindir': "${android.sdkDirectory}/tools",
    ]

    main = 'org.mozilla.gecko.annotationProcessors.SDKProcessor'
    // We only want to generate bindings for the main framework JAR,
    // but not any of the additional android.test libraries.
    args android.bootClasspath.findAll { !it.getName().startsWith('android.test.') }
    args 16
    args "${topobjdir}/widget/android/bindings"

    // Configure the arguments at evaluation-time, not at configuration-time.
    doFirst {
        // From -Pgenerate_sdk_bindings_args=... on command line; missing in
        // `android-gradle-dependencies` toolchain task.
        if (project.hasProperty('generate_sdk_bindings_args')) {
            args project.generate_sdk_bindings_args.split(':')
        }
    }

    workingDir "${topsrcdir}/widget/android/bindings"

    dependsOn project(':annotations').jar
}

apply from: "${topsrcdir}/mobile/android/gradle/jacoco_dependencies.gradle"
if (project.hasProperty('enable_code_coverage')) {
    apply from: "${topsrcdir}/mobile/android/gradle/jacoco_for_junit.gradle"
}

// Set up code coverage for tests on emulators.
if (mozconfig.substs.MOZ_JAVA_CODE_COVERAGE) {
    android {
        jacoco {
            version = "$jacoco_version"
        }
        buildTypes {
            debug {
                testCoverageEnabled true
            }
        }
    }

    configurations {
        // This configuration is used for dependencies that are not needed at compilation or
        // runtime, but need to be exported as artifacts of the build for usage on the testing
        // machines.
        coverageDependency
    }

    dependencies {
        // This is required both in the instrumented application classes and the test classes,
        // so `api` has to be used instead of `androidTestImplementation`.
        api "org.jacoco:org.jacoco.agent:$jacoco_version:runtime"

        coverageDependency ("org.jacoco:org.jacoco.cli:$jacoco_version:nodeps") {
            exclude group: 'org.ow2.asm', module: '*'
        }
    }

    // This task is used by `mach android archive-coverage-artifacts`.
    task copyCoverageDependencies(type: Copy) {
        from(configurations.coverageDependency) {
            include 'org.jacoco.cli-*-nodeps.jar'
            rename { _ -> 'target.jacoco-cli.jar' }
        }
        into "$buildDir/coverage"
    }

    // Generate tasks to archive compiled classfiles for later use with JaCoCo report generation.
    // One of these tasks is used by `mach android archive-coverage-artifacts`.
    android.libraryVariants.all { variant ->
        def name = variant.name
        def compileTask = tasks.getByName("compile${name.capitalize()}JavaWithJavac")
        task "archiveClassfiles${name.capitalize()}"(type: Zip, dependsOn: compileTask) {
            description = "Archive compiled classfiles for $name in order to export them as code coverage artifacts."
            def fileFilter = ['**/androidTest/**',
                              '**/test/**',
                              '**/R.class',
                              '**/R$*.class',
                              '**/BuildConfig.*',
                              '**/Manifest*.*',
                              '**/*Test*.*',
                              'android/**/*.*']
            from fileTree(dir: compileTask.destinationDir, excludes: fileFilter)
            destinationDir = file("${buildDir}/coverage")
            // Note: This task assumes only one variant of archiveClassfiles* will be used.
            // Running multiple variants of this task will overwrite the output archive.
            archiveName = 'target.geckoview_classfiles.zip'
        }
    }
}

apply plugin: 'org.mozilla.apilint'

apiLint {
    // TODO: Change this to `org` after hiding org.mozilla.gecko
    packageFilter = 'org.mozilla.geckoview'
    changelogFileName = 'src/main/java/org/mozilla/geckoview/doc-files/CHANGELOG.md'
    skipClassesRegex = ['^org.mozilla.geckoview.BuildConfig$']
    lintFilters = ['GV']
}