// Top-level build file where you can add configuration options common to all sub-projects/modules. import io.gitlab.arturbosch.detekt.Detekt import io.gitlab.arturbosch.detekt.DetektCreateBaselineTask import org.gradle.internal.logging.text.StyledTextOutput.Style import org.gradle.internal.logging.text.StyledTextOutputFactory import org.jetbrains.kotlin.gradle.tasks.KotlinCompile import static org.gradle.api.tasks.testing.TestResult.ResultType buildscript { repositories { gradle.mozconfig.substs.GRADLE_MAVEN_REPOSITORIES.each { repository -> maven { url = repository if (gradle.mozconfig.substs.ALLOW_INSECURE_GRADLE_REPOSITORIES) { allowInsecureProtocol = true } } } } dependencies { classpath libs.tools.androidgradle } } plugins { alias(libs.plugins.detekt) alias(libs.plugins.kotlin.android) apply false alias(libs.plugins.kotlin.compose) apply false alias(libs.plugins.ksp) } allprojects { repositories { gradle.mozconfig.substs.GRADLE_MAVEN_REPOSITORIES.each { repository -> maven { url = repository if (gradle.mozconfig.substs.ALLOW_INSECURE_GRADLE_REPOSITORIES) { allowInsecureProtocol = true } } } maven { url = "${gradle.mozconfig.topobjdir}/gradle/maven" } } // Enable Kotlin warnings as errors for all modules tasks.withType(KotlinCompile).configureEach { compilerOptions.allWarningsAsErrors = true } } subprojects { apply plugin: 'jacoco' // Prevent some dependencies used by Fenix/Focus from being used in AC. project.configurations.all { exclude group: 'com.adjust.sdk', module: 'adjust-android' exclude group: 'io.mockk', module: 'mockk' } project.configurations.configureEach { // Dependencies can't depend on a different major version of Glean than A-C itself. resolutionStrategy.eachDependency { details -> if (details.requested.group == 'org.mozilla.telemetry' && details.requested.name.contains('glean') ) { def requested = details.requested.version.tokenize(".") def defined = libs.versions.mozilla.glean.get().tokenize(".") // Check the major version if (requested[0] != defined[0]) { throw new AssertionError("Cannot resolve to a single Glean version. Requested: ${details.requested.version}, A-C uses: ${libs.versions.mozilla.glean.get()}") } else { // Enforce that all (transitive) dependencies are using the defined Glean version details.useVersion libs.versions.mozilla.glean.get() } } } resolutionStrategy.capabilitiesResolution.withCapability("org.mozilla.telemetry:glean-native") { def toBeSelected = candidates.find { it.id instanceof ModuleComponentIdentifier && it.id.module.contains('geckoview') } if (toBeSelected != null) { select(toBeSelected) } because 'use GeckoView Glean instead of standalone Glean' } } if (gradle.hasProperty('localProperties.dependencySubstitutions.geckoviewTopsrcdir')) { if (gradle.hasProperty('localProperties.dependencySubstitutions.geckoviewTopobjdir')) { ext.topobjdir = gradle."localProperties.dependencySubstitutions.geckoviewTopobjdir" } ext.topsrcdir = gradle."localProperties.dependencySubstitutions.geckoviewTopsrcdir" apply from: "${topsrcdir}/substitute-local-geckoview.gradle" } afterEvaluate { if (it.hasProperty('android')) { jacoco { toolVersion = libs.versions.jacoco.get() } // Format test output tasks.matching {it instanceof Test}.configureEach() { systemProperty "robolectric.logging", "stdout" systemProperty "logging.test-mode", "true" systemProperty "javax.net.ssl.trustStoreType", "JKS" testLogging.events = [] beforeSuite { descriptor -> if (descriptor.getClassName() != null) { println("\nSUITE: " + descriptor.getClassName()) } } beforeTest { descriptor -> println(" TEST: " + descriptor.getName()) } onOutput { descriptor, event -> it.logger.lifecycle(" " + event.message.trim()) } afterTest { descriptor, result -> switch (result.getResultType()) { case ResultType.SUCCESS: println(" SUCCESS") break case ResultType.FAILURE: def testId = descriptor.getClassName() + "." + descriptor.getName() println(" TEST-UNEXPECTED-FAIL | " + testId + " | " + result.getException()) break case ResultType.SKIPPED: println(" SKIPPED") break } it.logger.lifecycle("") } } dependencies { lintChecks project(':tooling-lint') } kotlin { jvmToolchain(config.jvmTargetCompatibility) } android { // We can't have one baseline file at the root of android-components because // this is not a project module and we would have to coordinate every module to // merge baselines. lint { baseline = file("${projectDir}/lint-baseline.xml") } buildToolsVersion gradle.mozconfig.substs.ANDROID_BUILD_TOOLS_VERSION testOptions { unitTests { includeAndroidResources = true } } // https://issuetracker.google.com/issues/379732901 packaging { resources { excludes += ['META-INF/LICENSE.md', 'META-INF/LICENSE-notice.md', 'META-INF/versions/9/OSGI-INF/MANIFEST.MF'] } } androidResources { ignoreAssetsPattern = "manifest.template.json" } } if (project.hasProperty("coverage") && project.name != "support-test") { android.buildTypes.all { buildType -> tasks.withType(Test).configureEach() { jacoco { includeNoLocationClasses = true excludes = ['jdk.internal.*'] } finalizedBy { "jacoco${buildType.name.capitalize()}TestReport" } } tasks.register("jacoco${buildType.name.capitalize()}TestReport", JacocoReport) { reports { xml.required = true html.required = true } def fileFilter = ['**/R.class', '**/R$*.class', '**/BuildConfig.*', '**/Manifest*.*', '**/*Test*.*', 'android/**/*.*', '**/*$[0-9].*'] def kotlinDebugTree = fileTree(dir: "$project.layout.buildDirectory/tmp/kotlin-classes/${buildType.name}", excludes: fileFilter) def javaDebugTree = fileTree(dir: "$project.layout.buildDirectory/intermediates/classes/${buildType.name}", excludes: fileFilter) def mainSrc = "$project.projectDir/src/main/java" sourceDirectories.setFrom(files([mainSrc])) classDirectories.setFrom(files([kotlinDebugTree, javaDebugTree])) getExecutionData().setFrom(fileTree(project.layout.buildDirectory).include([ "jacoco/test${buildType.name.capitalize()}UnitTest.exec" ])) } } android { buildTypes { debug { testCoverageEnabled true } } } } } } tasks.withType(KotlinCompile).configureEach { // Translate Kotlin messages like "w: ..." and "e: ..." into // "...: warning: ..." and "...: error: ...", to make Treeherder understand. def listener = { if (it.startsWith("e: warnings found")) { return } 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 doFirst { logging.addStandardErrorListener(listener) } doLast { logging.removeStandardErrorListener(listener) } } } if (findProject(":geckoview") == null) { // Avoid adding this task if it already exists in a different root project. tasks.register("clean", Delete) { delete rootProject.layout.buildDirectory } } detekt { input = files("$projectDir/components", "$projectDir/buildSrc", "$projectDir/samples") config = files("$projectDir/config/detekt.yml") baseline = file("$projectDir/config/detekt-baseline.xml") reports { html { enabled = true destination = file("$projectDir/build/reports/detekt.html") } xml { enabled = false } txt { enabled = false } } } tasks.withType(Detekt).configureEach() { // Custom detekt rules should be built before. // See https://detekt.dev/docs/introduction/extensions#pitfalls dependsOn(":tooling-detekt:assemble") autoCorrect = true exclude "**/build.gradle.kts" exclude "**/build/**" exclude "**/docs/**" exclude "**/resources/**" exclude "**/src/androidTest/**" exclude "**/src/iosTest/**" exclude "**/src/main/assets/extensions/**" exclude "**/src/test/**" exclude "**/test/src/**" exclude "**/tmp/**" exclude "**/tooling/fetch-tests/**" } // Apply same path exclusions as for the main task tasks.withType(DetektCreateBaselineTask).configureEach() { dependsOn(":browser-icons:updateBuiltInExtensionVersion") dependsOn(":feature-accounts:updateBuiltInExtensionVersion") dependsOn(":feature-readerview:updateBuiltInExtensionVersion") dependsOn(":feature-search:updateAdsExtensionVersion") dependsOn(":feature-search:updateCookiesExtensionVersion") dependsOn(":samples-browser:updateBorderifyExtensionVersion") dependsOn(":samples-browser:updateTestExtensionVersion") dependsOn(":samples-compose-browser:updateBorderifyExtensionVersion") dependsOn(":samples-compose-browser:updateTestExtensionVersion") dependsOn(":tooling-detekt:assemble") exclude "**/build.gradle.kts" exclude "**/build/**" exclude "**/docs/**" exclude "**/resources/**" exclude "**/src/androidTest/**" exclude "**/src/iosTest/**" exclude "**/src/main/assets/extensions/**" exclude "**/src/test/**" exclude "**/test/src/**" exclude "**/tmp/**" exclude "**/tooling/fetch-tests/**" } configurations { ktlint } dependencies { ktlint(libs.ktlint) { attributes { attribute(Bundling.BUNDLING_ATTRIBUTE, getObjects().named(Bundling, Bundling.EXTERNAL)) } } detektPlugins project(":tooling-detekt") } tasks.register("ktlint", JavaExec) { group = "verification" description = "Check Kotlin code style." classpath = configurations.ktlint mainClass.set("com.pinterest.ktlint.Main") args "components/**/*.kt" args "samples/**/*.kt" args "!**/build/**/*.kt" args "buildSrc/**/*.kt" args "--baseline=ktlint-baseline.xml" args "--reporter=json,output=build/reports/ktlint/ktlint.json" args "--reporter=plain" } tasks.register("ktlintFormat", JavaExec) { group = "formatting" description = "Fix Kotlin code style deviations." classpath = configurations.ktlint mainClass.set("com.pinterest.ktlint.Main") args "-F" args "components/**/*.kt" args "samples/**/*.kt" args "!**/build/**/*.kt" args "buildSrc/**/*.kt" args "--baseline=ktlint-baseline.xml" args "--reporter=json,output=build/reports/ktlint/ktlintFormat.json" args "--reporter=plain" jvmArgs("--add-opens", "java.base/java.lang=ALL-UNNAMED") } tasks.register("listRepositories") { def reposData = project.provider { project.repositories.collect { repo -> [name: repo.name, url: repo.url.toString()] } } doLast { println "Repositories:" reposData.get().each { println "Name: " + it.name + "; url: " + it.url } } }