no bug - Import translations from android-l10n r=release a=l10n CLOSED TREE
[gecko.git] / mobile / android / android-components / build.gradle
blobf1b46775c1a4f498ae9a3be2dbaf6d4f5338e18e
1 // Top-level build file where you can add configuration options common to all sub-projects/modules.
3 import io.gitlab.arturbosch.detekt.Detekt
4 import io.gitlab.arturbosch.detekt.DetektCreateBaselineTask
5 import org.gradle.internal.logging.text.StyledTextOutput.Style
6 import org.gradle.internal.logging.text.StyledTextOutputFactory
7 import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
8 import static org.gradle.api.tasks.testing.TestResult.ResultType
10 buildscript {
11     repositories {
12         gradle.mozconfig.substs.GRADLE_MAVEN_REPOSITORIES.each { repository ->
13             maven {
14                 url repository
15                 if (gradle.mozconfig.substs.ALLOW_INSECURE_GRADLE_REPOSITORIES) {
16                     allowInsecureProtocol = true
17                 }
18             }
19         }
20     }
22     dependencies {
23         classpath ComponentsDependencies.tools_androidgradle
24         classpath ComponentsDependencies.tools_kotlingradle
25     }
27     // Variables in plugins {} aren't directly supported. Hack around it by setting an
28     // intermediate variable which can pull from FenixDependencies.kt and be used later.
29     ext {
30         detekt_plugin = Versions.detekt
31         python_envs_plugin = Versions.python_envs_plugin
32         ksp_plugin = Versions.ksp_plugin
33     }
36 plugins {
37     id("io.gitlab.arturbosch.detekt").version("$detekt_plugin")
38     id("com.google.devtools.ksp").version("$ksp_plugin")
41 allprojects {
42     repositories {
43         gradle.mozconfig.substs.GRADLE_MAVEN_REPOSITORIES.each { repository ->
44             maven {
45                 url repository
46                 if (gradle.mozconfig.substs.ALLOW_INSECURE_GRADLE_REPOSITORIES) {
47                     allowInsecureProtocol = true
48                 }
49             }
50         }
52         maven {
53             url "${gradle.mozconfig.topobjdir}/gradle/maven"
54         }
55     }
58 subprojects {
59     apply plugin: 'jacoco'
61     // Enable Kotlin warnings as errors for all modules
62     tasks.withType(KotlinCompile).configureEach {
63         kotlinOptions.allWarningsAsErrors = true
64     }
66     project.configurations.configureEach {
67         // Dependencies can't depend on a different major version of Glean than A-C itself.
68         resolutionStrategy.eachDependency { details ->
69             if (details.requested.group == 'org.mozilla.telemetry'
70                     && details.requested.name.contains('glean') ) {
71                 def requested = details.requested.version.tokenize(".")
72                 def defined = Versions.mozilla_glean.tokenize(".")
73                 // Check the major version
74                 if (requested[0] != defined[0]) {
75                     throw new AssertionError("Cannot resolve to a single Glean version. Requested: ${details.requested.version}, A-C uses: ${Versions.mozilla_glean}")
76                 } else {
77                     // Enforce that all (transitive) dependencies are using the defined Glean version
78                     details.useVersion Versions.mozilla_glean
79                 }
80             }
81         }
83         resolutionStrategy.capabilitiesResolution.withCapability("org.mozilla.telemetry:glean-native") {
84             def toBeSelected = candidates.find { it.id instanceof ModuleComponentIdentifier && it.id.module.contains('geckoview') }
85             if (toBeSelected != null) {
86                 select(toBeSelected)
87             }
88             because 'use GeckoView Glean instead of standalone Glean'
89         }
90     }
93     // Allow local appservices substitution in each subproject.
94     def appServicesSrcDir = null
95     if (gradle.hasProperty('localProperties.autoPublish.application-services.dir')) {
96         appServicesSrcDir = gradle.getProperty('localProperties.autoPublish.application-services.dir')
97     } else if (gradle.hasProperty('localProperties.branchBuild.application-services.dir')) {
98         appServicesSrcDir = gradle.getProperty('localProperties.branchBuild.application-services.dir')
99     }
100     if (appServicesSrcDir) {
101         if (appServicesSrcDir.startsWith("/")) {
102             apply from: "${appServicesSrcDir}/build-scripts/substitute-local-appservices.gradle"
103         } else {
104             apply from: "${rootProject.projectDir}/${appServicesSrcDir}/build-scripts/substitute-local-appservices.gradle"
105         }
106     }
108     // Allow local Glean substitution in each subproject.
109     if (gradle.hasProperty('localProperties.autoPublish.glean.dir')) {
110         ext.gleanSrcDir = gradle."localProperties.autoPublish.glean.dir"
111         apply from: "${rootProject.projectDir}/${gleanSrcDir}/build-scripts/substitute-local-glean.gradle"
112     }
114     if (gradle.hasProperty('localProperties.dependencySubstitutions.geckoviewTopsrcdir')) {
115         if (gradle.hasProperty('localProperties.dependencySubstitutions.geckoviewTopobjdir')) {
116             ext.topobjdir = gradle."localProperties.dependencySubstitutions.geckoviewTopobjdir"
117         }
118         ext.topsrcdir = gradle."localProperties.dependencySubstitutions.geckoviewTopsrcdir"
119         apply from: "${topsrcdir}/substitute-local-geckoview.gradle"
120     }
122     afterEvaluate {
123         if (it.hasProperty('android')) {
124             jacoco {
125                 toolVersion = Versions.jacoco
126             }
128             // Format test output
129             tasks.matching {it instanceof Test}.configureEach() {
130                 systemProperty "robolectric.logging", "stdout"
131                 systemProperty "logging.test-mode", "true"
132                 systemProperty "javax.net.ssl.trustStoreType", "JKS"
134                 testLogging.events = []
136                 def out = services.get(StyledTextOutputFactory).create("an-ouput")
138                 beforeSuite { descriptor ->
139                     if (descriptor.getClassName() != null) {
140                         out.style(Style.Header).println("\nSUITE: " + descriptor.getClassName())
141                     }
142                 }
144                 beforeTest { descriptor ->
145                     out.style(Style.Description).println("  TEST: " + descriptor.getName())
146                 }
148                 onOutput { descriptor, event ->
149                     logger.lifecycle("    " + event.message.trim())
150                 }
152                 afterTest { descriptor, result ->
153                     switch (result.getResultType()) {
154                         case ResultType.SUCCESS:
155                             out.style(Style.Success).println("  SUCCESS")
156                             break
158                         case ResultType.FAILURE:
159                             def testId = descriptor.getClassName() + "." + descriptor.getName()
160                             out.style(Style.Failure).println("  TEST-UNEXPECTED-FAIL | " + testId + " | " + result.getException())
161                             break
163                         case ResultType.SKIPPED:
164                             out.style(Style.Info).println("  SKIPPED")
165                             break
166                     }
167                     logger.lifecycle("")
168                 }
169             }
171             dependencies {
172                 lintChecks project(':tooling-lint')
173             }
175             kotlin {
176                 jvmToolchain(config.jvmTargetCompatibility)
177             }
179             android {
180                 testOptions {
181                     unitTests {
182                         includeAndroidResources = true
183                     }
184                 }
186                 packagingOptions {
187                     resources {
188                         excludes += ['META-INF/atomicfu.kotlin_module', 'META-INF/AL2.0', 'META-INF/LGPL2.1']
189                         // Required dependencies using byte-buddy; remove after this is
190                         // fixed by: https://issuetracker.google.com/issues/170131605
191                         excludes.add("META-INF/licenses/ASM")
193                         pickFirsts += ['win32-x86-64/attach_hotspot_windows.dll', 'win32-x86/attach_hotspot_windows.dll']
194                     }
195                 }
197                 androidResources {
198                     ignoreAssetsPattern "manifest.template.json"
199                 }
201                 tasks.withType(KotlinCompile).configureEach {
202                     kotlinOptions.freeCompilerArgs += ["-opt-in=kotlin.RequiresOptIn"]
203                 }
204             }
206             if (project.hasProperty("coverage") && project.name != "support-test") {
207                 android.buildTypes.all { buildType ->
208                     tasks.withType(Test).configureEach() {
209                         jacoco {
210                             includeNoLocationClasses = true
211                             excludes = ['jdk.internal.*']
212                         }
214                         finalizedBy { "jacoco${buildType.name.capitalize()}TestReport" }
215                     }
217                     tasks.register("jacoco${buildType.name.capitalize()}TestReport", JacocoReport) {
218                         reports {
219                             xml.required = true
220                             html.required = true
221                         }
223                         def fileFilter = ['**/R.class', '**/R$*.class', '**/BuildConfig.*', '**/Manifest*.*',
224                                           '**/*Test*.*', 'android/**/*.*', '**/*$[0-9].*']
225                         def kotlinDebugTree = fileTree(dir: "$project.layout.buildDirectory/tmp/kotlin-classes/${buildType.name}", excludes: fileFilter)
226                         def javaDebugTree = fileTree(dir: "$project.layout.buildDirectory/intermediates/classes/${buildType.name}", excludes: fileFilter)
227                         def mainSrc = "$project.projectDir/src/main/java"
229                         sourceDirectories.setFrom(files([mainSrc]))
230                         classDirectories.setFrom(files([kotlinDebugTree, javaDebugTree]))
231                         getExecutionData().setFrom(fileTree(project.layout.buildDirectory).include([
232                                 "jacoco/test${buildType.name.capitalize()}UnitTest.exec"
233                         ]))
234                     }
235                 }
237                 android {
238                     buildTypes {
239                         debug {
240                             testCoverageEnabled true
241                         }
242                     }
243                 }
244             }
245         }
246     }
248     tasks.withType(KotlinCompile).configureEach {
249         // Translate Kotlin messages like "w: ..." and "e: ..." into
250         // "...: warning: ..." and "...: error: ...", to make Treeherder understand.
251         def listener = {
252             if (it.startsWith("e: warnings found")) {
253                 return
254             }
256             if (it.startsWith('w: ') || it.startsWith('e: ')) {
257                 def matches = (it =~ /([ew]): (.+):(\d+):(\d+) (.*)/)
258                 if (!matches) {
259                     logger.quiet "kotlinc message format has changed!"
260                     if (it.startsWith('w: ')) {
261                         // For warnings, don't continue because we don't want to throw an
262                         // exception. For errors, we want the exception so that the new error
263                         // message format gets translated properly.
264                         return
265                     }
266                 }
267                 def (_, type, file, line, column, message) = matches[0]
268                 type = (type == 'w') ? 'warning' : 'error'
269                 // Use logger.lifecycle, which does not go through stderr again.
270                 logger.lifecycle "$file:$line:$column: $type: $message"
271             }
272         } as StandardOutputListener
274         doFirst {
275             logging.addStandardErrorListener(listener)
276         }
277         doLast {
278             logging.removeStandardErrorListener(listener)
279         }
280     }
283 if (findProject(":geckoview") == null) {
284     // Avoid adding this task if it already exists in a different root project.
285     tasks.register("clean", Delete) {
286         delete rootProject.layout.buildDirectory
287     }
290 detekt {
291     input = files("$projectDir/components", "$projectDir/buildSrc", "$projectDir/samples")
292     config = files("$projectDir/config/detekt.yml")
293     baseline = file("$projectDir/config/detekt-baseline.xml")
295     reports {
296         html {
297             enabled = true
298             destination = file("$projectDir/build/reports/detekt.html")
299         }
300         xml {
301             enabled = false
302         }
303         txt {
304             enabled = false
305         }
306     }
309 tasks.withType(Detekt).configureEach() {
310     // Custom detekt rules should be build before
311     // See https://arturbosch.github.io/detekt/extensions.html#pitfalls
312     dependsOn(":tooling-detekt:assemble")
314     autoCorrect = true
316     exclude "**/build.gradle.kts"
317     exclude "**/src/androidTest/**"
318     exclude "**/src/iosTest/**"
319     exclude "**/src/test/**"
320     exclude "**/test/src/**"
321     exclude "**/build/**"
322     exclude "**/resources/**"
323     exclude "**/tmp/**"
324     exclude "**/tooling/fetch/tests/**"
325     exclude "**/tooling/fetch-tests/**"
326     exclude "**/src/main/assets/extensions/**"
327     exclude "**/docs/**"
330 // Apply same path exclusions as for the main task
331 tasks.withType(DetektCreateBaselineTask).configureEach() {
332     exclude "**/src/androidTest/**"
333     exclude "**/src/test/**"
334     exclude "**/test/src/**"
335     exclude "**/build/**"
336     exclude "**/resources/**"
337     exclude "**/tmp/**"
338     exclude "**/tooling/fetch/tests/**"
339     exclude "**/tooling/fetch-tests/**"
342 configurations {
343     ktlint
346 dependencies {
347     ktlint("com.pinterest:ktlint:${Versions.ktlint}") {
348         attributes {
349             attribute(Bundling.BUNDLING_ATTRIBUTE, getObjects().named(Bundling, Bundling.EXTERNAL))
350         }
351     }
352     detektPlugins project(":tooling-detekt")
355 tasks.register("ktlint", JavaExec) {
356     group = "verification"
357     description = "Check Kotlin code style."
358     classpath = configurations.ktlint
359     mainClass.set("com.pinterest.ktlint.Main")
360     args "components/**/*.kt" , "samples/**/*.kt", "!**/build/**/*.kt", "buildSrc/**/*.kt", "--baseline=ktlint-baseline.xml"
363 tasks.register("ktlintFormat", JavaExec) {
364     group = "formatting"
365     description = "Fix Kotlin code style deviations."
366     classpath = configurations.ktlint
367     mainClass.set("com.pinterest.ktlint.Main")
368     args "-F", "components/**/*.kt" , "samples/**/*.kt", "!**/build/**/*.kt", "buildSrc/**/*.kt", "--baseline=ktlint-baseline.xml"
369     jvmArgs("--add-opens", "java.base/java.lang=ALL-UNNAMED")
372 tasks.register("listRepositories") {
373     doLast {
374         println "Repositories:"
375         project.repositories.each { println "Name: " + it.name + "; url: " + it.url }
376    }
379 tasks.register("testToolsDir", Exec) {
380     group = "verification"
381     description = "Run tests in the tools/ directory."
382     workingDir = "tools"
383     commandLine = ["python3", "test_list_compatible_dependency_versions.py"]