Backed out changeset 2d88daa2e4bc (bug 1899848) for conflicting with the backout...
[gecko.git] / build.gradle
blob859609b3d07b27e593638ca2c0765e92f9465337
1 import org.tomlj.Toml
2 import org.tomlj.TomlParseResult
3 import org.tomlj.TomlTable
5 buildscript {
6     repositories {
7         gradle.mozconfig.substs.GRADLE_MAVEN_REPOSITORIES.each { repository ->
8             maven {
9                 url repository
10                 if (gradle.mozconfig.substs.ALLOW_INSECURE_GRADLE_REPOSITORIES) {
11                     allowInsecureProtocol = true
12                 }
13             }
14         }
15     }
17     ext {
18         detekt_plugin = Versions.detekt
19         python_envs_plugin = Versions.python_envs_plugin
20         ksp_plugin = Versions.ksp_plugin
22         // Used in mobile/android/fenix/app/build.gradle
23         protobuf_plugin = FenixVersions.protobuf_plugin
24     }
26     dependencies {
27         classpath 'org.mozilla.apilint:apilint:0.5.2'
28         classpath 'com.android.tools.build:gradle:8.0.2'  // Bug 1881001 for using AC version
29         classpath 'org.apache.commons:commons-exec:1.3'
30         classpath 'com.diffplug.spotless:spotless-plugin-gradle:6.25.0'
31         classpath 'org.tomlj:tomlj:1.1.0'
32         classpath ComponentsDependencies.tools_kotlingradle
34         // Used in mobile/android/fenix/app/build.gradle
35         classpath ComponentsDependencies.androidx_safeargs
36         classpath FenixDependencies.osslicenses_plugin
37         classpath FenixDependencies.tools_benchmarkgradle
38         classpath "org.mozilla.telemetry:glean-gradle-plugin:${Versions.mozilla_glean}"
39         classpath "${ApplicationServicesConfig.groupId}:tooling-nimbus-gradle:${ApplicationServicesConfig.version}"
40     }
43 plugins {
44     id("io.gitlab.arturbosch.detekt").version("$detekt_plugin")
45     id("com.google.devtools.ksp").version("$ksp_plugin")
48 def tryInt = { string ->
49     if (string == null) {
50         return string
51     }
52     if (string.isInteger()) {
53         return string as Integer
54     }
55     return string
58 // Parses the Cargo.lock and returns the version for the given package name.
59 def getRustVersionFor(packageName) {
60     String version = null;
61     TomlParseResult result = Toml.parse(file("Cargo.lock").getText());
62     for (object in result.getArray("package").toList()) {
63         def table = (TomlTable) object
64         if (table.getString("name") == packageName) {
65             if (version != null) {
66                 throw new StopExecutionException("Multiple versions for '${packageName}' found." +
67                         " Ensure '${packageName}' is only included once.")
68             }
69             version = table.getString("version")
70         }
71     }
72     return version
75 def now() {
76     Instant now = Instant.now();
77     return now.getEpochSecond() + now.getNano() / 1_000_000_000L;
80 allprojects {
81     def shouldPrintBuildStatus =
82             System.getenv("MACH") && !System.getenv("NO_BUILDSTATUS_MESSAGES")
84     if (shouldPrintBuildStatus) {
85         // Adding new line before each line to make sure they're dumped as a separate line.
86         // Add profile marker to the start of the project evaluation
87         project.beforeEvaluate {
88             println "\nBUILDSTATUS ${now()} START_gradle:evaluate-project ${project.name}"
89         }
91         // Add profile marker to the end of the project evaluation
92         project.afterEvaluate {
93             println "\nBUILDSTATUS ${now()} END_gradle:evaluate-project ${project.name}"
94         }
96         tasks.configureEach { task ->
97             // Add profile marker to the start of the gradle task
98             task.doFirst {
99                 println "\nBUILDSTATUS ${now()} START_gradle-task ${project.name}:${task.name}"
100             }
102             // Add profile marker to the end of the gradle task
103             task.doLast {
104                 println "\nBUILDSTATUS ${now()} END_gradle-task ${project.name}:${task.name}"
105             }
106         }
107     }
109     // Expose the per-object-directory configuration to all projects.
110     ext {
111         mozconfig = gradle.mozconfig
112         topsrcdir = gradle.mozconfig.topsrcdir
113         topobjdir = gradle.mozconfig.topobjdir
115         gleanVersion = Versions.mozilla_glean
116         if (gleanVersion != getRustVersionFor("glean")) {
117           throw new StopExecutionException("Mismatched Glean version, expected: ${gleanVersion}," +
118               " found ${getRustVersionFor("glean")}")
119         }
121         artifactSuffix = getArtifactSuffix()
122         versionName = getVersionName()
123         versionCode = computeVersionCode()
124         versionNumber = getVersionNumber()
125         buildId = getBuildId()
127         buildToolsVersion = mozconfig.substs.ANDROID_BUILD_TOOLS_VERSION
128         compileSdkVersion = tryInt(mozconfig.substs.ANDROID_TARGET_SDK)
129         targetSdkVersion = tryInt(mozconfig.substs.ANDROID_TARGET_SDK)
130         minSdkVersion = tryInt(mozconfig.substs.MOZ_ANDROID_MIN_SDK_VERSION)
131         manifestPlaceholders = [
132             ANDROID_PACKAGE_NAME: mozconfig.substs.ANDROID_PACKAGE_NAME,
133             ANDROID_TARGET_SDK: mozconfig.substs.ANDROID_TARGET_SDK,
134             MOZ_ANDROID_MIN_SDK_VERSION: mozconfig.substs.MOZ_ANDROID_MIN_SDK_VERSION,
135         ]
136     }
138     repositories {
139         gradle.mozconfig.substs.GRADLE_MAVEN_REPOSITORIES.each { repository ->
140             maven {
141                 url repository
142                 if (gradle.mozconfig.substs.ALLOW_INSECURE_GRADLE_REPOSITORIES) {
143                     allowInsecureProtocol = true
144                 }
145             }
146         }
147     }
149     // Use the semanticdb-javac and semanticdb-kotlinc plugins to generate semanticdb files for Searchfox
150     if (mozconfig.substs.ENABLE_MOZSEARCH_PLUGIN || mozconfig.substs.DOWNLOAD_ALL_GRADLE_DEPENDENCIES) {
151         def targetRoot = new File(topobjdir, "mozsearch_java_index")
152         def semanticdbJavacVersion = "com.sourcegraph:semanticdb-javac:0.9.10"
153         def semanticdbKotlincVersion = "com.sourcegraph:semanticdb-kotlinc:0.4.0"
155         afterEvaluate {
156             def addDependencyToConfigurationIfExists = { configurationName, dependency ->
157                 def configuration = configurations.findByName(configurationName)
158                 if (configuration != null) {
159                     dependencies.add(configurationName, dependency)
160                 }
161             }
163             addDependencyToConfigurationIfExists("compileOnly", semanticdbJavacVersion)
164             addDependencyToConfigurationIfExists("testCompileOnly", semanticdbJavacVersion)
165             addDependencyToConfigurationIfExists("androidTestCompileOnly", semanticdbJavacVersion)
166             addDependencyToConfigurationIfExists("kotlinCompilerPluginClasspath", semanticdbKotlincVersion)
167         }
169         tasks.withType(JavaCompile) {
170             options.compilerArgs += [
171                 "-Xplugin:semanticdb -sourceroot:${topsrcdir} -targetroot:${targetRoot}",
172             ]
173         }
175         tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile) {
176             compilerOptions.freeCompilerArgs.addAll([
177                 "-P", "plugin:semanticdb-kotlinc:sourceroot=${topsrcdir}",
178                 "-P", "plugin:semanticdb-kotlinc:targetroot=${targetRoot}",
179             ])
180         }
181     }
183     task downloadDependencies() {
184         description 'Download all dependencies to the Gradle cache'
185         doLast {
186             configurations.each { configuration ->
187                 if (configuration.canBeResolved) {
188                     configuration.allDependencies.each { dependency ->
189                         try {
190                             configuration.files(dependency)
191                         } catch(e) {
192                             println("Could not resolve ${configuration.name} -> ${dependency.name}")
193                             println(" > ${e.message}")
194                             if (e.cause) {
195                                 println(" >> ${e.cause}")
196                                 if (e.cause.cause) {
197                                     println(" >> ${e.cause.cause}")
198                                 }
199                             }
200                             println("")
201                         }
202                     }
203                 }
204             }
205         }
206     }
209 buildDir "${topobjdir}/gradle/build"
211 // A stream that processes bytes line by line, prepending a tag before sending
212 // each line to Gradle's logging.
213 class TaggedLogOutputStream extends org.apache.commons.exec.LogOutputStream {
214     String tag
215     Logger logger
217     TaggedLogOutputStream(tag, logger) {
218         this.tag = tag
219         this.logger = logger
220     }
222     void processLine(String line, int level) {
223         logger.lifecycle("${this.tag} ${line}")
224     }
227 ext.geckoBinariesOnlyIf = { task ->
228     // Never when Gradle was invoked within `mach build`.
229     if ('1' == System.env.GRADLE_INVOKED_WITHIN_MACH_BUILD) {
230         rootProject.logger.lifecycle("Skipping task ${task.path} because: within `mach build`")
231         return false
232     }
234     // Never for official builds.
235     if (mozconfig.substs.MOZILLA_OFFICIAL) {
236         rootProject.logger.lifecycle("Skipping task ${task.path} because: MOZILLA_OFFICIAL")
237         return false
238     }
240     // Multi-l10n builds set `AB_CD=multi`, which isn't a valid locale, and
241     // `MOZ_CHROME_MULTILOCALE`.  To avoid failures, if Gradle is invoked with
242     // either, we don't invoke Make at all; this allows a multi-locale omnijar
243     // to be consumed without modification.
244     if ('multi' == System.env.AB_CD || System.env.MOZ_CHROME_MULTILOCALE) {
245         rootProject.logger.lifecycle("Skipping task ${task.path} because: AB_CD=multi")
246         return false
247     }
249     // Single-locale l10n repacks set `IS_LANGUAGE_REPACK=1` and handle resource
250     // and code generation themselves.
251     if ('1' == System.env.IS_LANGUAGE_REPACK) {
252         rootProject.logger.lifecycle("Skipping task ${task.path} because: IS_LANGUAGE_REPACK")
253         return false
254     }
256     rootProject.logger.lifecycle("Executing task ${task.path}")
257     return true
260 // Non-official versions are like "61.0a1", where "a1" is the milestone.
261 // This simply strips that off, leaving "61.0" in this example.
262 def getAppVersionWithoutMilestone() {
263     return project.ext.mozconfig.substs.MOZ_APP_VERSION.replaceFirst(/a[0-9]/, "")
266 // This converts MOZ_APP_VERSION into an integer
267 // version code.
269 // We take something like 58.1.2a1 and come out with 5800102
270 // This gives us 3 digits for the major number, and 2 digits
271 // each for the minor and build number. Beta and Release
273 // This must be synchronized with _compute_gecko_version(...) in /taskcluster/gecko_taskgraph/transforms/task.py
274 def computeVersionCode() {
275     String appVersion = getAppVersionWithoutMilestone()
277     // Split on the dot delimiter, e.g. 58.1.1a1 -> ["58, "1", "1a1"]
278     String[] parts = appVersion.split('\\.')
280     assert parts.size() == 2 || parts.size() == 3
282     // Major
283     int code = Integer.parseInt(parts[0]) * 100000
285     // Minor
286     code += Integer.parseInt(parts[1]) * 100
288     // Build
289     if (parts.size() == 3) {
290         code += Integer.parseInt(parts[2])
291     }
293     return code;
296 def getVersionName() {
297     return "${mozconfig.substs.MOZ_APP_VERSION}-${mozconfig.substs.MOZ_UPDATE_CHANNEL}"
300 // Mimic Python: open(os.path.join(buildconfig.topobjdir, 'buildid.h')).readline().split()[2]
301 def getBuildId() {
302     return file("${topobjdir}/buildid.h").getText('utf-8').split()[2]
305 def getVersionNumber() {
306     def appVersion = getAppVersionWithoutMilestone()
307     def parts = appVersion.split('\\.')
308     def version = parts[0] + "." + parts[1] + "." + getBuildId()
309     def substs = project.ext.mozconfig.substs
310     if (!substs.MOZILLA_OFFICIAL && !substs.MOZ_ANDROID_FAT_AAR_ARCHITECTURES) {
311         // Use -SNAPSHOT versions locally to enable the local GeckoView substitution flow.
312         version += "-SNAPSHOT"
313     }
314     return version
317 def getArtifactSuffix() {
318     def substs = project.ext.mozconfig.substs
320     def suffix = ""
321     // Release artifacts don't specify the channel, for the sake of simplicity.
322     if (substs.MOZ_UPDATE_CHANNEL != 'release') {
323         suffix += "-${mozconfig.substs.MOZ_UPDATE_CHANNEL}"
324     }
326     return suffix
329 class MachExec extends Exec {
330     def MachExec() {
331         // Bug 1543982: When invoking `mach build` recursively, the outer `mach
332         // build` itself modifies the environment, causing configure to run
333         // again.  This tries to restore the environment that the outer `mach
334         // build` was invoked in.  See the comment in
335         // $topsrcdir/settings.gradle.
336         project.ext.mozconfig.mozconfig.env.unmodified.each { k, v -> environment.remove(k) }
337         environment project.ext.mozconfig.orig_mozconfig.env.unmodified
338         environment 'MOZCONFIG', project.ext.mozconfig.substs.MOZCONFIG
339     }
342 task machBuildFaster(type: MachExec) {
343     onlyIf rootProject.ext.geckoBinariesOnlyIf
345     workingDir "${topsrcdir}"
347     commandLine mozconfig.substs.PYTHON3
348     args "${topsrcdir}/mach"
349     args 'build'
350     args 'faster'
352     // Add `-v` if we're running under `--info` (or `--debug`).
353     if (project.logger.isEnabled(LogLevel.INFO)) {
354         args '-v'
355     }
357     // `path` is like `:machBuildFaster`.
358     standardOutput = new TaggedLogOutputStream("${path}>", logger)
359     errorOutput = standardOutput
362 task machStagePackage(type: MachExec) {
363     onlyIf rootProject.ext.geckoBinariesOnlyIf
365     dependsOn rootProject.machBuildFaster
367     workingDir "${topobjdir}"
369     // We'd prefer this to be a `mach` invocation, but `mach build
370     // mobile/android/installer/stage-package` doesn't work as expected.
371     commandLine mozconfig.substs.GMAKE
372     args '-C'
373     args "${topobjdir}/mobile/android/installer"
374     args 'stage-package'
376     outputs.file "${topobjdir}/dist/geckoview/assets/omni.ja"
378     outputs.file "${topobjdir}/dist/geckoview/assets/${mozconfig.substs.ANDROID_CPU_ARCH}/libxul.so"
379     outputs.file "${topobjdir}/dist/geckoview/lib/${mozconfig.substs.ANDROID_CPU_ARCH}/libmozglue.so"
381     // Force running `stage-package`.
382     outputs.upToDateWhen { false }
384     // `path` is like `:machStagePackage`.
385     standardOutput = new TaggedLogOutputStream("${path}>", logger)
386     errorOutput = standardOutput
389 afterEvaluate {
390     subprojects { project ->
391         tasks.withType(JavaCompile) {
392             // Add compiler args for all code except third-party code.
393             options.compilerArgs += [
394                 // Turn on all warnings, except...
395                 "-Xlint:all",
396                 // Deprecation, because we do use deprecated API for compatibility.
397                 "-Xlint:-deprecation",
398                 // Serial, because we don't use Java serialization.
399                 "-Xlint:-serial",
400                 // Classfile, because javac has a bug with MethodParameters attributes
401                 // with Java 7. https://bugs.openjdk.java.net/browse/JDK-8190452
402                 "-Xlint:-classfile"]
404             // In GeckoView java projects only, turn all remaining warnings
405             // into errors unless marked by @SuppressWarnings.
406             def projectName = project.getName()
407             if (projectName.startsWith('geckoview')
408                 || projectName == 'annotations'
409                 || projectName == 'exoplayer2'
410                 || projectName == 'messaging_example'
411                 || projectName == 'port_messaging_example'
412                 || projectName == 'test_runner'
413             ) {
414                 options.compilerArgs += [
415                         "-Werror"
416                 ]
417             }
418         }
420     }
423 apply plugin: 'idea'
425 idea {
426     project {
427         languageLevel = '1.8'
428     }
430     module {
431         // Object directories take a huge amount of time for IntelliJ to index.
432         // Exclude them.  Convention is that object directories start with obj.
433         // IntelliJ is clever and will not exclude the parts of the object
434         // directory that are referenced, if there are any.  In practice,
435         // indexing the entirety of the tree is taking too long, so exclude all
436         // but mobile/.
437         def topsrcdirURI = file(topsrcdir).toURI()
438         excludeDirs += files(file(topsrcdir)
439             .listFiles({it.isDirectory()} as FileFilter)
440             .collect({topsrcdirURI.relativize(it.toURI()).toString()}) // Relative paths.
441             .findAll({!it.equals('mobile/')}))
443         // If topobjdir is below topsrcdir, hide only some portions of that tree.
444         def topobjdirURI = file(topobjdir).toURI()
445         if (!topsrcdirURI.relativize(topobjdirURI).isAbsolute()) {
446             excludeDirs -= file(topobjdir)
447             excludeDirs += files(file(topobjdir).listFiles())
448             excludeDirs -= file("${topobjdir}/gradle")
449         }
450     }
453 subprojects { project ->
454     // Perform spotless lint in GeckoView projects only.
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"
465         spotless {
466             java {
467                 target project.fileTree(project.projectDir) {
468                     include '**/*.java'
469                     exclude '**/thirdparty/**'
470                 }
471                 googleJavaFormat('1.17.0')
472             }
473             kotlin {
474                 target project.fileTree(project.projectDir) {
475                     include '**/*.kt'
476                     exclude '**/thirdparty/**'
477                 }
478                 ktlint('0.49.1')
479             }
480         }
481     } else {
482         afterEvaluate {
483             // Set the source and target compatibility for non-GeckoView projects only.
484             if (it.hasProperty('android')) {
485                 android {
486                     compileOptions {
487                         sourceCompatibility JavaVersion.VERSION_17
488                         targetCompatibility JavaVersion.VERSION_17
489                     }
490                 }
491             }
492         }
493     }
495     project.configurations.configureEach {
496         resolutionStrategy.capabilitiesResolution.withCapability("org.mozilla.telemetry:glean-native") {
497             def toBeSelected = candidates.find {
498                 it.id instanceof ProjectComponentIdentifier && it.id.projectName.contains('geckoview')
499             }
500             if (toBeSelected != null) {
501                 select(toBeSelected)
502             }
503             because 'use GeckoView Glean instead of standalone Glean'
504         }
505     }