commit a14b337ef877ebfa945d518cdbf6a69d08cdd735
parent d77df21b0c5f47ce004be57d966a5006df372788
Author: mcarare <48995920+mcarare@users.noreply.github.com>
Date: Tue, 2 Dec 2025 15:28:04 +0000
Bug 1986995 - Generate the Public Suffix List asset at build time. r=android-reviewers,nalexander,ahochheiden
This patch removes the manually uploaded Public Suffix List (PSL) asset from the source tree and replaces it with a Gradle task that generates it at build time.
The new `generatePslAsset` task reads from the canonical PSL source file in `netwerk/dns/effective_tld_names.dat` and creates the binary asset required by the `lib-publicsuffixlist` component. This ensures the PSL is always up-to-date with the netwerk version and removes the need to manually update and check in the asset file.
The custom `PublicSuffixListPlugin` is refactored into a self-contained, cacheable Gradle task for better performance and maintainability.
Differential Revision: https://phabricator.services.mozilla.com/D272237
Diffstat:
4 files changed, 112 insertions(+), 86 deletions(-)
diff --git a/mobile/android/android-components/components/lib/publicsuffixlist/build.gradle b/mobile/android/android-components/components/lib/publicsuffixlist/build.gradle
@@ -3,21 +3,22 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
plugins {
+ id 'com.android.library'
+ id 'kotlin-android'
id 'mozac.PublicSuffixListPlugin'
}
-apply plugin: 'com.android.library'
-apply plugin: 'kotlin-android'
-apply plugin: 'mozac.PublicSuffixListPlugin'
+apply from: '../../../common-config.gradle'
+apply from: '../../../publish.gradle'
android {
- buildFeatures {
- viewBinding = true
- }
-
namespace = 'mozilla.components.lib.publicsuffixlist'
}
+publicSuffixList {
+ sourceFile = file("${gradle.mozconfig.topsrcdir}/netwerk/dns/effective_tld_names.dat")
+}
+
dependencies {
implementation libs.androidx.annotation
implementation libs.kotlinx.coroutines
@@ -30,6 +31,4 @@ dependencies {
testImplementation libs.robolectric
}
-apply from: '../../../common-config.gradle'
-apply from: '../../../publish.gradle'
ext.configurePublish(config.componentsGroupId, project.name, project.ext.description)
diff --git a/mobile/android/android-components/components/lib/publicsuffixlist/src/main/assets/publicsuffixes b/mobile/android/android-components/components/lib/publicsuffixlist/src/main/assets/publicsuffixes
Binary files differ.
diff --git a/mobile/android/android-components/plugins/publicsuffixlist/build.gradle b/mobile/android/android-components/plugins/publicsuffixlist/build.gradle
@@ -22,11 +22,13 @@ repositories {
dependencies {
implementation libs.okhttp
implementation libs.okio
+
+ compileOnly libs.android.gradle.plugin
}
gradlePlugin {
plugins.register("mozac.PublicSuffixListPlugin") {
id = "mozac.PublicSuffixListPlugin"
- implementationClass = "PublicSuffixListPlugin"
+ implementationClass = "mozilla.components.gradle.plugins.PublicSuffixListPlugin"
}
}
diff --git a/mobile/android/android-components/plugins/publicsuffixlist/src/main/java/PublicSuffixListPlugin.kt b/mobile/android/android-components/plugins/publicsuffixlist/src/main/java/PublicSuffixListPlugin.kt
@@ -2,108 +2,111 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-import okhttp3.OkHttpClient
-import okhttp3.Request
+package mozilla.components.gradle.plugins
+
+import com.android.build.api.variant.AndroidComponentsExtension
+import com.android.build.gradle.LibraryPlugin
+import okio.Buffer
import okio.ByteString
import okio.ByteString.Companion.encodeUtf8
-import okio.buffer
-import okio.sink
+import org.gradle.api.DefaultTask
+import org.gradle.api.GradleException
+import org.gradle.api.InvalidUserDataException
import org.gradle.api.Plugin
import org.gradle.api.Project
+import org.gradle.api.file.DirectoryProperty
+import org.gradle.api.file.RegularFileProperty
+import org.gradle.api.tasks.InputFile
+import org.gradle.api.tasks.OutputDirectory
+import org.gradle.api.tasks.TaskAction
+import org.gradle.kotlin.dsl.withType
import java.io.File
import java.util.TreeSet
/**
- * Gradle plugin to update the public suffix list used by the `lib-publicsuffixlist` component.
- *
- * Base on PublicSuffixListGenerator from OkHttp:
- * https://github.com/square/okhttp/blob/master/okhttp/src/test/java/okhttp3/internal/publicsuffix/PublicSuffixListGenerator.java
+ * A self-contained, configuration-cache-compatible Gradle task to generate the Public Suffix List asset.
*/
-class PublicSuffixListPlugin : Plugin<Project> {
- override fun apply(project: Project) {
- project.tasks.register("updatePSL") {
- doLast {
- val filename = project.projectDir.absolutePath + "/src/main/assets/publicsuffixes"
- updatePublicSuffixList(filename)
- }
- }
- }
-
- private fun updatePublicSuffixList(destination: String) {
- val list = fetchPublicSuffixList()
- writeListToDisk(destination, list)
- }
-
- private fun writeListToDisk(destination: String, data: PublicSuffixListData) {
- val fileSink = File(destination).sink()
-
- fileSink.buffer().use { sink ->
- sink.writeInt(data.totalRuleBytes)
+abstract class GeneratePslAssetTask : DefaultTask() {
- for (domain in data.sortedRules) {
- sink.write(domain).writeByte('\n'.code)
- }
+ @get:InputFile
+ abstract val sourceFile: RegularFileProperty
- sink.writeInt(data.totalExceptionRuleBytes)
+ @get:OutputDirectory
+ abstract val outputDir: DirectoryProperty
- for (domain in data.sortedExceptionRules) {
- sink.write(domain).writeByte('\n'.code)
- }
+ @TaskAction
+ fun generate() {
+ val source = sourceFile.get().asFile
+ if (!source.exists()) {
+ throw GradleException("Public Suffix List source file not found: ${source.absolutePath}")
}
- }
-
- private fun fetchPublicSuffixList(): PublicSuffixListData {
- val client = OkHttpClient.Builder().build()
-
- val request = Request.Builder()
- .url("https://publicsuffix.org/list/public_suffix_list.dat")
- .build()
- client.newCall(request).execute().use { response ->
- val source = response.body!!.source()
+ val startTime = System.currentTimeMillis()
+ logger.info("PublicSuffixList> Executing generatePslAsset: Reading Public Suffix List from ${source.path}")
- val data = PublicSuffixListData()
+ val listData = parsePublicSuffixList(source)
+ val newContent = buildBinaryContent(listData)
+ val destination = outputDir.file("publicsuffixes").get().asFile
- while (!source.exhausted()) {
- val line = source.readUtf8LineStrict()
+ logger.info("PublicSuffixList> Writing new Public Suffix List asset...")
+ destination.parentFile.mkdirs()
+ destination.writeBytes(newContent.toByteArray())
- if (line.trim { it <= ' ' }.isEmpty() || line.startsWith("//")) {
- continue
- }
-
- if (line.contains(WILDCARD_CHAR)) {
- assertWildcardRule(line)
- }
+ val duration = System.currentTimeMillis() - startTime
+ logger.info("PublicSuffixList> Public Suffix List asset generation complete in ${duration}ms.")
+ }
- var rule = line.encodeUtf8()
+ private fun buildBinaryContent(data: PublicSuffixListData): ByteString {
+ val buffer = Buffer()
+ buffer.writeInt(data.totalRuleBytes)
+ for (domain in data.sortedRules) {
+ buffer.write(domain).writeByte('\n'.code)
+ }
+ buffer.writeInt(data.totalExceptionRuleBytes)
+ for (domain in data.sortedExceptionRules) {
+ buffer.write(domain).writeByte('\n'.code)
+ }
+ return buffer.readByteString()
+ }
- if (rule.startsWith(EXCEPTION_RULE_MARKER)) {
- rule = rule.substring(1)
- // We use '\n' for end of value.
- data.totalExceptionRuleBytes += rule.size + 1
- data.sortedExceptionRules.add(rule)
- } else {
- data.totalRuleBytes += rule.size + 1 // We use '\n' for end of value.
- data.sortedRules.add(rule)
+ private fun parsePublicSuffixList(sourceFile: File): PublicSuffixListData {
+ val data = PublicSuffixListData()
+
+ sourceFile.useLines { lines ->
+ lines.filter { it.isNotBlank() && !it.startsWith("//") }
+ .forEach { line ->
+ if (line.contains(WILDCARD_CHAR)) {
+ assertWildcardRule(line)
+ }
+
+ var rule = line.encodeUtf8()
+ if (rule.startsWith(EXCEPTION_RULE_MARKER)) {
+ rule = rule.substring(1)
+ // We use '\n' for end of value.
+ data.sortedExceptionRules.add(rule)
+ data.totalExceptionRuleBytes += rule.size + 1
+ } else {
+ data.sortedRules.add(rule)
+ // We use '\n' for end of value.
+ data.totalRuleBytes += rule.size + 1
+ }
}
- }
-
- return data
}
+
+ return data
}
- @Suppress("TooGenericExceptionThrown", "ThrowsCount")
+ @Suppress("ThrowsCount")
private fun assertWildcardRule(rule: String) {
- if (rule.indexOf(WILDCARD_CHAR) != 0) {
- throw RuntimeException("Wildcard is not not in leftmost position")
+ if (!rule.startsWith(WILDCARD_CHAR)) {
+ throw InvalidUserDataException("Wildcard is not in leftmost position")
}
-
- if (rule.indexOf(WILDCARD_CHAR, 1) != -1) {
- throw RuntimeException("Rule contains multiple wildcards")
+ if (rule.lastIndexOf(WILDCARD_CHAR) > 0) {
+ throw InvalidUserDataException("Rule contains multiple wildcards")
}
if (rule.length == 1) {
- throw RuntimeException("Rule wildcards the first level")
+ throw InvalidUserDataException("Rule wildcards the first level")
}
}
@@ -113,10 +116,32 @@ class PublicSuffixListPlugin : Plugin<Project> {
}
}
-data class PublicSuffixListData(
+abstract class PublicSuffixListExtension {
+ @get:InputFile
+ abstract val sourceFile: RegularFileProperty
+}
+
+class PublicSuffixListPlugin : Plugin<Project> {
+ override fun apply(project: Project) {
+ project.plugins.withType<LibraryPlugin>().configureEach {
+ val extension = project.extensions.create("publicSuffixList", PublicSuffixListExtension::class.java)
+
+ val generateTaskProvider = project.tasks.register("generatePslAsset", GeneratePslAssetTask::class.java) {
+ outputDir.set(project.layout.buildDirectory.dir("generated/assets/publicsuffixlist"))
+ sourceFile.set(extension.sourceFile)
+ }
+
+ val androidComponents = project.extensions.getByType(AndroidComponentsExtension::class.java)
+ androidComponents.onVariants(androidComponents.selector().all()) { variant ->
+ variant.sources.assets?.addGeneratedSourceDirectory(generateTaskProvider, GeneratePslAssetTask::outputDir)
+ }
+ }
+ }
+}
+
+private data class PublicSuffixListData(
var totalRuleBytes: Int = 0,
var totalExceptionRuleBytes: Int = 0,
-
val sortedRules: TreeSet<ByteString> = TreeSet(),
val sortedExceptionRules: TreeSet<ByteString> = TreeSet(),
)