build.gradle (20018B)
1 import org.gradle.api.services.BuildServiceParameters 2 import org.tomlj.Toml 3 import org.tomlj.TomlParseResult 4 import org.tomlj.TomlTable 5 6 buildscript { 7 repositories { 8 mavenLocal() 9 10 gradle.mozconfig.substs.GRADLE_MAVEN_REPOSITORIES.each { repository -> 11 maven { 12 url = repository 13 if (gradle.mozconfig.substs.ALLOW_INSECURE_GRADLE_REPOSITORIES) { 14 allowInsecureProtocol = true 15 } 16 } 17 } 18 } 19 20 dependencies { 21 classpath libs.android.gradle.plugin 22 classpath libs.tomlj 23 24 // Used in mobile/android/fenix/app/build.gradle 25 classpath libs.androidx.benchmark.gradle 26 classpath libs.androidx.navigation.safeargs 27 classpath libs.osslicenses.plugin 28 classpath libs.mozilla.glean.gradle.plugin 29 } 30 } 31 32 plugins { 33 id 'ApkSizePlugin' 34 id "mozac.ConfigPlugin" 35 alias(libs.plugins.detekt) 36 alias(libs.plugins.kotlin.android) apply false 37 alias(libs.plugins.kotlin.compose) apply false 38 alias(libs.plugins.ksp) 39 alias(libs.plugins.spotless) 40 } 41 42 def tryInt = { string -> 43 if (string == null) { 44 return string 45 } 46 if (string.isInteger()) { 47 return string as Integer 48 } 49 return string 50 } 51 52 abstract class VerifyGleanVersionTask extends DefaultTask { 53 @InputFile 54 final RegularFileProperty source = project.objects.fileProperty().convention(project.layout.projectDirectory.file("Cargo.lock")) 55 56 @Input 57 String expectedVersion = project.ext.gleanVersion 58 59 @OutputFile 60 final RegularFileProperty outputFile = project.objects.fileProperty() 61 62 @TaskAction 63 void verifyGleanVersion() { 64 def foundVersion = getRustVersionFor(source.get().asFile, "glean") 65 if (expectedVersion != foundVersion) { 66 throw new GradleException("Mismatched Glean version, expected: '${expectedVersion}'," + 67 " found '${foundVersion}'") 68 } else { 69 logger.lifecycle("verifyGleanVersion> expected version matches found version '${foundVersion}'") 70 } 71 72 outputFile.get().asFile.text = "glean-${foundVersion}" 73 } 74 75 // Parses the Cargo.lock and returns the version for the given package name. 76 static String getRustVersionFor(file, packageName) { 77 String version = null; 78 TomlParseResult result = Toml.parse(file.getText()); 79 for (object in result.getArray("package").toList()) { 80 def table = (TomlTable) object 81 if (table.getString("name") == packageName) { 82 if (version != null) { 83 throw new GradleException("Multiple versions for '${packageName}' found." + 84 " Ensure '${packageName}' is only included once.") 85 } 86 version = table.getString("version") 87 } 88 } 89 return version 90 } 91 } 92 93 tasks.register("verifyGleanVersion", VerifyGleanVersionTask) { 94 outputFile.convention(layout.buildDirectory.file("glean/verifyGleanVersion.marker")) 95 } 96 97 allprojects { 98 // Expose the per-object-directory configuration to all projects. 99 ext { 100 mozconfig = gradle.mozconfig 101 topsrcdir = gradle.mozconfig.topsrcdir 102 topobjdir = gradle.mozconfig.topobjdir 103 104 gleanVersion = libs.versions.glean.get() // Verification done in verifyGleanVersion task 105 106 artifactSuffix = getArtifactSuffix() 107 versionName = getVersionName() 108 versionCode = computeVersionCode() 109 versionNumber = getVersionNumber() 110 buildId = getBuildId() 111 112 buildToolsVersion = mozconfig.substs.ANDROID_BUILD_TOOLS_VERSION 113 compileSdkMajorVersion = tryInt(mozconfig.substs.ANDROID_COMPILE_SDK_MAJOR) 114 compileSdkMinorVersion = tryInt(mozconfig.substs.ANDROID_COMPILE_SDK_MINOR) 115 minSdkVersion = tryInt(mozconfig.substs.MOZ_ANDROID_MIN_SDK_VERSION) 116 targetSdkVersion = tryInt(mozconfig.substs.ANDROID_TARGET_SDK) 117 manifestPlaceholders = [ 118 ANDROID_TARGET_SDK: mozconfig.substs.ANDROID_TARGET_SDK, 119 MOZ_ANDROID_MIN_SDK_VERSION: mozconfig.substs.MOZ_ANDROID_MIN_SDK_VERSION, 120 ] 121 } 122 123 afterEvaluate { 124 if (it.hasProperty('android')) { 125 android { 126 buildToolsVersion gradle.mozconfig.substs.ANDROID_BUILD_TOOLS_VERSION 127 testOptions { 128 unitTests.includeAndroidResources = true 129 } 130 } 131 } 132 } 133 134 repositories { 135 mavenLocal() 136 137 gradle.mozconfig.substs.GRADLE_MAVEN_REPOSITORIES.each { repository -> 138 maven { 139 url = repository 140 if (gradle.mozconfig.substs.ALLOW_INSECURE_GRADLE_REPOSITORIES) { 141 allowInsecureProtocol = true 142 } 143 } 144 } 145 } 146 147 // Use the semanticdb-javac and semanticdb-kotlinc plugins to generate semanticdb files for Searchfox 148 if (mozconfig.substs.ENABLE_MOZSEARCH_PLUGIN || mozconfig.substs.DOWNLOAD_ALL_GRADLE_DEPENDENCIES) { 149 def targetRoot = new File(topobjdir, "mozsearch_java_index") 150 151 afterEvaluate { 152 def addDependencyToConfigurationIfExists = { configurationName, dependency -> 153 def configuration = configurations.findByName(configurationName) 154 if (configuration != null) { 155 dependencies.add(configurationName, dependency) 156 } 157 } 158 159 addDependencyToConfigurationIfExists("compileOnly", libs.semanticdb.java) 160 addDependencyToConfigurationIfExists("testCompileOnly", libs.semanticdb.java) 161 addDependencyToConfigurationIfExists("androidTestCompileOnly", libs.semanticdb.java) 162 addDependencyToConfigurationIfExists("kotlinCompilerPluginClasspath", libs.semanticdb.kotlin) 163 } 164 165 tasks.withType(JavaCompile) { 166 options.compilerArgs += [ 167 "-Xplugin:semanticdb -sourceroot:${topsrcdir} -targetroot:${targetRoot}", 168 ] 169 } 170 171 tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile) { 172 compilerOptions.freeCompilerArgs.addAll([ 173 "-P", "plugin:semanticdb-kotlinc:sourceroot=${topsrcdir}".toString(), 174 "-P", "plugin:semanticdb-kotlinc:targetroot=${targetRoot}".toString(), 175 ]) 176 } 177 } 178 } 179 180 abstract class MozconfigService implements BuildService<MozconfigService.Params>, AutoCloseable { 181 interface Params extends BuildServiceParameters { 182 MapProperty<String, Object> getMozconfigParam() 183 } 184 185 void close() { 186 } 187 188 Object getMozconfig() { 189 return parameters.mozconfigParam.get() 190 } 191 } 192 193 // Non-official versions are like "61.0a1" or "61.0b1", where "a1" and "b1" 194 // are the milestone. 195 // This simply strips that off, leaving "61.0" in this example. 196 def getAppVersionWithoutMilestone() { 197 return project.ext.mozconfig.substs.MOZ_APP_VERSION.replaceFirst(/[ab][0-9]/, "") 198 } 199 200 // This converts MOZ_APP_VERSION into an integer 201 // version code. 202 // 203 // We take something like 58.1.2a1 and come out with 5800102 204 // This gives us 3 digits for the major number, and 2 digits 205 // each for the minor and build number. Beta and Release 206 // 207 // This must be synchronized with _compute_gecko_version(...) in /taskcluster/gecko_taskgraph/transforms/task.py 208 def computeVersionCode() { 209 String appVersion = getAppVersionWithoutMilestone() 210 211 // Split on the dot delimiter, e.g. 58.1.1a1 -> ["58, "1", "1a1"] 212 String[] parts = appVersion.split('\\.') 213 214 assert parts.size() == 2 || parts.size() == 3 215 216 // Major 217 int code = Integer.parseInt(parts[0]) * 100000 218 219 // Minor 220 code += Integer.parseInt(parts[1]) * 100 221 222 // Build 223 if (parts.size() == 3) { 224 code += Integer.parseInt(parts[2]) 225 } 226 227 return code; 228 } 229 230 def getVersionName() { 231 return "${mozconfig.substs.MOZ_APP_VERSION}-${mozconfig.substs.MOZ_UPDATE_CHANNEL}" 232 } 233 234 // Mimic Python: open(os.path.join(buildconfig.topobjdir, 'buildid.h')).readline().split()[2] 235 def getBuildId() { 236 return file("${topobjdir}/buildid.h").getText('utf-8').split()[2] 237 } 238 239 def getVersionNumber() { 240 def appVersion = getAppVersionWithoutMilestone() 241 def parts = appVersion.split('\\.') 242 def version = parts[0] + "." + parts[1] + "." + getBuildId() 243 def substs = project.ext.mozconfig.substs 244 if (!substs.MOZILLA_OFFICIAL && !substs.MOZ_ANDROID_FAT_AAR_ARCHITECTURES) { 245 // Use -SNAPSHOT versions locally to enable the local GeckoView substitution flow. 246 version += "-SNAPSHOT" 247 } 248 return version 249 } 250 251 def getArtifactSuffix() { 252 def substs = project.ext.mozconfig.substs 253 254 def suffix = "" 255 // Release artifacts don't specify the channel, for the sake of simplicity. 256 if (substs.MOZ_UPDATE_CHANNEL != 'release') { 257 suffix += "-${mozconfig.substs.MOZ_UPDATE_CHANNEL}" 258 } 259 260 return suffix 261 } 262 263 abstract class MachExec extends Exec { 264 def MachExec() { 265 // Bug 1543982: When invoking `mach build` recursively, the outer `mach 266 // build` itself modifies the environment, causing configure to run 267 // again. This tries to restore the environment that the outer `mach 268 // build` was invoked in. See the comment in 269 // $topsrcdir/settings.gradle. 270 project.ext.mozconfig.mozconfig.env.unmodified.each { k, v -> environment.remove(k) } 271 environment project.ext.mozconfig.orig_mozconfig.env.unmodified 272 environment 'MOZCONFIG', project.ext.mozconfig.substs.MOZCONFIG 273 } 274 275 static def geckoBinariesOnlyIf(task, mozconfig) { 276 // Never when Gradle was invoked within `mach build`. 277 if ('1' == System.env.GRADLE_INVOKED_WITHIN_MACH_BUILD) { 278 task.logger.lifecycle("Skipping task ${task.path} because: within `mach build`") 279 return false 280 } 281 282 // Never for official builds. 283 if (mozconfig.substs.MOZILLA_OFFICIAL) { 284 task.logger.lifecycle("Skipping task ${task.path} because: MOZILLA_OFFICIAL") 285 return false 286 } 287 if (mozconfig.substs.ENABLE_MOZSEARCH_PLUGIN) { 288 task.logger.lifecycle("Skipping task ${task.path} because: ENABLE_MOZSEARCH_PLUGIN") 289 return false 290 } 291 292 // Multi-l10n builds set `AB_CD=multi`, which isn't a valid locale, and 293 // `MOZ_CHROME_MULTILOCALE`. To avoid failures, if Gradle is invoked with 294 // either, we don't invoke Make at all; this allows a multi-locale omnijar 295 // to be consumed without modification. 296 if ('multi' == System.env.AB_CD || System.env.MOZ_CHROME_MULTILOCALE) { 297 task.logger.lifecycle("Skipping task ${task.path} because: AB_CD=multi") 298 return false 299 } 300 301 // Single-locale l10n repacks set `IS_LANGUAGE_REPACK=1` and handle resource 302 // and code generation themselves. 303 if ('1' == System.env.IS_LANGUAGE_REPACK) { 304 task.logger.lifecycle("Skipping task ${task.path} because: IS_LANGUAGE_REPACK") 305 return false 306 } 307 308 task.logger.lifecycle("Executing task ${task.path}") 309 return true 310 } 311 } 312 313 task machBuildFaster(type: MachExec) { 314 def mozconfigProvider = gradle.sharedServices.registerIfAbsent("mozconfig", MozconfigService) { 315 parameters.mozconfigParam.set(project.ext.mozconfig) 316 } 317 usesService(mozconfigProvider) 318 onlyIf { task -> MachExec.geckoBinariesOnlyIf(task, mozconfigProvider.get().getMozconfig()) } 319 320 workingDir "${topsrcdir}" 321 322 commandLine mozconfig.substs.PYTHON3 323 args "${topsrcdir}/mach" 324 args 'build' 325 args 'faster' 326 327 // Add `-v` if we're running under `--info` (or `--debug`). 328 if (project.logger.isEnabled(LogLevel.INFO)) { 329 args '-v' 330 } 331 332 standardOutput = System.out 333 errorOutput = System.err 334 } 335 336 task machStagePackage(type: MachExec) { 337 def mozconfigProvider = gradle.sharedServices.registerIfAbsent("mozconfig", MozconfigService) { 338 parameters.mozconfigParam.set(project.ext.mozconfig) 339 } 340 usesService(mozconfigProvider) 341 onlyIf { task -> MachExec.geckoBinariesOnlyIf(task, mozconfigProvider.get().getMozconfig()) } 342 dependsOn rootProject.machBuildFaster 343 344 workingDir "${topobjdir}" 345 346 commandLine mozconfig.substs.PYTHON3 347 args "${topsrcdir}/mach" 348 args 'build' 349 args 'stage-package' 350 351 outputs.file "${topobjdir}/dist/geckoview/assets/omni.ja" 352 353 outputs.file "${topobjdir}/dist/geckoview/assets/${mozconfig.substs.ANDROID_CPU_ARCH}/libxul.so" 354 outputs.file "${topobjdir}/dist/geckoview/lib/${mozconfig.substs.ANDROID_CPU_ARCH}/libmozglue.so" 355 356 // Force running `stage-package`. 357 outputs.upToDateWhen { false } 358 359 standardOutput = System.out 360 errorOutput = System.err 361 } 362 363 afterEvaluate { 364 subprojects { project -> 365 tasks.withType(JavaCompile) { 366 // Add compiler args for all code except third-party code. 367 options.compilerArgs += [ 368 // Turn on all warnings, except... 369 "-Xlint:all", 370 // Deprecation, because we do use deprecated API for compatibility. 371 "-Xlint:-deprecation", 372 // Serial, because we don't use Java serialization. 373 "-Xlint:-serial", 374 // Classfile, because javac has a bug with MethodParameters attributes 375 // with Java 7. https://bugs.openjdk.java.net/browse/JDK-8190452 376 "-Xlint:-classfile"] 377 378 // In GeckoView java projects only, turn all remaining warnings 379 // into errors unless marked by @SuppressWarnings. 380 def projectName = project.getName() 381 if (projectName.startsWith('geckoview') 382 || projectName == 'annotations' 383 || projectName == 'exoplayer2' 384 || projectName == 'messaging_example' 385 || projectName == 'port_messaging_example' 386 || projectName == 'test_runner' 387 ) { 388 options.compilerArgs += [ 389 "-Werror" 390 ] 391 } 392 } 393 394 project.configurations.configureEach { 395 // Dependencies can't depend on a different major version of Glean than A-C itself. 396 resolutionStrategy.eachDependency { details -> 397 if (details.requested.group == 'org.mozilla.telemetry' 398 && details.requested.name.contains('glean') ) { 399 def requested = details.requested.version.tokenize(".") 400 def defined = project.ext.gleanVersion.tokenize(".") 401 // Check the major version 402 if (requested[0] != defined[0]) { 403 throw new AssertionError("Cannot resolve to a single Glean version. Requested: ${details.requested.version}, A-C uses: ${libs.mozilla.glean}") 404 } else { 405 // Enforce that all (transitive) dependencies are using the defined Glean version 406 details.useVersion project.ext.gleanVersion 407 } 408 } 409 } 410 411 resolutionStrategy.capabilitiesResolution.withCapability("org.mozilla.telemetry:glean-native") { 412 def toBeSelected = candidates.find { it.id instanceof ModuleComponentIdentifier && it.id.module.contains('geckoview') } 413 if (toBeSelected != null) { 414 select(toBeSelected) 415 } 416 because 'use GeckoView Glean instead of standalone Glean' 417 } 418 } 419 } 420 } 421 422 apply plugin: 'idea' 423 424 idea { 425 project { 426 languageLevel = '1.8' 427 } 428 429 module { 430 // Object directories take a huge amount of time for IntelliJ to index. 431 // Exclude them. Convention is that object directories start with obj. 432 // IntelliJ is clever and will not exclude the parts of the object 433 // directory that are referenced, if there are any. In practice, 434 // indexing the entirety of the tree is taking too long, so exclude all 435 // but mobile/. 436 def topsrcdirURI = file(topsrcdir).toURI() 437 excludeDirs += files(file(topsrcdir) 438 .listFiles({it.isDirectory()} as FileFilter) 439 .collect({topsrcdirURI.relativize(it.toURI()).toString()}) // Relative paths. 440 .findAll({!it.equals('mobile/')})) 441 442 // If topobjdir is below topsrcdir, hide only some portions of that tree. 443 def topobjdirURI = file(topobjdir).toURI() 444 if (!topsrcdirURI.relativize(topobjdirURI).isAbsolute()) { 445 excludeDirs -= file(topobjdir) 446 excludeDirs += files(file(topobjdir).listFiles()) 447 excludeDirs -= file("${topobjdir}/gradle") 448 } 449 } 450 } 451 452 subprojects { project -> 453 // Perform spotless lint in GeckoView projects only. 454 // Sync with spotless_projects in mobile/android/geckoview/gradle.configure. 455 def projectName = project.getName() 456 if (projectName.startsWith('geckoview') 457 || projectName == 'annotations' 458 || projectName == 'exoplayer2' 459 || projectName == 'messaging_example' 460 || projectName == 'port_messaging_example' 461 || projectName == 'test_runner' 462 ) { 463 apply plugin: "com.diffplug.spotless" 464 465 spotless { 466 lineEndings = com.diffplug.spotless.LineEnding.UNIX 467 java { 468 target project.fileTree(project.projectDir) { 469 include '**/*.java' 470 exclude '**/thirdparty/**' 471 } 472 googleJavaFormat(libs.versions.google.java.format.get()) 473 } 474 kotlin { 475 target project.fileTree(project.projectDir) { 476 include '**/*.kt' 477 exclude '**/thirdparty/**' 478 } 479 ktlint("${libs.versions.ktlint.get()}").setEditorConfigPath("${topsrcdir}/mobile/android/geckoview/.editorconfig") 480 } 481 } 482 483 // Work around https://github.com/diffplug/spotless/issues/1958 by 484 // explicitly depending on Spotless plugin dependencies. 485 project.configurations { 486 googleJavaFormat 487 } 488 489 project.dependencies { 490 googleJavaFormat libs.google.java.format 491 } 492 } 493 494 afterEvaluate { 495 // Our vendored copy of exoplayer2 hits build failures when targeting Java 17. 496 // Given our intent to remove it in the near future, just leave it alone here. 497 if (it.hasProperty('android') && projectName != 'exoplayer2') { 498 kotlin { 499 jvmToolchain(config.jvmTargetCompatibility) 500 } 501 } 502 503 if (it.hasProperty('android') 504 && (project.projectDir.absolutePath.contains("android-components") 505 || projectName == "fenix" 506 || projectName == "focus-android") 507 ) { 508 dependencies { 509 lintChecks project.project(':components:tooling-lint') 510 } 511 512 android { 513 // Copied from subbproject's build.gradle 514 lint { 515 baseline = file("${project.projectDir}/lint-baseline.xml") 516 } 517 } 518 } 519 } 520 521 project.configurations.configureEach { 522 resolutionStrategy.capabilitiesResolution.withCapability("org.mozilla.telemetry:glean-native") { 523 def toBeSelected = candidates.find { 524 it.id instanceof ProjectComponentIdentifier && it.id.projectName.contains('geckoview') 525 } 526 if (toBeSelected != null) { 527 select(toBeSelected) 528 } 529 because 'use GeckoView Glean instead of standalone Glean' 530 } 531 } 532 } 533 534 tasks.register("lint-a-c") { 535 subprojects.each{ 536 if (it.tasks.findByName("lint") != null && it.projectDir.absolutePath.contains("android-components")) { 537 dependsOn it.tasks.named("lint") 538 } 539 } 540 }