mobile/android/geckoview/build.gradle
author Cosmin Sabou <csabou@mozilla.com>
Wed, 21 Nov 2018 17:46:44 +0200
changeset 447478 3bd6b94b23e5dc644606c0f7f708f9008ccf0939
parent 447475 bae43dfd3700460a55f3559eabbc888975f3a3c0
child 447890 0bb898d00ed7b9d1bdaf3b4320cbc2ac5bca00fc
permissions -rw-r--r--
Backed out changeset bae43dfd3700 (bug 1485045) for toolchains gradle bustages. CLOSED TREE

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

    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}\""
        // This should really come from the included binaries, but that's not easy.
        buildConfigField 'String', "MOZ_APP_ABI", mozconfig.substs['COMPILE_ENVIRONMENT'] ? "\"${ mozconfig.substs.TARGET_XPCOM_ABI}\"" : '"arm-eabi-gcc3"';
        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}\"";

        buildConfigField 'String', "USER_AGENT_GECKOVIEW_MOBILE", "\"Mozilla/5.0 (Android \" + android.os.Build.VERSION.RELEASE + \"; Mobile; rv: ${mozconfig.substs.MOZ_APP_VERSION}) Gecko/${mozconfig.substs.MOZ_APP_VERSION} GeckoView/${mozconfig.substs.MOZ_APP_VERSION}\"";
        buildConfigField 'String', "USER_AGENT_GECKOVIEW_TABLET", "\"Mozilla/5.0 (Android \" + android.os.Build.VERSION.RELEASE + \"; Tablet; rv: ${mozconfig.substs.MOZ_APP_VERSION}) Gecko/${mozconfig.substs.MOZ_APP_VERSION} GeckoView/${mozconfig.substs.MOZ_APP_VERSION}\"";

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

        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 "2g"
    }

    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.1'
    androidTestImplementation 'com.android.support.test:rules:1.0.1'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
    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
    }

    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'

uploadArchives {
    repositories.mavenDeployer {
        pom.groupId = 'org.mozilla.geckoview'

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

        pom.version = computeVersionNumber()

        pom.project {
            licenses {
                license {
                    name 'The Mozilla Public License, v. 2.0'
                    url 'http://mozilla.org/MPL/2.0/'
                    distribution 'repo'
                }
            }
        }
        repository(url: "file://${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') }

    artifacts {
        // Instead of default (release) configuration, publish one with Gecko binaries.
        archives bundleWithGeckoBinariesRelease
        // Javadoc and sources for developer ergononomics.
        archives javadocJarWithGeckoBinariesRelease
        archives sourcesJarWithGeckoBinariesRelease
    }

    // 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'
    args android.bootClasspath
    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.
        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'

// TODO: Change this to `org` after hiding org.mozilla.gecko
apiLint.packageFilter = 'org.mozilla.geckoview'