This repository has been archived by the owner on Feb 20, 2023. It is now read-only.
/
build.gradle
620 lines (523 loc) · 24.6 KB
/
build.gradle
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
plugins {
id "com.jetbrains.python.envs" version "0.0.26"
}
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'jacoco'
apply from: "$project.rootDir/automation/gradle/versionCode.gradle"
apply plugin: 'androidx.navigation.safeargs.kotlin'
apply plugin: 'com.google.android.gms.oss-licenses-plugin'
import com.android.build.gradle.internal.tasks.AppPreBuildTask
import org.gradle.internal.logging.text.StyledTextOutput.Style
import org.gradle.internal.logging.text.StyledTextOutputFactory
import static org.gradle.api.tasks.testing.TestResult.ResultType
import com.android.build.OutputFile
android {
compileSdkVersion 28
defaultConfig {
applicationId "org.mozilla"
minSdkVersion Config.minSdkVersion
targetSdkVersion Config.targetSdkVersion
versionCode 1
versionName Config.generateDebugVersionName()
vectorDrawables.useSupportLibrary = true
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
testInstrumentationRunnerArguments clearPackageData: 'true'
manifestPlaceholders.isRaptorEnabled = "false"
resValue "bool", "IS_DEBUG", "false"
buildConfigField "boolean", "USE_RELEASE_VERSIONING", "false"
}
def releaseTemplate = {
shrinkResources true
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
matchingFallbacks = ['release'] // Use on the "release" build type in dependencies (AARs)
}
buildTypes {
debug {
shrinkResources false
minifyEnabled false
applicationIdSuffix ".fenix.debug"
manifestPlaceholders.isRaptorEnabled = "true"
resValue "bool", "IS_DEBUG", "true"
pseudoLocalesEnabled true
}
forPerformanceTest releaseTemplate >> { // the ">>" concatenates the raptor-specific options with the template
manifestPlaceholders.isRaptorEnabled = "true"
applicationIdSuffix ".fenix.performancetest"
debuggable true
}
fenixNightlyLegacy releaseTemplate >> {
applicationIdSuffix ".fenix"
buildConfigField "boolean", "USE_RELEASE_VERSIONING", "true"
}
fenixNightly releaseTemplate >> {
applicationIdSuffix ".fenix.nightly"
buildConfigField "boolean", "USE_RELEASE_VERSIONING", "true"
}
fenixBeta releaseTemplate >> {
applicationIdSuffix ".fenix.beta"
buildConfigField "boolean", "USE_RELEASE_VERSIONING", "true"
}
fenixProduction releaseTemplate >> {
applicationIdSuffix ".fenix"
buildConfigField "boolean", "USE_RELEASE_VERSIONING", "true"
}
fennecProduction releaseTemplate >> {
buildConfigField "boolean", "USE_RELEASE_VERSIONING", "true"
applicationIdSuffix ".firefox"
manifestPlaceholders = [
// This release type is meant to replace Firefox (Release channel) and therefore needs to inherit
// its sharedUserId for all eternity. See:
// https://searchfox.org/mozilla-central/search?q=moz_android_shared_id&case=false®exp=false&path=
// Shipping an app update without sharedUserId can have
// fatal consequences. For example see:
// - https://issuetracker.google.com/issues/36924841
// - https://issuetracker.google.com/issues/36905922
"sharedUserId": "org.mozilla.firefox.sharedID"
]
}
}
variantFilter { // There's a "release" build type that exists by default that we don't use (it's replaced by "nightly" and "beta")
if (buildType.name == 'release') {
setIgnore true
}
// Current build variant setup:
//
// | geckoNightly | geckoBeta |
// |--------------------|---------------|-----------|
// | debug | ✅ | ✅ | Both variants for testing and development.
// | forPerformanceTest | ✅ | ✅ | Both variants unless the perf team only cares about Nightly (TBD).
// | fenixNightlyLegacy | ✅ | ✅ | Release type will be decommissioned soon.
// | fenixNightly | ✅ | ✅ | Built with both, but only the "geckoNightly" one is published to Google Play
// | fenixBeta | ❌ | ✅ | Fenix Beta ships with GV Beta
// | fenixProduction | ❌ | ✅ | Fenix Production ships with GV Beta
// | fennecProduction | ❌ | ✅ | Fenix build to replace production Firefox builds
//
def flavors = flavors*.name.toString().toLowerCase()
if (buildType.name == 'fenixBeta' && flavors.contains("geckonightly")) {
setIgnore true
}
if (buildType.name == 'fenixProduction' && flavors.contains("geckonightly")) {
setIgnore true
}
if (buildType.name == 'fennecProduction' && flavors.contains("geckonightly")) {
setIgnore true
}
}
testOptions {
execution 'ANDROIDX_TEST_ORCHESTRATOR'
unitTests.includeAndroidResources = true
}
flavorDimensions "engine"
productFlavors {
geckoNightly {
dimension "engine"
}
geckoBeta {
dimension "engine"
}
}
splits {
abi {
enable true
reset()
include "x86", "armeabi-v7a", "arm64-v8a", "x86_64"
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
lintOptions {
lintConfig file("lint.xml")
}
packagingOptions {
exclude 'META-INF/atomicfu.kotlin_module'
}
testOptions {
unitTests.returnDefaultValues = true
}
}
def baseVersionCode = generatedVersionCode
android.applicationVariants.all { variant ->
// -------------------------------------------------------------------------------------------------
// Set up kotlin-allopen plugin for writing tests
// -------------------------------------------------------------------------------------------------
boolean hasTest = gradle.startParameter.taskNames.find { it.contains("test") || it.contains("Test") } != null
if (hasTest) {
apply plugin: 'kotlin-allopen'
allOpen {
annotation("org.mozilla.fenix.test.OpenClass")
}
}
// -------------------------------------------------------------------------------------------------
// Generate version codes for builds
// -------------------------------------------------------------------------------------------------
def isDebug = variant.buildType.resValues['IS_DEBUG']?.value ?: false
def useReleaseVersioning = variant.buildType.buildConfigFields['USE_RELEASE_VERSIONING']?.value ?: false
def versionName = Config.releaseVersionName(project)
println("----------------------------------------------")
println("Variant name: " + variant.name)
println("Application ID: " + [variant.mergedFlavor.applicationId, variant.buildType.applicationIdSuffix].findAll().join())
println("Build type: " + variant.buildType.name)
println("Flavor: " + variant.flavorName)
println("Telemetry enabled: " + !isDebug)
if (useReleaseVersioning) {
// The Google Play Store does not allow multiple APKs for the same app that all have the
// same version code. Therefore we need to have different version codes for our ARM and x86
// builds.
// Our generated version code now has a length of 9 (See automation/gradle/versionCode.gradle).
// Our x86 builds need a higher version code to avoid installing ARM builds on an x86 device
// with ARM compatibility mode.
variant.outputs.each { output ->
def abi = output.getFilter(OutputFile.ABI)
def versionCodeOverride
if (variant.name.contains("Fennec")) {
versionCodeOverride = Config.generateFennecVersionCode(abi)
} else if (abi == "x86_64") {
versionCodeOverride = baseVersionCode + 3
} else if (abi == "x86") {
versionCodeOverride = baseVersionCode + 2
} else if (abi == "arm64-v8a") {
versionCodeOverride = baseVersionCode + 1
} else if (abi == "armeabi-v7a") {
versionCodeOverride = baseVersionCode
} else {
throw RuntimeException("Unknown ABI: $abi")
}
println("versionCode for $abi = $versionCodeOverride")
output.versionNameOverride = versionName
output.versionCodeOverride = versionCodeOverride
}
// If this is a release build, validate that "versionName" is set
tasks.withType(AppPreBuildTask) { prebuildTask ->
// You can't add a closure to a variant, so we need to look for an early variant-specific type
// of task (AppPreBuildTask is the first) and filter to make sure we're looking at the task for
// this variant that we're currently configuring
if (prebuildTask.variantName != variant.name) {
return
}
// Append to the task so the first thing it does is run our validation
prebuildTask.doFirst {
if (!project.hasProperty('versionName')) {
throw new RuntimeException("Release builds require the 'versionName' property to be set.\n" +
"If you're using an IDE, set your build variant to be a \"debug\" type.\n" +
"If you're using the command-line, either build a debug variant instead ('./gradlew assembleDebug')\n" +
"\tor continue building the release build and set the \"versionName\" property ('./gradlew -PversionName=<...> assembleNightly').")
// TODO when Android Studio 3.5.0 is prevalent, we can set the "debug" build type as the default
// https://issuetracker.google.com/issues/36988145#comment59
}
}
}
}
// -------------------------------------------------------------------------------------------------
// BuildConfig: Set variables for Sentry, Crash Reporting, and Telemetry
// -------------------------------------------------------------------------------------------------
buildConfigField 'String', 'SENTRY_TOKEN', 'null'
if (!isDebug) {
buildConfigField 'boolean', 'CRASH_REPORTING', 'true'
// Reading sentry token from local file (if it exists). In a release task on taskcluster it will be available.
try {
def token = new File("${rootDir}/.sentry_token").text.trim()
buildConfigField 'String', 'SENTRY_TOKEN', '"' + token + '"'
} catch (FileNotFoundException ignored) {}
} else {
buildConfigField 'boolean', 'CRASH_REPORTING', 'false'
}
if (!isDebug) {
buildConfigField 'boolean', 'TELEMETRY', 'true'
} else {
buildConfigField 'boolean', 'TELEMETRY', 'false'
}
def buildDate = Config.generateBuildDate()
buildConfigField 'String', 'BUILD_DATE', '"' + buildDate + '"'
def variantName = variant.getName()
// -------------------------------------------------------------------------------------------------
// Adjust: Read token from local file if it exists (Only release builds)
// -------------------------------------------------------------------------------------------------
print("Adjust token: ")
if (!isDebug) {
try {
def token = new File("${rootDir}/.adjust_token").text.trim()
buildConfigField 'String', 'ADJUST_TOKEN', '"' + token + '"'
println "(Added from .adjust_token file)"
} catch (FileNotFoundException ignored) {
buildConfigField 'String', 'ADJUST_TOKEN', 'null'
println("X_X")
}
} else {
buildConfigField 'String', 'ADJUST_TOKEN', 'null'
println("--")
}
// -------------------------------------------------------------------------------------------------
// Leanplum: Read token from local file if it exists
// -------------------------------------------------------------------------------------------------
print("Leanplum token: ")
try {
def parts = new File("${rootDir}/.leanplum_token").text.trim().split(":")
def id = parts[0]
def key = parts[1]
buildConfigField 'String', 'LEANPLUM_ID', '"' + id + '"'
buildConfigField 'String', 'LEANPLUM_TOKEN', '"' + key + '"'
println "(Added from .leanplum_token file)"
} catch (FileNotFoundException ignored) {
buildConfigField 'String', 'LEANPLUM_ID', 'null'
buildConfigField 'String', 'LEANPLUM_TOKEN', 'null'
println("X_X")
}
}
androidExtensions {
experimental = true
}
dependencies {
implementation project(':architecture')
geckoNightlyImplementation Deps.mozilla_browser_engine_gecko_nightly
geckoBetaImplementation Deps.mozilla_browser_engine_gecko_beta
implementation Deps.kotlin_stdlib
implementation Deps.kotlin_coroutines
testImplementation Deps.kotlin_coroutines_test
implementation Deps.androidx_appcompat
implementation Deps.androidx_constraintlayout
implementation Deps.androidx_coordinatorlayout
implementation Deps.rxAndroid
implementation Deps.rxKotlin
implementation Deps.rxBindings
implementation Deps.autodispose
implementation Deps.autodispose_android
implementation Deps.autodispose_android_aac
implementation Deps.anko_commons
implementation Deps.anko_sdk
implementation Deps.anko_constraintlayout
implementation Deps.sentry
implementation Deps.leanplum
implementation Deps.osslicenses_library
implementation Deps.mozilla_concept_engine
implementation Deps.mozilla_concept_push
implementation Deps.mozilla_concept_storage
implementation Deps.mozilla_concept_sync
implementation Deps.mozilla_concept_toolbar
implementation Deps.mozilla_browser_awesomebar
implementation Deps.mozilla_feature_downloads
implementation Deps.mozilla_browser_domains
implementation Deps.mozilla_browser_icons
implementation Deps.mozilla_browser_menu
implementation Deps.mozilla_browser_search
implementation Deps.mozilla_browser_session
implementation Deps.mozilla_browser_state
implementation Deps.mozilla_browser_storage_sync
implementation Deps.mozilla_browser_toolbar
implementation Deps.mozilla_feature_accounts
implementation Deps.mozilla_feature_app_links
implementation Deps.mozilla_feature_awesomebar
implementation Deps.mozilla_feature_contextmenu
implementation Deps.mozilla_feature_customtabs
implementation Deps.mozilla_feature_downloads
implementation Deps.mozilla_feature_intent
implementation Deps.mozilla_feature_media
implementation Deps.mozilla_feature_prompts
implementation Deps.mozilla_feature_push
implementation Deps.mozilla_feature_pwa
implementation Deps.mozilla_feature_qr
implementation Deps.mozilla_feature_search
implementation Deps.mozilla_feature_session
implementation Deps.mozilla_feature_toolbar
implementation Deps.mozilla_feature_tabs
implementation Deps.mozilla_feature_findinpage
implementation Deps.mozilla_feature_site_permissions
implementation Deps.mozilla_feature_readerview
implementation Deps.mozilla_feature_tab_collections
implementation Deps.mozilla_feature_sendtab
implementation Deps.mozilla_service_firefox_accounts
implementation Deps.mozilla_service_fretboard
implementation Deps.mozilla_service_glean
implementation Deps.mozilla_service_experiments
implementation Deps.mozilla_support_ktx
implementation Deps.mozilla_support_rustlog
implementation Deps.mozilla_support_utils
implementation Deps.mozilla_ui_colors
implementation Deps.mozilla_ui_icons
implementation Deps.mozilla_ui_publicsuffixlist
implementation Deps.mozilla_lib_crash
implementation Deps.mozilla_lib_push_firebase
debugImplementation Deps.leakcanary
releaseImplementation Deps.leakcanary_noop
implementation Deps.mozilla_lib_fetch_httpurlconnection
implementation Deps.androidx_legacy
implementation Deps.androidx_paging
implementation Deps.androidx_preference
implementation Deps.androidx_fragment
implementation Deps.androidx_navigation_fragment
implementation Deps.androidx_navigation_ui
implementation Deps.androidx_recyclerview
implementation Deps.androidx_lifecycle_runtime
implementation Deps.androidx_lifecycle_viewmodel
implementation Deps.androidx_core
implementation Deps.androidx_core_ktx
implementation Deps.androidx_transition
implementation Deps.androidx_work_ktx
implementation Deps.google_material
implementation Deps.autodispose
implementation Deps.adjust
implementation Deps.installreferrer // Required by Adjust
implementation Deps.google_ads_id // Required for the Google Advertising ID
androidTestImplementation Deps.uiautomator
// Removed pending AndroidX fixes
// androidTestImplementation "tools.fastlane:screengrab:1.2.0"
// androidTestImplementation "br.com.concretesolutions:kappuccino:1.2.1"
androidTestImplementation Deps.espresso_core, {
exclude group: 'com.android.support', module: 'support-annotations'
}
androidTestImplementation(Deps.espresso_contrib) {
exclude module: 'appcompat-v7'
exclude module: 'support-v4'
exclude module: 'support-annotations'
exclude module: 'recyclerview-v7'
exclude module: 'design'
exclude module: 'espresso-core'
}
androidTestImplementation Deps.androidx_test_core
androidTestImplementation Deps.espresso_idling_resources
androidTestImplementation Deps.tools_test_runner
androidTestImplementation Deps.tools_test_rules
androidTestUtil Deps.orchestrator
androidTestImplementation Deps.espresso_core, {
exclude group: 'com.android.support', module: 'support-annotations'
}
androidTestImplementation Deps.mockwebserver
testImplementation Deps.mozilla_support_test
testImplementation Deps.androidx_junit
testImplementation (Deps.robolectric) {
exclude group: 'org.apache.maven'
}
testImplementation 'org.apache.maven:maven-ant-tasks:2.1.3'
// For production builds, the native code for all `org.mozilla.appservices`
// dependencies gets compiled together into a single "megazord" build, and
// different megazords are published for different subsets of features. Ref
// https://mozilla.github.io/application-services/docs/applications/consuming-megazord-libraries.html
// We want to use the one that's specifically designed for Fenix.
implementation Deps.fenix_megazord
testImplementation Deps.fenix_megazord_forUnitTests
implementation Deps.mozilla_support_rusthttp
modules {
module('org.mozilla.appservices:full-megazord') {
replacedBy('org.mozilla.appservices:fenix-megazord', 'prefer the fenix megazord, to reduce final application size')
}
module('org.mozilla.appservices:fenix-megazord') {
replacedBy('org.mozilla.appservices:fenix-megazord-forUnitTests', 'prefer the forUnitTests variant if present')
}
}
testImplementation Deps.mockito_core
androidTestImplementation Deps.mockito_android
testImplementation Deps.mockk
testImplementation Deps.assertk
debugImplementation Deps.flipper
debugImplementation Deps.soLoader
releaseImplementation Deps.flipper_noop
}
if (project.hasProperty("raptor")) {
android.defaultConfig.manifestPlaceholders.isRaptorEnabled = "true"
}
if (project.hasProperty("coverage")) {
tasks.withType(Test) {
jacoco.includeNoLocationClasses = true
}
android.applicationVariants.all { variant ->
task "jacoco${variant.name.capitalize()}TestReport"(type: JacocoReport, dependsOn: "test${variant.name.capitalize()}UnitTest") {
reports {
xml.enabled = true
html.enabled = true
}
def fileFilter = ['**/R.class', '**/R$*.class', '**/BuildConfig.*', '**/Manifest*.*',
'**/*Test*.*', 'android/**/*.*', '**/*$[0-9].*']
def kotlinDebugTree = fileTree(dir: "$project.buildDir/tmp/kotlin-classes/${variant.name}", excludes: fileFilter)
def javaDebugTree = fileTree(dir: "$project.buildDir/intermediates/classes/${variant.flavorName}/${variant.buildType.name}",
excludes: fileFilter)
def mainSrc = "$project.projectDir/src/main/java"
sourceDirectories = files([mainSrc])
classDirectories = files([kotlinDebugTree, javaDebugTree])
executionData = fileTree(dir: project.buildDir, includes: [
"jacoco/test${variant.name.capitalize()}UnitTest.exec", 'outputs/code-coverage/connected/*coverage.ec'
])
}
}
android {
buildTypes {
debug {
testCoverageEnabled true
}
}
}
}
// -------------------------------------------------------------------------------------------------
// Task for printing APK information for the requested variant
// Usage: "./gradlew printVariant -PvariantBuildType=nightly -PvariantEngine=geckoNightly"
// -------------------------------------------------------------------------------------------------
task printVariant {
doLast {
def rawVariant = android.applicationVariants.find {
it.buildType.name == variantBuildType &&
it.productFlavors.find { it.dimension == 'engine' }.name == variantEngine
}
println 'variant: ' + groovy.json.JsonOutput.toJson([
name: rawVariant.name,
apks: rawVariant.variantData.outputScope.apkDatas.collect { [
abi: it.filters.find { it.filterType == 'ABI' }.identifier,
fileName: it.outputFileName,
]}
])
}
}
def glean_android_components_tag = (
Versions.mozilla_android_components.endsWith('-SNAPSHOT') ?
'master' :
'v' + Versions.mozilla_android_components
)
apply from: 'https://github.com/mozilla-mobile/android-components/raw/' + glean_android_components_tag + '/components/service/glean/scripts/sdk_generator.gradle'
// For production builds, the native code for all `org.mozilla.appservices` dependencies gets compiled together
// into a single "megazord" build, and different megazords are published for different subsets of features.
// Ref https://mozilla.github.io/application-services/docs/applications/consuming-megazord-libraries.html
// Substitute all appservices dependencies with an appropriate megazord.
afterEvaluate {
// Format test output. Ported from AC #2401
tasks.matching {it instanceof Test}.all {
systemProperty "robolectric.logging", "stdout"
systemProperty "logging.test-mode", "true"
testLogging.events = []
def out = services.get(StyledTextOutputFactory).create("tests")
beforeSuite { descriptor ->
if (descriptor.getClassName() != null) {
out.style(Style.Header).println("\nSUITE: " + descriptor.getClassName())
}
}
beforeTest { descriptor ->
out.style(Style.Description).println(" TEST: " + descriptor.getName())
}
onOutput { descriptor, event ->
logger.lifecycle(" " + event.message.trim())
}
afterTest { descriptor, result ->
switch (result.getResultType()) {
case ResultType.SUCCESS:
out.style(Style.Success).println(" SUCCESS")
break
case ResultType.FAILURE:
out.style(Style.Failure).println(" FAILURE")
logger.lifecycle("", result.getException())
break
case ResultType.SKIPPED:
out.style(Style.Info).println(" SKIPPED")
break
}
logger.lifecycle("")
}
}
}
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"
}