build.gradle (15467B)
1 // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 3 import io.gitlab.arturbosch.detekt.Detekt 4 import io.gitlab.arturbosch.detekt.DetektCreateBaselineTask 5 import org.jetbrains.kotlin.gradle.tasks.KotlinCompile 6 7 import static org.gradle.api.tasks.testing.TestResult.ResultType 8 9 buildscript { 10 repositories { 11 gradle.mozconfig.substs.GRADLE_MAVEN_REPOSITORIES.each { repository -> 12 maven { 13 url = repository 14 if (gradle.mozconfig.substs.ALLOW_INSECURE_GRADLE_REPOSITORIES) { 15 allowInsecureProtocol = true 16 } 17 } 18 } 19 } 20 21 dependencies { 22 classpath libs.android.gradle.plugin 23 } 24 } 25 26 plugins { 27 alias(libs.plugins.detekt) 28 alias(libs.plugins.kotlin.android) apply false 29 alias(libs.plugins.kotlin.compose) apply false 30 alias(libs.plugins.ksp) 31 } 32 33 allprojects { 34 repositories { 35 gradle.mozconfig.substs.GRADLE_MAVEN_REPOSITORIES.each { repository -> 36 maven { 37 url = repository 38 if (gradle.mozconfig.substs.ALLOW_INSECURE_GRADLE_REPOSITORIES) { 39 allowInsecureProtocol = true 40 } 41 } 42 } 43 44 maven { 45 url = "${gradle.mozconfig.topobjdir}/gradle/maven" 46 } 47 } 48 49 ext { 50 gleanVersion = libs.versions.glean.get() 51 } 52 53 // Enable Kotlin warnings as errors for all modules 54 tasks.withType(KotlinCompile).configureEach { 55 compilerOptions.allWarningsAsErrors = true 56 } 57 58 if (gradle.mozconfig.substs.DOWNLOAD_ALL_GRADLE_DEPENDENCIES) { 59 // Work around https://github.com/google/ksp/issues/1964 by explicitly 60 // declaring dependencies that are dynamically added during Gradle task 61 // execution. 62 pluginManager.withPlugin('com.google.devtools.ksp') { 63 project.configurations { 64 kspDependencies 65 } 66 67 project.dependencies { 68 kspDependencies libs.ksp.symbol.processing.aa 69 kspDependencies libs.ksp.symbol.processing.aa.embeddable 70 kspDependencies libs.ksp.symbol.processing.api 71 kspDependencies libs.ksp.symbol.processing.common.deps 72 } 73 } 74 } 75 } 76 77 subprojects { 78 apply plugin: 'jacoco' 79 80 // Prevent some dependencies used by Fenix/Focus from being used in AC. 81 project.configurations.all { 82 exclude group: 'com.adjust.sdk', module: 'adjust-android' 83 exclude group: 'io.mockk', module: 'mockk' 84 } 85 86 project.configurations.configureEach { 87 // Dependencies can't depend on a different major version of Glean than A-C itself. 88 resolutionStrategy.eachDependency { details -> 89 if (details.requested.group == 'org.mozilla.telemetry' 90 && details.requested.name.contains('glean') ) { 91 def requested = details.requested.version.tokenize(".") 92 def defined = project.ext.gleanVersion.tokenize(".") 93 // Check the major version 94 if (requested[0] != defined[0]) { 95 throw new AssertionError("Cannot resolve to a single Glean version. Requested: ${details.requested.version}, A-C uses: ${project.ext.gleanVersion}") 96 } else { 97 // Enforce that all (transitive) dependencies are using the defined Glean version 98 details.useVersion project.ext.gleanVersion 99 } 100 } 101 } 102 103 resolutionStrategy.capabilitiesResolution.withCapability("org.mozilla.telemetry:glean-native") { 104 def toBeSelected = candidates.find { it.id instanceof ModuleComponentIdentifier && it.id.module.contains('geckoview') } 105 if (toBeSelected != null) { 106 select(toBeSelected) 107 } 108 because 'use GeckoView Glean instead of standalone Glean' 109 } 110 } 111 112 if (gradle.hasProperty('localProperties.dependencySubstitutions.geckoviewTopsrcdir')) { 113 if (gradle.hasProperty('localProperties.dependencySubstitutions.geckoviewTopobjdir')) { 114 ext.topobjdir = gradle."localProperties.dependencySubstitutions.geckoviewTopobjdir" 115 } 116 ext.topsrcdir = gradle."localProperties.dependencySubstitutions.geckoviewTopsrcdir" 117 apply from: "${topsrcdir}/substitute-local-geckoview.gradle" 118 } 119 120 afterEvaluate { 121 if (it.hasProperty('android')) { 122 // Format test output 123 tasks.matching {it instanceof Test}.configureEach() { 124 systemProperty "robolectric.logging", "stdout" 125 systemProperty "logging.test-mode", "true" 126 systemProperty "javax.net.ssl.trustStoreType", "JKS" 127 128 testLogging.events = [] 129 130 beforeSuite { descriptor -> 131 if (descriptor.getClassName() != null) { 132 println("\nSUITE: " + descriptor.getClassName()) 133 } 134 } 135 136 beforeTest { descriptor -> 137 println(" TEST: " + descriptor.getName()) 138 } 139 140 onOutput { descriptor, event -> 141 it.logger.lifecycle(" " + event.message.trim()) 142 } 143 144 afterTest { descriptor, result -> 145 switch (result.getResultType()) { 146 case ResultType.SUCCESS: 147 println(" SUCCESS") 148 break 149 150 case ResultType.FAILURE: 151 def testId = descriptor.getClassName() + "." + descriptor.getName() 152 println(" TEST-UNEXPECTED-FAIL | " + testId + " | " + result.getException()) 153 break 154 155 case ResultType.SKIPPED: 156 println(" SKIPPED") 157 break 158 } 159 it.logger.lifecycle("") 160 } 161 } 162 163 dependencies { 164 lintChecks project(':components:tooling-lint') 165 } 166 167 kotlin { 168 jvmToolchain(config.jvmTargetCompatibility) 169 } 170 171 android { 172 // We can't have one baseline file at the root of android-components because 173 // this is not a project module and we would have to coordinate every module to 174 // merge baselines. 175 lint { 176 baseline = file("${projectDir}/lint-baseline.xml") 177 } 178 179 buildToolsVersion gradle.mozconfig.substs.ANDROID_BUILD_TOOLS_VERSION 180 181 testOptions { 182 testCoverage { 183 jacocoVersion = libs.versions.jacoco.get() 184 } 185 unitTests { 186 includeAndroidResources = true 187 } 188 } 189 190 packaging { 191 resources { 192 excludes += ['META-INF/LICENSE.md', 'META-INF/LICENSE-notice.md'] 193 } 194 } 195 196 androidResources { 197 ignoreAssetsPattern = "manifest.template.json" 198 } 199 } 200 201 if (project.name != "support-test") { 202 android.buildTypes.all { buildType -> 203 tasks.withType(Test).configureEach() { 204 jacoco { 205 includeNoLocationClasses = true 206 excludes = ['jdk.internal.*'] 207 } 208 209 finalizedBy { "jacoco${buildType.name.capitalize()}TestReport" } 210 } 211 212 tasks.register("jacoco${buildType.name.capitalize()}TestReport", JacocoReport) { 213 reports { 214 xml.required = true 215 html.required = true 216 } 217 218 def fileFilter = ['**/R.class', '**/R$*.class', '**/BuildConfig.*', '**/Manifest*.*', 219 '**/*Test*.*', 'android/**/*.*', '**/*$[0-9].*'] 220 def kotlinDebugTree = fileTree(dir: "$project.layout.buildDirectory/tmp/kotlin-classes/${buildType.name}", excludes: fileFilter) 221 def javaDebugTree = fileTree(dir: "$project.layout.buildDirectory/intermediates/classes/${buildType.name}", excludes: fileFilter) 222 def mainSrc = "$project.projectDir/src/main/java" 223 224 sourceDirectories.setFrom(files([mainSrc])) 225 classDirectories.setFrom(files([kotlinDebugTree, javaDebugTree])) 226 getExecutionData().setFrom(fileTree(project.layout.buildDirectory).include([ 227 "jacoco/test${buildType.name.capitalize()}UnitTest.exec" 228 ])) 229 } 230 } 231 232 android { 233 buildTypes { 234 debug { 235 // Enable test coverage when fetching dependencies: it's an easy way to 236 // ensure we fetch the Jacoco agent dependency `org.jacoco.agent`, which 237 // resolves to `org.jacoco.agent-$VERSION-runtime.jar`. 238 testCoverageEnabled = project.hasProperty("coverage") || gradle.mozconfig.substs.DOWNLOAD_ALL_GRADLE_DEPENDENCIES 239 } 240 } 241 } 242 } 243 } 244 } 245 246 tasks.withType(KotlinCompile).configureEach { 247 // Translate Kotlin messages like "w: ..." and "e: ..." into 248 // "...: warning: ..." and "...: error: ...", to make Treeherder understand. 249 def listener = { 250 if (it.startsWith("e: warnings found")) { 251 return 252 } 253 254 if (it.startsWith('w: ') || it.startsWith('e: ')) { 255 def matches = (it =~ /([ew]): (.+):(\d+):(\d+) (.*)/) 256 if (!matches) { 257 logger.quiet "kotlinc message format has changed!" 258 if (it.startsWith('w: ')) { 259 // For warnings, don't continue because we don't want to throw an 260 // exception. For errors, we want the exception so that the new error 261 // message format gets translated properly. 262 return 263 } 264 } 265 def (_, type, file, line, column, message) = matches[0] 266 type = (type == 'w') ? 'warning' : 'error' 267 // Use logger.lifecycle, which does not go through stderr again. 268 logger.lifecycle "$file:$line:$column: $type: $message" 269 } 270 } as StandardOutputListener 271 272 doFirst { 273 logging.addStandardErrorListener(listener) 274 } 275 doLast { 276 logging.removeStandardErrorListener(listener) 277 } 278 } 279 280 tasks.withType(AbstractTestTask).configureEach { 281 failOnNoDiscoveredTests = false 282 } 283 } 284 285 if (findProject(":geckoview") == null) { 286 // Avoid adding this task if it already exists in a different root project. 287 tasks.register("clean", Delete) { 288 delete rootProject.layout.buildDirectory 289 } 290 } 291 292 detekt { 293 input = files("$projectDir/components", "$projectDir/buildSrc", "$projectDir/samples") 294 config = files("$projectDir/config/detekt.yml") 295 baseline = file("$projectDir/config/detekt-baseline.xml") 296 297 reports { 298 html { 299 enabled = true 300 destination = file("$projectDir/build/reports/detekt.html") 301 } 302 xml { 303 enabled = false 304 } 305 txt { 306 enabled = false 307 } 308 } 309 } 310 311 tasks.named("detekt").configure { 312 reports { 313 custom { 314 reportId = "suppression-count" 315 outputLocation.set(file("$projectDir/build/reports/suppressions.txt")) 316 } 317 } 318 } 319 320 tasks.withType(Detekt).configureEach() { 321 // Custom detekt rules should be built before. 322 // See https://detekt.dev/docs/introduction/extensions#pitfalls 323 dependsOn(":components:tooling-detekt:assemble") 324 325 autoCorrect = true 326 327 exclude "**/build.gradle.kts" 328 exclude "**/build/**" 329 exclude "**/docs/**" 330 exclude "**/resources/**" 331 exclude "**/src/androidTest/**" 332 exclude "**/src/iosTest/**" 333 exclude "**/src/main/assets/extensions/**" 334 exclude "**/src/test/**" 335 exclude "**/test/src/**" 336 exclude "**/tmp/**" 337 exclude "**/tooling/fetch-tests/**" 338 } 339 340 // Apply same path exclusions as for the main task 341 tasks.withType(DetektCreateBaselineTask).configureEach() { 342 dependsOn(":components:browser-icons:updateBuiltInExtensionVersion") 343 dependsOn(":components:feature-accounts:updateBuiltInExtensionVersion") 344 dependsOn(":components:feature-readerview:updateBuiltInExtensionVersion") 345 dependsOn(":components:feature-search:updateAdsExtensionVersion") 346 dependsOn(":components:feature-search:updateCookiesExtensionVersion") 347 dependsOn(":components:samples-browser:updateBorderifyExtensionVersion") 348 dependsOn(":components:samples-browser:updateTestExtensionVersion") 349 dependsOn(":components:samples-compose-browser:updateBorderifyExtensionVersion") 350 dependsOn(":components:samples-compose-browser:updateTestExtensionVersion") 351 dependsOn(":components:tooling-detekt:assemble") 352 353 exclude "**/build.gradle.kts" 354 exclude "**/build/**" 355 exclude "**/docs/**" 356 exclude "**/resources/**" 357 exclude "**/src/androidTest/**" 358 exclude "**/src/iosTest/**" 359 exclude "**/src/main/assets/extensions/**" 360 exclude "**/src/test/**" 361 exclude "**/test/src/**" 362 exclude "**/tmp/**" 363 exclude "**/tooling/fetch-tests/**" 364 } 365 366 configurations { 367 ktlint 368 369 detektDependencies 370 } 371 372 dependencies { 373 ktlint(libs.ktlint) { 374 attributes { 375 attribute(Bundling.BUNDLING_ATTRIBUTE, getObjects().named(Bundling, Bundling.EXTERNAL)) 376 } 377 } 378 detektPlugins project(":components:tooling-detekt") 379 detekt libs.detekt.cli 380 381 detektDependencies libs.detekt.cli 382 } 383 384 tasks.register("ktlint", JavaExec) { 385 group = "verification" 386 description = "Check Kotlin code style." 387 classpath = configurations.ktlint 388 mainClass.set("com.pinterest.ktlint.Main") 389 args "components/**/*.kt" 390 args "samples/**/*.kt" 391 args "!**/build/**/*.kt" 392 args "buildSrc/**/*.kt" 393 args "--reporter=json,output=build/reports/ktlint/ktlint.json" 394 args "--reporter=plain" 395 } 396 397 tasks.register("ktlintFormat", JavaExec) { 398 group = "formatting" 399 description = "Fix Kotlin code style deviations." 400 classpath = configurations.ktlint 401 mainClass.set("com.pinterest.ktlint.Main") 402 args "-F" 403 args "components/**/*.kt" 404 args "samples/**/*.kt" 405 args "!**/build/**/*.kt" 406 args "buildSrc/**/*.kt" 407 args "--reporter=json,output=build/reports/ktlint/ktlintFormat.json" 408 args "--reporter=plain" 409 jvmArgs("--add-opens", "java.base/java.lang=ALL-UNNAMED") 410 } 411 412 tasks.register("listRepositories") { 413 def reposData = project.provider { 414 project.repositories.collect { repo -> 415 [name: repo.name, url: repo.url.toString()] 416 } 417 } 418 doLast { 419 println "Repositories:" 420 reposData.get().each { println "Name: " + it.name + "; url: " + it.url } 421 } 422 }