diff --git a/gradle-plugin/src/main/kotlin/app/cash/better/dynamic/features/BetterDynamicFeaturesFeatureExtension.kt b/gradle-plugin/src/main/kotlin/app/cash/better/dynamic/features/BetterDynamicFeaturesFeatureExtension.kt index e1a4864..90da7ac 100644 --- a/gradle-plugin/src/main/kotlin/app/cash/better/dynamic/features/BetterDynamicFeaturesFeatureExtension.kt +++ b/gradle-plugin/src/main/kotlin/app/cash/better/dynamic/features/BetterDynamicFeaturesFeatureExtension.kt @@ -19,7 +19,5 @@ import org.gradle.api.Project import org.gradle.api.provider.Property abstract class BetterDynamicFeaturesFeatureExtension { - abstract val enableResourceRewriting: Property - abstract val baseProject: Property } diff --git a/gradle-plugin/src/main/kotlin/app/cash/better/dynamic/features/BetterDynamicFeaturesPlugin.kt b/gradle-plugin/src/main/kotlin/app/cash/better/dynamic/features/BetterDynamicFeaturesPlugin.kt index d38ea2d..15f550b 100644 --- a/gradle-plugin/src/main/kotlin/app/cash/better/dynamic/features/BetterDynamicFeaturesPlugin.kt +++ b/gradle-plugin/src/main/kotlin/app/cash/better/dynamic/features/BetterDynamicFeaturesPlugin.kt @@ -19,11 +19,6 @@ import app.cash.better.dynamic.features.codegen.AndroidVariant import app.cash.better.dynamic.features.codegen.TypesafeImplementationsCompilationTask import app.cash.better.dynamic.features.codegen.TypesafeImplementationsGeneratorTask import app.cash.better.dynamic.features.codegen.api.KSP_REPORT_DIRECTORY_PREFIX -import app.cash.better.dynamic.features.magic.AaptMagic -import app.cash.better.dynamic.features.magic.MagicRClassFixingTask -import app.cash.better.dynamic.features.magic.MagicRFixingTask -import app.cash.better.dynamic.features.magic.ResourceClashFinderTask -import app.cash.better.dynamic.features.magic.ResourceStyleableMapperTask import app.cash.better.dynamic.features.tasks.BaseLockfileWriterTask import app.cash.better.dynamic.features.tasks.CheckExternalResourcesTask import app.cash.better.dynamic.features.tasks.CheckLockfileTask @@ -77,7 +72,6 @@ class BetterDynamicFeaturesPlugin : Plugin { } } - @OptIn(AaptMagic::class) private fun applyToFeature(project: Project) { val pluginExtension = project.extensions.create( "betterDynamicFeatures", @@ -87,14 +81,12 @@ class BetterDynamicFeaturesPlugin : Plugin { val sharedConfiguration = project.createSharedFeatureConfiguration() project.setupFeatureDependencyGraphTasks(androidComponents, sharedConfiguration) - project.setupFeatureRMagicTask(androidComponents, pluginExtension) project.plugins.withId("com.google.devtools.ksp") { project.dependencies.add("implementation", "app.cash.better.dynamic.features:runtime:$VERSION") project.setupFeatureKsp(androidComponents) } } - @OptIn(AaptMagic::class) private fun applyToApplication(project: Project) { val pluginExtension = project.extensions.create("betterDynamicFeatures", BetterDynamicFeaturesExtension::class.java) @@ -118,7 +110,6 @@ class BetterDynamicFeaturesPlugin : Plugin { } project.setupBaseDependencyGraphTasks(androidComponents, featureProjects, sharedConfiguration) - project.setupBaseResourceClashTask(androidComponents, featureProjects) } androidComponents.onVariants { variant -> @@ -282,7 +273,6 @@ class BetterDynamicFeaturesPlugin : Plugin { tasks.named("preBuild").dependsOn(checkLockfileTask) } - @AaptMagic private fun Project.setupBaseResourcesCheckingTasks( androidComponents: AndroidComponentsExtension<*, *, *>, pluginExtension: BetterDynamicFeaturesExtension, @@ -337,144 +327,6 @@ class BetterDynamicFeaturesPlugin : Plugin { } } - @AaptMagic - private fun Project.setupBaseResourceClashTask( - androidComponents: AndroidComponentsExtension<*, *, *>, - featureProjects: List, - ) { - androidComponents.onVariants { variant -> - afterEvaluate { - tasks.register( - taskName("find", variant, "ResourceClashes"), - ResourceClashFinderTask::class.java, - ) { task -> - val linkTask = tasks.named( - taskName("process", variant, "Resources"), - LinkApplicationAndroidResourcesTask::class.java, - ) - task.baseSymbolList.set { linkTask.get().getTextSymbolOutputFile()!! } - - task.featureSymbolLists.setFrom( - featureProjects.map { - it.buildDir.resolve("intermediates/runtime_symbol_list/${variant.name}/R.txt") - }, - ) - featureProjects.forEach { - task.dependsOn(it.tasks.named(taskName("process", variant, "Resources"))) - } - task.dependsOn(tasks.named(taskName("process", variant, "Resources"))) - task.resourceMappingFile.set(buildDir.resolve("betterDynamicFeatures/resource-mapping/${variant.name}/mapping.txt")) - } - } - } - } - - @AaptMagic - private fun Project.setupFeatureRMagicTask( - androidComponents: AndroidComponentsExtension<*, *, *>, - pluginExtension: BetterDynamicFeaturesFeatureExtension, - ) { - androidComponents.onVariants { variant -> - afterEvaluate { - if (!pluginExtension.enableResourceRewriting.getOrElse(false)) return@afterEvaluate - - val baseProject = pluginExtension.baseProject.orNull - require(baseProject != null) { - """ - |A base project has not been set for this feature module! This will result in undefined runtime behaviour. - |Add a reference to your base module in the feature module configuration: - | - |betterDynamicFeatures { - | baseProject.set(project(":app")) - |} - """.trimMargin() - return@afterEvaluate - } - - val styleableTask = tasks.register(taskName("extract", variant, "StyleableResources"), ResourceStyleableMapperTask::class.java) { task -> - task.resourceMappingFile.set( - baseProject.tasks.named( - "find${variant.name.capitalized()}ResourceClashes", - ResourceClashFinderTask::class.java, - ).flatMap { it.resourceMappingFile }, - ) - task.styleableArraysFile.set(buildDir.resolve("betterDynamicFeatures/resource-mapping/${variant.name}/styleables.txt")) - val linkTask = tasks.named( - taskName("process", variant, "Resources"), - LinkApplicationAndroidResourcesTask::class.java, - ) - task.runtimeSymbolList.set { linkTask.get().getTextSymbolOutputFile()!! } - - task.dependsOn(tasks.named(taskName("process", variant, "Resources"))) - } - - // This task rewrites Android Binary XML files when assembling APKs - val ohNoTask = tasks.register( - "magicRewrite${variant.name.capitalized()}BinaryResources", - MagicRFixingTask::class.java, - ) { task -> - task.processedResourceArchive.set { buildDir.resolve("intermediates/processed_res/${variant.name}/out/resources-${variant.name}.ap_") } - task.outputArchive.set { buildDir.resolve("intermediates/processed_res/${variant.name}/out/resources-${variant.name}.ap_") } - - // I'm in Spain without the S - task.resourceMappingFile.set( - baseProject.tasks.named( - "find${variant.name.capitalized()}ResourceClashes", - ResourceClashFinderTask::class.java, - ).flatMap { it.resourceMappingFile }, - ) - - task.mode.set(MagicRFixingTask.Mode.BinaryXml) - task.dependsOn(styleableTask) - } - tasks.named("assemble${variant.name.capitalized()}").dependsOn(ohNoTask) - tasks.named(taskName("package", variant)).dependsOn(ohNoTask) - - // This task rewrites Proto XML files when producing an app bundle - val protohNoTask = tasks.register( - "magicRewrite${variant.name.capitalized()}ProtoResources", - MagicRFixingTask::class.java, - ) { task -> - task.processedResourceArchive.set { buildDir.resolve("intermediates/linked_res_for_bundle/${variant.name}/bundled-res.ap_") } - task.outputArchive.set { buildDir.resolve("intermediates/linked_res_for_bundle/${variant.name}/bundled-res.ap_") } - - // Bread in French - task.resourceMappingFile.set( - baseProject.tasks.named( - "find${variant.name.capitalized()}ResourceClashes", - ResourceClashFinderTask::class.java, - ).flatMap { it.resourceMappingFile }, - ) - - task.mode.set(MagicRFixingTask.Mode.ProtoXml) - task.dependsOn(styleableTask) - task.dependsOn(tasks.named(taskName("bundle", variant, "Resources"))) - } - tasks.named(taskName("build", variant, "PreBundle")).dependsOn(protohNoTask) - - val rClassTask = tasks.register(taskName("magicRewrite", variant, "RClasses"), MagicRClassFixingTask::class.java) { task -> - task.resourceMappingFile.set( - baseProject.tasks.named( - "find${variant.name.capitalized()}ResourceClashes", - ResourceClashFinderTask::class.java, - ).flatMap { it.resourceMappingFile }, - ) - task.styleableMappingFile.set(styleableTask.flatMap { it.styleableArraysFile }) - - task.jar.set(buildDir.resolve("intermediates/compile_and_runtime_not_namespaced_r_class_jar/${variant.name}/R.jar")) - task.output.set(buildDir.resolve("intermediates/compile_and_runtime_not_namespaced_r_class_jar/${variant.name}/R.jar")) - - task.dependsOn(tasks.named("process${variant.name.capitalized()}Resources")) - } - tasks.withType(KspTask::class.java).configureEach { kspTask -> - if (!kspTaskMatchesVariant(kspTask, variant)) return@configureEach - kspTask.dependsOn(rClassTask) - } - tasks.named(taskName("compile", variant, "JavaWithJavac")).dependsOn(rClassTask) - } - } - } - private fun taskName(vararg args: Any) = args.mapIndexed { index, arg -> when (arg) { is Variant -> if (index != 0) arg.name.replaceFirstChar { it.uppercase() } else arg.name diff --git a/gradle-plugin/src/main/kotlin/app/cash/better/dynamic/features/magic/AaptMagic.kt b/gradle-plugin/src/main/kotlin/app/cash/better/dynamic/features/magic/AaptMagic.kt deleted file mode 100644 index 1250ffb..0000000 --- a/gradle-plugin/src/main/kotlin/app/cash/better/dynamic/features/magic/AaptMagic.kt +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (C) 2023 Square, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package app.cash.better.dynamic.features.magic - -/** - * Right now we do a bunch of magical nonsense to fix an AAPT bug. - * We want to remove this magic ASAP, and this will help make it easier to find and delete that - * magic in the future. - * - * [https://issuetracker.google.com/issues/275748380](https://issuetracker.google.com/issues/275748380) - */ -@RequiresOptIn -annotation class AaptMagic diff --git a/gradle-plugin/src/main/kotlin/app/cash/better/dynamic/features/magic/MagicRClassFixingTask.kt b/gradle-plugin/src/main/kotlin/app/cash/better/dynamic/features/magic/MagicRClassFixingTask.kt deleted file mode 100644 index 4df63ba..0000000 --- a/gradle-plugin/src/main/kotlin/app/cash/better/dynamic/features/magic/MagicRClassFixingTask.kt +++ /dev/null @@ -1,151 +0,0 @@ -/* - * Copyright (C) 2023 Square, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package app.cash.better.dynamic.features.magic - -import javassist.ClassPool -import javassist.CtClass -import javassist.CtField -import org.gradle.api.DefaultTask -import org.gradle.api.file.RegularFileProperty -import org.gradle.api.tasks.InputFile -import org.gradle.api.tasks.OutputFile -import org.gradle.api.tasks.TaskAction -import java.io.BufferedOutputStream -import java.io.FileOutputStream -import java.nio.file.FileSystems -import java.nio.file.Files -import java.nio.file.Path -import java.util.jar.JarEntry -import java.util.jar.JarOutputStream -import kotlin.io.path.inputStream -import kotlin.io.path.isRegularFile -import kotlin.io.path.name -import kotlin.io.path.readBytes - -/** - * This task manipulates the bytecode of the generated classes in the generated `R.jar` archive so - * that the static fields contain the correct resource ID values taken from the base module. - */ -@AaptMagic -abstract class MagicRClassFixingTask : DefaultTask() { - @get:InputFile - abstract val resourceMappingFile: RegularFileProperty - - @get:InputFile - abstract val jar: RegularFileProperty - - @get:InputFile - abstract val styleableMappingFile: RegularFileProperty - - @get:OutputFile - abstract val output: RegularFileProperty - - private lateinit var resourceMapping: Map> - private lateinit var styleableMapping: Map> - - @TaskAction - fun rewriteClasses() { - resourceMapping = readResourceMapping() - styleableMapping = readStyleableMapping() - - val tempJarFile = temporaryDir.resolve("R.jar") - val jarOutput = JarOutputStream(BufferedOutputStream(FileOutputStream(tempJarFile))) - - val jarFs = FileSystems.newFileSystem(jar.asFile.get().toPath(), javaClass.classLoader) - val pool = ClassPool(null) - - Files.walk(jarFs.getPath("")).filter(Path::isRegularFile).forEach { path -> - val bytes = when (path.name) { - "R\$attr.class" -> processClass(pool, path, "attr").toBytecode() - "R\$id.class" -> processClass(pool, path, "id").toBytecode() - "R\$styleable.class" -> processStyleableClass(pool, path).toBytecode() - else -> path.readBytes() - } - - jarOutput.run { - putNextEntry(JarEntry(path.toString())) - write(bytes) - closeEntry() - } - } - - jarOutput.close() - tempJarFile.copyTo(output.asFile.get(), overwrite = true) - } - - private fun processClass(pool: ClassPool, file: Path, type: String): CtClass { - val clazz = pool.makeClass(file.inputStream()) - val attrs = resourceMapping.getValue(type) - - // Prevent concurrent modification - val fieldsCopy = clazz.declaredFields.toList() - - fieldsCopy.forEach { field -> - val originalId = field.constantValue as Int - if (attrs.containsKey(originalId)) { - clazz.removeField(field) - clazz.addField(field, CtField.Initializer.constant(attrs.getValue(originalId).base)) - } - } - - return clazz - } - - private fun processStyleableClass(pool: ClassPool, file: Path): CtClass { - val clazz = pool.makeClass(file.inputStream()) - val fieldsCopy = clazz.declaredFields.toList() - - fieldsCopy.forEach { field -> - if (styleableMapping.containsKey(field.name)) { - val realIds = styleableMapping.getValue(field.name) - clazz.removeField(field) - clazz.addField(field, CtField.Initializer.byExpr("new int[] { ${realIds.joinToString()} }")) - } - } - - // If we don't clear the static initializer, it'll try to overwrite the values in fields that we added - clazz.classInitializer?.setBody("{}") - - return clazz - } - - private fun readResourceMapping(): Map> = - resourceMappingFile.asFile.get() - .readLines() - .map { line -> - val (key, feature, base) = line.split(" ") - val (type, name) = key.split("/") - - ResourceEntry(type, name, feature.toInt(), base.toInt()) - } - .groupBy { it.type } - .mapValues { (_, value) -> value.associateBy { it.feature } } - - private fun readStyleableMapping(): Map> = styleableMappingFile.asFile.get() - .readLines() - .associate { line -> - val parts = line.split(" ") - - parts[0] to parts.drop(1).map { it.toInt() } - } - - private data class ResourceEntry( - val type: String, - val name: String, - val feature: Int, - val base: Int, - ) -} diff --git a/gradle-plugin/src/main/kotlin/app/cash/better/dynamic/features/magic/MagicRFixingTask.kt b/gradle-plugin/src/main/kotlin/app/cash/better/dynamic/features/magic/MagicRFixingTask.kt deleted file mode 100644 index ba0ea3d..0000000 --- a/gradle-plugin/src/main/kotlin/app/cash/better/dynamic/features/magic/MagicRFixingTask.kt +++ /dev/null @@ -1,210 +0,0 @@ -/* - * Copyright (C) 2023 Square, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package app.cash.better.dynamic.features.magic - -import com.android.aapt.XmlElement -import com.android.aapt.XmlNode -import com.reandroid.archive.APKArchive -import com.reandroid.archive.FileInputSource -import com.reandroid.arsc.chunk.xml.ResXmlAttribute -import com.reandroid.arsc.chunk.xml.ResXmlDocument -import com.reandroid.arsc.chunk.xml.ResXmlElement -import com.reandroid.arsc.chunk.xml.ResXmlIDMap -import com.reandroid.arsc.container.SingleBlockContainer -import com.reandroid.arsc.value.ValueType -import org.gradle.api.DefaultTask -import org.gradle.api.file.RegularFileProperty -import org.gradle.api.provider.Property -import org.gradle.api.tasks.Input -import org.gradle.api.tasks.InputFile -import org.gradle.api.tasks.OutputFile -import org.gradle.api.tasks.TaskAction -import java.nio.file.FileSystems -import java.nio.file.Files -import kotlin.io.path.inputStream -import kotlin.io.path.isDirectory -import kotlin.io.path.outputStream - -/** - * This task rewrites the compiled layout resource files to use the correct `attr` and `id` references. - */ -@AaptMagic -abstract class MagicRFixingTask : DefaultTask() { - @get:InputFile - abstract val resourceMappingFile: RegularFileProperty - - /** - * the intermediate `.ap_` file - */ - @get:InputFile - abstract val processedResourceArchive: RegularFileProperty - - @get:OutputFile - abstract val outputArchive: RegularFileProperty - - @get:Input - abstract val mode: Property - - private fun readResourceMapping(): Map = - resourceMappingFile.asFile.get() - .readLines() - .map { it.split(" ") } - .associate { (_, key, value) -> key.toInt() to value.toInt() } - - @TaskAction - fun rewrite() { - val resourceMapping = readResourceMapping() - when (mode.get()) { - Mode.BinaryXml -> rewriteBinaryXml(resourceMapping) - Mode.ProtoXml -> rewriteProtoXml(resourceMapping) - null -> {} - } - } - - private fun rewriteProtoXml(resourceMapping: Map) { - val copyFile = temporaryDir.resolve("copy.ap_") - copyFile.delete() - val copy = processedResourceArchive.asFile.get().copyTo(copyFile) - - val zip = FileSystems.newFileSystem(copy.toPath(), this::class.java.classLoader) - Files.walk(zip.getPath("")).filter { - it.startsWith(zip.getPath("res", "layout")) && !it.isDirectory() - }.forEach { - val rootNode = it.inputStream().use { input -> XmlNode.ADAPTER.decode(input) } - val result = processProtoFile(rootNode, resourceMapping) - - it.outputStream().use { output -> XmlNode.ADAPTER.encode(output, result) } - } - - zip.close() - copyFile.copyTo(outputArchive.get().asFile, overwrite = true) - } - - private fun processProtoFile(root: XmlNode, resourceMapping: Map): XmlNode { - val mappedXmlNode = root.walkElements { element -> - val mappedAttributes = element.attribute.map { attr -> - if (resourceMapping.containsKey(attr.resource_id)) { - logger.info("Replaced attribute %s %x with %x".format(attr.name, attr.resource_id, resourceMapping.getValue(attr.resource_id))) - return@map attr.copy(resource_id = resourceMapping.getValue(attr.resource_id)) - } - - if (attr.compiled_item?.ref?.id != null && resourceMapping.containsKey(attr.compiled_item.ref.id)) { - logger.info("Updated attr ${attr.name} reference of %x with %x".format(attr.compiled_item.ref.id, resourceMapping.getValue(attr.compiled_item.ref.id))) - return@map attr.copy( - compiled_item = attr.compiled_item.copy( - ref = attr.compiled_item.ref.copy( - id = resourceMapping.getValue(attr.compiled_item.ref.id), - ), - ), - ) - } - - return@map attr - } - - element.copy(attribute = mappedAttributes) - } - - return mappedXmlNode - } - - private fun XmlNode.walkElements(block: (element: XmlElement) -> XmlElement): XmlNode { - if (element == null) return this - val mappedElement = block(element) - - val children = mappedElement.child.toMutableList() - for (i in 0..children.lastIndex) { - children[i] = children[i].walkElements(block) - } - - return this.copy(element = mappedElement.copy(child = children)) - } - - private fun rewriteBinaryXml(resourceMapping: Map) { - val copyFile = temporaryDir.resolve("copy.ap_") - copyFile.delete() - val copy = processedResourceArchive.asFile.get().copyTo(copyFile) - - val apkArchive = APKArchive.loadZippedApk(copy) - - temporaryDir.resolve("res/layouts").mkdirs() - - apkArchive.listInputSources().filter { it.name.startsWith("res/layout") }.forEach { source -> - val document = source.openStream().use { ResXmlDocument().apply { readBytes(it) } } - processLayoutFile(document, resourceMapping) - - val tempFile = temporaryDir.resolve(source.name) - tempFile.parentFile.mkdirs() - document.writeBytes(tempFile.outputStream()) - - // Replace the entry in the APK - apkArchive.add(FileInputSource(tempFile, source.name)) - } - - val outputArchive = outputArchive.asFile.get() - apkArchive.writeApk(outputArchive.outputStream()) - } - - private fun processLayoutFile(xmlDocument: ResXmlDocument, resourceMapping: Map) { - xmlDocument.walkElements { element -> - element.listAttributes().forEach { attr -> - // Rewrite attribute IDs - if (resourceMapping.containsKey(attr.nameResourceID)) { - val original = attr.nameResourceID - - val index = attr.getResXmlIDMap()!!.getByResId(attr.nameResourceID).index - attr.getResXmlIDMap()!!.resXmlIDArray.addResourceId(index, resourceMapping.getValue(attr.nameResourceID)) - logger.info("Replaced attribute %s %x with %x".format(attr.name, original, attr.nameResourceID)) - } - - if (attr.valueType == ValueType.REFERENCE && resourceMapping.containsKey(attr.data)) { - logger.info("Updated attr ${attr.fullName} reference of %x with %x".format(attr.data, resourceMapping.getValue(attr.data))) - attr.setValueAsResourceId(resourceMapping.getValue(attr.data)) - } - } - } - - // Recomputes offsets in the binary data with our new ID(s) - xmlDocument.refresh() - } - - /** - * Walk the binary XML tree (starting with the root) for all elements in the document. - */ - @Suppress("UNCHECKED_CAST") - private fun ResXmlDocument.walkElements(block: (element: ResXmlElement) -> Unit) { - val root = (childes.last() as SingleBlockContainer).item - block(root) - - val queue = ArrayDeque(root.listElements()) - while (queue.isNotEmpty()) { - val next = queue.removeFirst() - block(next) - - queue += next.listElements() - } - } - - private fun ResXmlAttribute.getResXmlIDMap(): ResXmlIDMap? { - val xmlElement = this.parentResXmlElement - return xmlElement?.resXmlIDMap - } - - enum class Mode { - BinaryXml, - ProtoXml; - } -} diff --git a/gradle-plugin/src/main/kotlin/app/cash/better/dynamic/features/magic/ResourceClashFinderTask.kt b/gradle-plugin/src/main/kotlin/app/cash/better/dynamic/features/magic/ResourceClashFinderTask.kt deleted file mode 100644 index 662baa4..0000000 --- a/gradle-plugin/src/main/kotlin/app/cash/better/dynamic/features/magic/ResourceClashFinderTask.kt +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (C) 2023 Square, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package app.cash.better.dynamic.features.magic - -import org.gradle.api.DefaultTask -import org.gradle.api.file.ConfigurableFileCollection -import org.gradle.api.file.RegularFileProperty -import org.gradle.api.tasks.InputFile -import org.gradle.api.tasks.InputFiles -import org.gradle.api.tasks.OutputFile -import org.gradle.api.tasks.TaskAction -import java.io.File - -@AaptMagic -abstract class ResourceClashFinderTask : DefaultTask() { - @get:InputFile - abstract val baseSymbolList: RegularFileProperty - - @get:InputFiles - abstract val featureSymbolLists: ConfigurableFileCollection - - @get:OutputFile - abstract val resourceMappingFile: RegularFileProperty - - @TaskAction - fun findClashes() { - // Maps the feature module value to the base module value - val clashMappings = mutableMapOf() - - val baseResourcesByName = - parseSymbols(baseSymbolList.get().asFile) - .filter { it.type in SUPPORTED_TYPES } - .associateBy { it.key } - - featureSymbolLists.files.forEach { file -> - parseSymbols(file).filter { it.type in SUPPORTED_TYPES }.forEach { symbol -> - when (val base = baseResourcesByName[symbol.key]) { - null -> {} - else -> { - clashMappings[symbol.id] = Clash(symbol.key, symbol.id, base.id) - } - } - } - } - - resourceMappingFile.asFile.get().writeText( - clashMappings.entries.joinToString(separator = "\n") { (_, clash) -> - "${clash.resource} ${clash.feature} ${clash.base}" - }, - ) - } - - private fun parseSymbols(file: File) = file.readLines().mapNotNull { line -> - val parts = line.split(" ") - if (parts[0] == "int[]") return@mapNotNull null - - val id = parts[3].removePrefix("0x").toInt(16) - Symbol(parts[1], parts[2], id) - } - - /** - * int attr specialText 0x7e010000 - * int dimen fab_margin 0x7e020000 - * int id FirstFragment 0x7e030000 - * int id button_first 0x7e030001 - * int id custom_text_view 0x7e030002 - * int id fab 0x7e030003 - * int id nav_graph 0x7e030004 - */ - private data class Symbol(val type: String, val name: String, val id: Int) - - private data class Clash(val resource: String, val feature: Int, val base: Int) - - private val Symbol.key get() = "$type/$name" - - private companion object { - val SUPPORTED_TYPES = setOf("attr", "id") - } -} diff --git a/gradle-plugin/src/main/kotlin/app/cash/better/dynamic/features/magic/ResourceStyleableMapperTask.kt b/gradle-plugin/src/main/kotlin/app/cash/better/dynamic/features/magic/ResourceStyleableMapperTask.kt deleted file mode 100644 index 6b8d5d9..0000000 --- a/gradle-plugin/src/main/kotlin/app/cash/better/dynamic/features/magic/ResourceStyleableMapperTask.kt +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (C) 2023 Square, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package app.cash.better.dynamic.features.magic - -import org.gradle.api.DefaultTask -import org.gradle.api.file.RegularFileProperty -import org.gradle.api.tasks.InputFile -import org.gradle.api.tasks.OutputFile -import org.gradle.api.tasks.TaskAction -import java.io.File - -@AaptMagic -abstract class ResourceStyleableMapperTask : DefaultTask() { - - @get:InputFile - abstract val runtimeSymbolList: RegularFileProperty - - @get:InputFile - abstract val resourceMappingFile: RegularFileProperty - - @get:OutputFile - abstract val styleableArraysFile: RegularFileProperty - - private lateinit var resourceMapping: Map - private fun readResourceMapping(): Map = - resourceMappingFile.asFile.get() - .readLines() - .map { it.split(" ") } - .associate { (_, key, value) -> key.toInt() to value.toInt() } - - @TaskAction - fun writeStyleables() { - resourceMapping = readResourceMapping() - - val styleables = parseStyleables(runtimeSymbolList.get().asFile) - - val results = styleables.map { styleable -> - val newIds = styleable.ids.map { resourceMapping[it] ?: it } - styleable.copy(ids = newIds) - } - - styleableArraysFile.asFile.get().writeText( - results.joinToString(separator = "\n") { - """${it.name} ${it.ids.joinToString(separator = " ")}""" - }, - ) - } - - private fun parseStyleables(file: File) = file.readLines().mapNotNull { line -> - val parts = line.split(" ") - if (parts[0] != "int[]") return@mapNotNull null - - val match = Regex("""\{(.*)\}""").find(line)!!.value - val ids = match.removePrefix("{ ").removeSuffix(" }").split(", ") - .map { it.removePrefix("0x").toInt(16) } - - Styleable(parts[2], ids) - } - - private data class Styleable(val name: String, val ids: List) -} diff --git a/gradle-plugin/src/main/proto/aapt/pb/Configuration.proto b/gradle-plugin/src/main/proto/aapt/pb/Configuration.proto deleted file mode 100644 index 8a4644c..0000000 --- a/gradle-plugin/src/main/proto/aapt/pb/Configuration.proto +++ /dev/null @@ -1,206 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -syntax = "proto3"; - -package aapt.pb; - -option java_package = "com.android.aapt"; - -// A description of the requirements a device must have in order for a -// resource to be matched and selected. -message Configuration { - enum LayoutDirection { - LAYOUT_DIRECTION_UNSET = 0; - LAYOUT_DIRECTION_LTR = 1; - LAYOUT_DIRECTION_RTL = 2; - } - - enum ScreenLayoutSize { - SCREEN_LAYOUT_SIZE_UNSET = 0; - SCREEN_LAYOUT_SIZE_SMALL = 1; - SCREEN_LAYOUT_SIZE_NORMAL = 2; - SCREEN_LAYOUT_SIZE_LARGE = 3; - SCREEN_LAYOUT_SIZE_XLARGE = 4; - } - - enum ScreenLayoutLong { - SCREEN_LAYOUT_LONG_UNSET = 0; - SCREEN_LAYOUT_LONG_LONG = 1; - SCREEN_LAYOUT_LONG_NOTLONG = 2; - } - - enum ScreenRound { - SCREEN_ROUND_UNSET = 0; - SCREEN_ROUND_ROUND = 1; - SCREEN_ROUND_NOTROUND = 2; - } - - enum WideColorGamut { - WIDE_COLOR_GAMUT_UNSET = 0; - WIDE_COLOR_GAMUT_WIDECG = 1; - WIDE_COLOR_GAMUT_NOWIDECG = 2; - } - - enum Hdr { - HDR_UNSET = 0; - HDR_HIGHDR = 1; - HDR_LOWDR = 2; - } - - enum Orientation { - ORIENTATION_UNSET = 0; - ORIENTATION_PORT = 1; - ORIENTATION_LAND = 2; - ORIENTATION_SQUARE = 3; - } - - enum UiModeType { - UI_MODE_TYPE_UNSET = 0; - UI_MODE_TYPE_NORMAL = 1; - UI_MODE_TYPE_DESK = 2; - UI_MODE_TYPE_CAR = 3; - UI_MODE_TYPE_TELEVISION = 4; - UI_MODE_TYPE_APPLIANCE = 5; - UI_MODE_TYPE_WATCH = 6; - UI_MODE_TYPE_VRHEADSET = 7; - } - - enum UiModeNight { - UI_MODE_NIGHT_UNSET = 0; - UI_MODE_NIGHT_NIGHT = 1; - UI_MODE_NIGHT_NOTNIGHT = 2; - } - - enum Touchscreen { - TOUCHSCREEN_UNSET = 0; - TOUCHSCREEN_NOTOUCH = 1; - TOUCHSCREEN_STYLUS = 2; - TOUCHSCREEN_FINGER = 3; - } - - enum KeysHidden { - KEYS_HIDDEN_UNSET = 0; - KEYS_HIDDEN_KEYSEXPOSED = 1; - KEYS_HIDDEN_KEYSHIDDEN = 2; - KEYS_HIDDEN_KEYSSOFT = 3; - } - - enum Keyboard { - KEYBOARD_UNSET = 0; - KEYBOARD_NOKEYS = 1; - KEYBOARD_QWERTY = 2; - KEYBOARD_TWELVEKEY = 3; - } - - enum NavHidden { - NAV_HIDDEN_UNSET = 0; - NAV_HIDDEN_NAVEXPOSED = 1; - NAV_HIDDEN_NAVHIDDEN = 2; - } - - enum Navigation { - NAVIGATION_UNSET = 0; - NAVIGATION_NONAV = 1; - NAVIGATION_DPAD = 2; - NAVIGATION_TRACKBALL = 3; - NAVIGATION_WHEEL = 4; - } - - // - // Axis/dimensions that are understood by the runtime. - // - - // Mobile country code. - uint32 mcc = 1; - - // Mobile network code. - uint32 mnc = 2; - - // BCP-47 locale tag. - string locale = 3; - - // Left-to-right, right-to-left... - LayoutDirection layout_direction = 4; - - // Screen width in pixels. Prefer screen_width_dp. - uint32 screen_width = 5; - - // Screen height in pixels. Prefer screen_height_dp. - uint32 screen_height = 6; - - // Screen width in density independent pixels (dp). - uint32 screen_width_dp = 7; - - // Screen height in density independent pixels (dp). - uint32 screen_height_dp = 8; - - // The smallest screen dimension, regardless of orientation, in dp. - uint32 smallest_screen_width_dp = 9; - - // Whether the device screen is classified as small, normal, large, xlarge. - ScreenLayoutSize screen_layout_size = 10; - - // Whether the device screen is long. - ScreenLayoutLong screen_layout_long = 11; - - // Whether the screen is round (Android Wear). - ScreenRound screen_round = 12; - - // Whether the screen supports wide color gamut. - WideColorGamut wide_color_gamut = 13; - - // Whether the screen has high dynamic range. - Hdr hdr = 14; - - // Which orientation the device is in (portrait, landscape). - Orientation orientation = 15; - - // Which type of UI mode the device is in (television, car, etc.). - UiModeType ui_mode_type = 16; - - // Whether the device is in night mode. - UiModeNight ui_mode_night = 17; - - // The device's screen density in dots-per-inch (dpi). - uint32 density = 18; - - // Whether a touchscreen exists, supports a stylus, or finger. - Touchscreen touchscreen = 19; - - // Whether the keyboard hardware keys are currently hidden, exposed, or - // if the keyboard is a software keyboard. - KeysHidden keys_hidden = 20; - - // The type of keyboard present (none, QWERTY, 12-key). - Keyboard keyboard = 21; - - // Whether the navigation is exposed or hidden. - NavHidden nav_hidden = 22; - - // The type of navigation present on the device - // (trackball, wheel, dpad, etc.). - Navigation navigation = 23; - - // The minimum SDK version of the device. - uint32 sdk_version = 24; - - // - // Build-time only dimensions. - // - - string product = 25; -} diff --git a/gradle-plugin/src/main/proto/aapt/pb/Resources.proto b/gradle-plugin/src/main/proto/aapt/pb/Resources.proto deleted file mode 100644 index 4e1800c..0000000 --- a/gradle-plugin/src/main/proto/aapt/pb/Resources.proto +++ /dev/null @@ -1,639 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -syntax = "proto3"; - -import "aapt/pb/Configuration.proto"; - -package aapt.pb; - -option java_package = "com.android.aapt"; - -// A string pool that wraps the binary form of the C++ class android::ResStringPool. -message StringPool { - bytes data = 1; -} - -// The position of a declared entity within a file. -message SourcePosition { - uint32 line_number = 1; - uint32 column_number = 2; -} - -// Developer friendly source file information for an entity in the resource table. -message Source { - // The index of the string path within the source string pool of a ResourceTable. - uint32 path_idx = 1; - SourcePosition position = 2; -} - -// The name and version fingerprint of a build tool. -message ToolFingerprint { - string tool = 1; - string version = 2; -} - -// Top level message representing a resource table. -message ResourceTable { - // The string pool containing source paths referenced throughout the resource table. This does - // not end up in the final binary ARSC file. - StringPool source_pool = 1; - - // Resource definitions corresponding to an Android package. - repeated Package package = 2; - - // The declarations within the resource table. - repeated Overlayable overlayable = 3; - - // The version fingerprints of the tools that built the resource table. - repeated ToolFingerprint tool_fingerprint = 4; -} - -// A package ID in the range [0x00, 0xff]. -message PackageId { - uint32 id = 1; -} - -// Defines resources for an Android package. -message Package { - // The package ID of this package, in the range [0x00, 0xff]. - // - ID 0x00 is reserved for shared libraries, or when the ID is assigned at run-time. - // - ID 0x01 is reserved for the 'android' package (framework). - // - ID range [0x02, 0x7f) is reserved for auto-assignment to shared libraries at run-time. - // - ID 0x7f is reserved for the application package. - // - IDs > 0x7f are reserved for the application as well and are treated as feature splits. - // This may not be set if no ID was assigned. - PackageId package_id = 1; - - // The Java compatible Android package name of the app. - string package_name = 2; - - // The series of types defined by the package. - repeated Type type = 3; -} - -// A type ID in the range [0x01, 0xff]. -message TypeId { - uint32 id = 1; -} - -// A set of resources grouped under a common type. Such types include string, layout, xml, dimen, -// attr, etc. This maps to the second part of a resource identifier in Java (R.type.entry). -message Type { - // The ID of the type. This may not be set if no ID was assigned. - TypeId type_id = 1; - - // The name of the type. This corresponds to the 'type' part of a full resource name of the form - // package:type/entry. The set of legal type names is listed in Resource.cpp. - string name = 2; - - // The entries defined for this type. - repeated Entry entry = 3; -} - -// The Visibility of a symbol/entry (public, private, undefined). -message Visibility { - // The visibility of the resource outside of its package. - enum Level { - // No visibility was explicitly specified. This is typically treated as private. - // The distinction is important when two separate R.java files are generated: a public and - // private one. An unknown visibility, in this case, would cause the resource to be omitted - // from either R.java. - UNKNOWN = 0; - - // A resource was explicitly marked as private. This means the resource can not be accessed - // outside of its package unless the @*package:type/entry notation is used (the asterisk being - // the private accessor). If two R.java files are generated (private + public), the resource - // will only be emitted to the private R.java file. - PRIVATE = 1; - - // A resource was explicitly marked as public. This means the resource can be accessed - // from any package, and is emitted into all R.java files, public and private. - PUBLIC = 2; - } - - Level level = 1; - - // The path at which this entry's visibility was defined (eg. public.xml). - Source source = 2; - - // The comment associated with the tag. - string comment = 3; - - // Indicates that the resource id may change across builds and that the public R.java identifier - // for this resource should not be final. This is set to `true` for resources in `staging-group` - // tags. - bool staged_api = 4; -} - -// Whether a resource comes from a compile-time overlay and is explicitly allowed to not overlay an -// existing resource. -message AllowNew { - // Where this was defined in source. - Source source = 1; - - // Any comment associated with the declaration. - string comment = 2; -} - -// Represents a set of overlayable resources. -message Overlayable { - // The name of the . - string name = 1; - - // The location of the declaration in the source. - Source source = 2; - - // The component responsible for enabling and disabling overlays targeting this . - string actor = 3; -} - -// Represents an overlayable declaration within an tag. -message OverlayableItem { - enum Policy { - NONE = 0; - PUBLIC = 1; - SYSTEM = 2; - VENDOR = 3; - PRODUCT = 4; - SIGNATURE = 5; - ODM = 6; - OEM = 7; - ACTOR = 8; - CONFIG_SIGNATURE = 9; - } - - // The location of the declaration in source. - Source source = 1; - - // Any comment associated with the declaration. - string comment = 2; - - // The policy defined by the enclosing tag of this . - repeated Policy policy = 3; - - // The index into overlayable list that points to the tag that contains - // this . - uint32 overlayable_idx = 4; -} - -// The staged resource ID definition of a finalized resource. -message StagedId { - Source source = 1; - uint32 staged_id = 2; -} - -// An entry ID in the range [0x0000, 0xffff]. -message EntryId { - uint32 id = 1; -} - -// An entry declaration. An entry has a full resource ID that is the combination of package ID, -// type ID, and its own entry ID. An entry on its own has no value, but values are defined for -// various configurations/variants. -message Entry { - // The ID of this entry. Together with the package ID and type ID, this forms a full resource ID - // of the form 0xPPTTEEEE, where PP is the package ID, TT is the type ID, and EEEE is the entry - // ID. - // This may not be set if no ID was assigned. - EntryId entry_id = 1; - - // The name of this entry. This corresponds to the 'entry' part of a full resource name of the - // form package:type/entry. - string name = 2; - - // The visibility of this entry (public, private, undefined). - Visibility visibility = 3; - - // Whether this resource, when originating from a compile-time overlay, is allowed to NOT overlay - // any existing resources. - AllowNew allow_new = 4; - - // Whether this resource can be overlaid by a runtime resource overlay (RRO). - OverlayableItem overlayable_item = 5; - - // The set of values defined for this entry, each corresponding to a different - // configuration/variant. - repeated ConfigValue config_value = 6; - - // The staged resource ID of this finalized resource. - StagedId staged_id = 7; -} - -// A Configuration/Value pair. -message ConfigValue { - Configuration config = 1; - Value value = 2; -} - -// The generic meta-data for every value in a resource table. -message Value { - // Where the value was defined. - Source source = 1; - - // Any comment associated with the value. - string comment = 2; - - // Whether the value can be overridden. - bool weak = 3; - - // The value is either an Item or a CompoundValue. - oneof value { - Item item = 4; - CompoundValue compound_value = 5; - } -} - -// An Item is an abstract type. It represents a value that can appear inline in many places, such -// as XML attribute values or on the right hand side of style attribute definitions. The concrete -// type is one of the types below. Only one can be set. -message Item { - oneof value { - Reference ref = 1; - String str = 2; - RawString raw_str = 3; - StyledString styled_str = 4; - FileReference file = 5; - Id id = 6; - Primitive prim = 7; - } -} - -// A CompoundValue is an abstract type. It represents a value that is a made of other values. -// These can only usually appear as top-level resources. The concrete type is one of the types -// below. Only one can be set. -message CompoundValue { - oneof value { - Attribute attr = 1; - Style style = 2; - Styleable styleable = 3; - Array array = 4; - Plural plural = 5; - MacroBody macro = 6; - } -} - -// Message holding a boolean, so it can be optionally encoded. -message Boolean { - bool value = 1; -} - -// A value that is a reference to another resource. This reference can be by name or resource ID. -message Reference { - enum Type { - // A plain reference (@package:type/entry). - REFERENCE = 0; - - // A reference to a theme attribute (?package:type/entry). - ATTRIBUTE = 1; - } - - Type type = 1; - - // The resource ID (0xPPTTEEEE) of the resource being referred. This is optional. - uint32 id = 2; - - // The name of the resource being referred. This is optional if the resource ID is set. - string name = 3; - - // Whether this reference is referencing a private resource (@*package:type/entry). - bool private = 4; - - // Whether this reference is dynamic. - Boolean is_dynamic = 5; - - // The type flags used when compiling the reference. Used for substituting the contents of macros. - uint32 type_flags = 6; - - // Whether raw string values would have been accepted in place of this reference definition. Used - // for substituting the contents of macros. - bool allow_raw = 7; -} - -// A value that represents an ID. This is just a placeholder, as ID values are used to occupy a -// resource ID (0xPPTTEEEE) as a unique identifier. Their value is unimportant. -message Id { -} - -// A value that is a string. -message String { - string value = 1; -} - -// A value that is a raw string, which is unescaped/uninterpreted. This is typically used to -// represent the value of a style attribute before the attribute is compiled and the set of -// allowed values is known. -message RawString { - string value = 1; -} - -// A string with styling information, like html tags that specify boldness, italics, etc. -message StyledString { - // The raw text of the string. - string value = 1; - - // A Span marks a region of the string text that is styled. - message Span { - // The name of the tag, and its attributes, encoded as follows: - // tag_name;attr1=value1;attr2=value2;[...] - string tag = 1; - - // The first character position this span applies to, in UTF-16 offset. - uint32 first_char = 2; - - // The last character position this span applies to, in UTF-16 offset. - uint32 last_char = 3; - } - - repeated Span span = 2; -} - -// A value that is a reference to an external entity, like an XML file or a PNG. -message FileReference { - enum Type { - UNKNOWN = 0; - PNG = 1; - BINARY_XML = 2; - PROTO_XML = 3; - } - - // Path to a file within the APK (typically res/type-config/entry.ext). - string path = 1; - - // The type of file this path points to. For UAM bundle, this cannot be - // BINARY_XML. - Type type = 2; -} - -// A value that represents a primitive data type (float, int, boolean, etc.). -// Refer to Res_value in ResourceTypes.h for info on types and formatting -message Primitive { - message NullType { - } - message EmptyType { - } - oneof oneof_value { - NullType null_value = 1; - EmptyType empty_value = 2; - float float_value = 3; - uint32 dimension_value = 13; - uint32 fraction_value = 14; - int32 int_decimal_value = 6; - uint32 int_hexadecimal_value = 7; - bool boolean_value = 8; - uint32 color_argb8_value = 9; - uint32 color_rgb8_value = 10; - uint32 color_argb4_value = 11; - uint32 color_rgb4_value = 12; - float dimension_value_deprecated = 4 [deprecated=true]; - float fraction_value_deprecated = 5 [deprecated=true]; - } -} - -// A value that represents an XML attribute and what values it accepts. -message Attribute { - // A Symbol used to represent an enum or a flag. - message Symbol { - // Where the enum/flag item was defined. - Source source = 1; - - // Any comments associated with the enum or flag. - string comment = 2; - - // The name of the enum/flag as a reference. Enums/flag items are generated as ID resource - // values. - Reference name = 3; - - // The value of the enum/flag. - uint32 value = 4; - - // The data type of the enum/flag as defined in android::Res_value. - uint32 type = 5; - } - - // Bitmask of formats allowed for an attribute. - enum FormatFlags { - NONE = 0x0; // Proto3 requires a default of 0. - ANY = 0x0000ffff; // Allows any type except ENUM and FLAGS. - REFERENCE = 0x01; // Allows Reference values. - STRING = 0x02; // Allows String/StyledString values. - INTEGER = 0x04; // Allows any integer BinaryPrimitive values. - BOOLEAN = 0x08; // Allows any boolean BinaryPrimitive values. - COLOR = 0x010; // Allows any color BinaryPrimitive values. - FLOAT = 0x020; // Allows any float BinaryPrimitive values. - DIMENSION = 0x040; // Allows any dimension BinaryPrimitive values. - FRACTION = 0x080; // Allows any fraction BinaryPrimitive values. - ENUM = 0x00010000; // Allows enums that are defined in the Attribute's symbols. - // ENUM and FLAGS cannot BOTH be set. - FLAGS = 0x00020000; // Allows flags that are defined in the Attribute's symbols. - // ENUM and FLAGS cannot BOTH be set. - } - - // A bitmask of types that this XML attribute accepts. Corresponds to the flags in the - // enum FormatFlags. - uint32 format_flags = 1; - - // The smallest integer allowed for this XML attribute. Only makes sense if the format includes - // FormatFlags::INTEGER. - int32 min_int = 2; - - // The largest integer allowed for this XML attribute. Only makes sense if the format includes - // FormatFlags::INTEGER. - int32 max_int = 3; - - // The set of enums/flags defined in this attribute. Only makes sense if the format includes - // either FormatFlags::ENUM or FormatFlags::FLAGS. Having both is an error. - repeated Symbol symbol = 4; -} - -// A value that represents a style. -message Style { - // An XML attribute/value pair defined in the style. - message Entry { - // Where the entry was defined. - Source source = 1; - - // Any comments associated with the entry. - string comment = 2; - - // A reference to the XML attribute. - Reference key = 3; - - // The Item defined for this XML attribute. - Item item = 4; - } - - // The optinal style from which this style inherits attributes. - Reference parent = 1; - - // The source file information of the parent inheritance declaration. - Source parent_source = 2; - - // The set of XML attribute/value pairs for this style. - repeated Entry entry = 3; -} - -// A value that represents a XML resource. These are not real resources and -// only end up as Java fields in the generated R.java. They do not end up in the binary ARSC file. -message Styleable { - // An attribute defined for this styleable. - message Entry { - // Where the attribute was defined within the block. - Source source = 1; - - // Any comments associated with the declaration. - string comment = 2; - - // The reference to the attribute. - Reference attr = 3; - } - - // The set of attribute declarations. - repeated Entry entry = 1; -} - -// A value that represents an array of resource values. -message Array { - // A single element of the array. - message Element { - // Where the element was defined. - Source source = 1; - - // Any comments associated with the element. - string comment = 2; - - // The value assigned to this element. - Item item = 3; - } - - // The list of array elements. - repeated Element element = 1; -} - -// A value that represents a string and its many variations based on plurality. -message Plural { - // The arity of the plural. - enum Arity { - ZERO = 0; - ONE = 1; - TWO = 2; - FEW = 3; - MANY = 4; - OTHER = 5; - } - - // The plural value for a given arity. - message Entry { - // Where the plural was defined. - Source source = 1; - - // Any comments associated with the plural. - string comment = 2; - - // The arity of the plural. - Arity arity = 3; - - // The value assigned to this plural. - Item item = 4; - } - - // The set of arity/plural mappings. - repeated Entry entry = 1; -} - -// Defines an abstract XmlNode that must be either an XmlElement, or -// a text node represented by a string. -message XmlNode { - oneof node { - XmlElement element = 1; - string text = 2; - } - - // Source line and column info. - SourcePosition source = 3; -} - -// An in an XML document. -message XmlElement { - // Namespaces defined on this element. - repeated XmlNamespace namespace_declaration = 1; - - // The namespace URI of this element. - string namespace_uri = 2; - - // The name of this element. - string name = 3; - - // The attributes of this element. - repeated XmlAttribute attribute = 4; - - // The children of this element. - repeated XmlNode child = 5; -} - -// A namespace declaration on an XmlElement (xmlns:android="http://..."). -message XmlNamespace { - string prefix = 1; - string uri = 2; - - // Source line and column info. - SourcePosition source = 3; -} - -// An attribute defined on an XmlElement (android:text="..."). -message XmlAttribute { - string namespace_uri = 1; - string name = 2; - string value = 3; - - // Source line and column info. - SourcePosition source = 4; - - // The optional resource ID (0xPPTTEEEE) of the attribute. - uint32 resource_id = 5; - - // The optional interpreted/compiled version of the `value` string. - Item compiled_item = 6; -} - -message MacroBody { - string raw_string = 1; - StyleString style_string = 2; - repeated UntranslatableSection untranslatable_sections = 3; - repeated NamespaceAlias namespace_stack = 4; - SourcePosition source = 5; -} - -message NamespaceAlias { - string prefix = 1; - string package_name = 2; - bool is_private = 3; -} - -message StyleString { - message Span { - string name = 1; - uint32 start_index = 2; - uint32 end_index = 3; - } - string str = 1; - repeated Span spans = 2; -} - -message UntranslatableSection { - uint64 start_index = 1; - uint64 end_index = 2; -} diff --git a/gradle-plugin/src/main/proto/aapt/pb/ResourcesInternal.proto b/gradle-plugin/src/main/proto/aapt/pb/ResourcesInternal.proto deleted file mode 100644 index e153c0a..0000000 --- a/gradle-plugin/src/main/proto/aapt/pb/ResourcesInternal.proto +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -syntax = "proto3"; - -import "aapt/pb/Configuration.proto"; -import "aapt/pb/Resources.proto"; - -package aapt.pb.internal; - -option java_package = "android.aapt.pb.internal"; - -// The top level message representing an external resource file (layout XML, PNG, etc). -// This is used to represent a compiled file before it is linked. Only useful to aapt2. -message CompiledFile { - message Symbol { - // The name of the symbol (in the form package:type/name). - string resource_name = 1; - - // The position in the file at which this symbol is defined. For debug use. - aapt.pb.SourcePosition source = 2; - } - - // The name of the resource (in the form package:type/name). - string resource_name = 1; - - // The configuration for which the resource is defined. - aapt.pb.Configuration config = 2; - - // The type of the file. - aapt.pb.FileReference.Type type = 3; - - // The filesystem path to where the source file originated. - // Mainly used to display helpful error messages. - string source_path = 4; - - // Any symbols this file auto-generates/exports (eg. @+id/foo in an XML file). - repeated Symbol exported_symbol = 5; -} diff --git a/gradle-plugin/src/test/fixtures/attr-rewrite-binary/app/.gitignore b/gradle-plugin/src/test/fixtures/attr-rewrite-binary/app/.gitignore deleted file mode 100644 index 42afabf..0000000 --- a/gradle-plugin/src/test/fixtures/attr-rewrite-binary/app/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build \ No newline at end of file diff --git a/gradle-plugin/src/test/fixtures/attr-rewrite-binary/app/build.gradle.kts b/gradle-plugin/src/test/fixtures/attr-rewrite-binary/app/build.gradle.kts deleted file mode 100644 index ab88828..0000000 --- a/gradle-plugin/src/test/fixtures/attr-rewrite-binary/app/build.gradle.kts +++ /dev/null @@ -1,43 +0,0 @@ -import com.android.build.gradle.tasks.PackageApplication - -plugins { - id("com.android.application") - id("org.jetbrains.kotlin.android") - id("com.google.devtools.ksp") - id("app.cash.better.dynamic.features") -} - -android { - namespace = "com.example.attr" - compileSdk = 33 - - defaultConfig { - applicationId = "com.example.attr" - minSdk = 24 - targetSdk = 33 - versionCode = 1 - versionName = "1.0" - - testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" - } - - buildTypes { - release { - isMinifyEnabled = false - proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") - } - } - buildFeatures { - viewBinding = true - } - dynamicFeatures += setOf(":feature") -} - -dependencies { - implementation("androidx.core:core-ktx:1.10.0") - implementation("com.google.android.material:material:1.8.0") - implementation("androidx.appcompat:appcompat:1.6.1") - implementation("androidx.constraintlayout:constraintlayout:2.1.4") - implementation("androidx.navigation:navigation-fragment-ktx:2.5.3") - implementation("androidx.navigation:navigation-ui-ktx:2.5.3") -} diff --git a/gradle-plugin/src/test/fixtures/attr-rewrite-binary/app/proguard-rules.pro b/gradle-plugin/src/test/fixtures/attr-rewrite-binary/app/proguard-rules.pro deleted file mode 100644 index 481bb43..0000000 --- a/gradle-plugin/src/test/fixtures/attr-rewrite-binary/app/proguard-rules.pro +++ /dev/null @@ -1,21 +0,0 @@ -# Add project specific ProGuard rules here. -# You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} - -# Uncomment this to preserve the line number information for -# debugging stack traces. -#-keepattributes SourceFile,LineNumberTable - -# If you keep the line number information, uncomment this to -# hide the original source file name. -#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/gradle-plugin/src/test/fixtures/attr-rewrite-binary/app/src/androidTest/java/com/example/attr/ExampleInstrumentedTest.kt b/gradle-plugin/src/test/fixtures/attr-rewrite-binary/app/src/androidTest/java/com/example/attr/ExampleInstrumentedTest.kt deleted file mode 100644 index 1e754c5..0000000 --- a/gradle-plugin/src/test/fixtures/attr-rewrite-binary/app/src/androidTest/java/com/example/attr/ExampleInstrumentedTest.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.example.attr - -import androidx.test.platform.app.InstrumentationRegistry -import androidx.test.ext.junit.runners.AndroidJUnit4 - -import org.junit.Test -import org.junit.runner.RunWith - -import org.junit.Assert.* - -/** - * Instrumented test, which will execute on an Android device. - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -@RunWith(AndroidJUnit4::class) -class ExampleInstrumentedTest { - @Test - fun useAppContext() { - // Context of the app under test. - val appContext = InstrumentationRegistry.getInstrumentation().targetContext - assertEquals("com.example.attr", appContext.packageName) - } -} \ No newline at end of file diff --git a/gradle-plugin/src/test/fixtures/attr-rewrite-binary/app/src/main/AndroidManifest.xml b/gradle-plugin/src/test/fixtures/attr-rewrite-binary/app/src/main/AndroidManifest.xml deleted file mode 100644 index 76795b6..0000000 --- a/gradle-plugin/src/test/fixtures/attr-rewrite-binary/app/src/main/AndroidManifest.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/gradle-plugin/src/test/fixtures/attr-rewrite-binary/app/src/main/java/com/example/attr/CustomTextView.kt b/gradle-plugin/src/test/fixtures/attr-rewrite-binary/app/src/main/java/com/example/attr/CustomTextView.kt deleted file mode 100644 index 93ce771..0000000 --- a/gradle-plugin/src/test/fixtures/attr-rewrite-binary/app/src/main/java/com/example/attr/CustomTextView.kt +++ /dev/null @@ -1,33 +0,0 @@ -package com.example.attr - -import android.app.Activity -import android.content.Context -import android.content.ContextWrapper -import android.util.AttributeSet -import android.util.Log -import androidx.appcompat.widget.AppCompatTextView - -class CustomTextView(context: Context, attrs: AttributeSet?) : AppCompatTextView(context, attrs) { - init { - val a = context.obtainStyledAttributes(attrs, R.styleable.CustomTextView) - val textAttribute = a.getString(R.styleable.CustomTextView_specialText) - this.text = textAttribute - - val containingActivity = findActivity() - Log.wtf("CustomTextView", "Initialized in $containingActivity, with specialText=$textAttribute") - - a.recycle() - } - - private fun findActivity(): Activity? { - var itContext = context - while (itContext is ContextWrapper) { - if (itContext is Activity) { - return itContext - } - itContext = itContext.baseContext - } - - return null - } -} diff --git a/gradle-plugin/src/test/fixtures/attr-rewrite-binary/app/src/main/java/com/example/attr/FirstFragment.kt b/gradle-plugin/src/test/fixtures/attr-rewrite-binary/app/src/main/java/com/example/attr/FirstFragment.kt deleted file mode 100644 index 77a483f..0000000 --- a/gradle-plugin/src/test/fixtures/attr-rewrite-binary/app/src/main/java/com/example/attr/FirstFragment.kt +++ /dev/null @@ -1,46 +0,0 @@ -package com.example.attr - -import android.content.Intent -import android.os.Bundle -import androidx.fragment.app.Fragment -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.navigation.fragment.findNavController -import com.example.attr.databinding.FragmentFirstBinding - -/** - * A simple [Fragment] subclass as the default destination in the navigation. - */ -class FirstFragment : Fragment() { - - private var _binding: FragmentFirstBinding? = null - - // This property is only valid between onCreateView and - // onDestroyView. - private val binding get() = _binding!! - - override fun onCreateView( - inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle? - ): View? { - - _binding = FragmentFirstBinding.inflate(inflater, container, false) - return binding.root - - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - binding.buttonFirst.setOnClickListener { - requireActivity().startActivity(Intent(requireContext(), Class.forName("com.example.attr.feature.FeatureActivity"))) -// findNavController().navigate(R.id.action_FirstFragment_to_SecondFragment) - } - } - - override fun onDestroyView() { - super.onDestroyView() - _binding = null - } -} \ No newline at end of file diff --git a/gradle-plugin/src/test/fixtures/attr-rewrite-binary/app/src/main/java/com/example/attr/MainActivity.kt b/gradle-plugin/src/test/fixtures/attr-rewrite-binary/app/src/main/java/com/example/attr/MainActivity.kt deleted file mode 100644 index b53c0e8..0000000 --- a/gradle-plugin/src/test/fixtures/attr-rewrite-binary/app/src/main/java/com/example/attr/MainActivity.kt +++ /dev/null @@ -1,58 +0,0 @@ -package com.example.attr - -import android.os.Bundle -import com.google.android.material.snackbar.Snackbar -import androidx.appcompat.app.AppCompatActivity -import androidx.navigation.findNavController -import androidx.navigation.ui.AppBarConfiguration -import androidx.navigation.ui.navigateUp -import androidx.navigation.ui.setupActionBarWithNavController -import android.view.Menu -import android.view.MenuItem -import com.example.attr.databinding.ActivityMainBinding - -class MainActivity : AppCompatActivity() { - - private lateinit var appBarConfiguration: AppBarConfiguration - private lateinit var binding: ActivityMainBinding - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - binding = ActivityMainBinding.inflate(layoutInflater) - setContentView(binding.root) - - setSupportActionBar(binding.toolbar) - - val navController = findNavController(R.id.nav_host_fragment_content_main) - appBarConfiguration = AppBarConfiguration(navController.graph) - setupActionBarWithNavController(navController, appBarConfiguration) - - binding.fab.setOnClickListener { view -> - Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG) - .setAction("Action", null).show() - } - } - - override fun onCreateOptionsMenu(menu: Menu): Boolean { - // Inflate the menu; this adds items to the action bar if it is present. - menuInflater.inflate(R.menu.menu_main, menu) - return true - } - - override fun onOptionsItemSelected(item: MenuItem): Boolean { - // Handle action bar item clicks here. The action bar will - // automatically handle clicks on the Home/Up button, so long - // as you specify a parent activity in AndroidManifest.xml. - return when (item.itemId) { - R.id.action_settings -> true - else -> super.onOptionsItemSelected(item) - } - } - - override fun onSupportNavigateUp(): Boolean { - val navController = findNavController(R.id.nav_host_fragment_content_main) - return navController.navigateUp(appBarConfiguration) - || super.onSupportNavigateUp() - } -} \ No newline at end of file diff --git a/gradle-plugin/src/test/fixtures/attr-rewrite-binary/app/src/main/java/com/example/attr/SecondFragment.kt b/gradle-plugin/src/test/fixtures/attr-rewrite-binary/app/src/main/java/com/example/attr/SecondFragment.kt deleted file mode 100644 index 8f1b163..0000000 --- a/gradle-plugin/src/test/fixtures/attr-rewrite-binary/app/src/main/java/com/example/attr/SecondFragment.kt +++ /dev/null @@ -1,44 +0,0 @@ -package com.example.attr - -import android.os.Bundle -import androidx.fragment.app.Fragment -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.navigation.fragment.findNavController -import com.example.attr.databinding.FragmentSecondBinding - -/** - * A simple [Fragment] subclass as the second destination in the navigation. - */ -class SecondFragment : Fragment() { - - private var _binding: FragmentSecondBinding? = null - - // This property is only valid between onCreateView and - // onDestroyView. - private val binding get() = _binding!! - - override fun onCreateView( - inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle? - ): View? { - - _binding = FragmentSecondBinding.inflate(inflater, container, false) - return binding.root - - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - binding.buttonSecond.setOnClickListener { - findNavController().navigate(R.id.action_SecondFragment_to_FirstFragment) - } - } - - override fun onDestroyView() { - super.onDestroyView() - _binding = null - } -} \ No newline at end of file diff --git a/gradle-plugin/src/test/fixtures/attr-rewrite-binary/app/src/main/res/drawable/ic_launcher_background.xml b/gradle-plugin/src/test/fixtures/attr-rewrite-binary/app/src/main/res/drawable/ic_launcher_background.xml deleted file mode 100644 index 07d5da9..0000000 --- a/gradle-plugin/src/test/fixtures/attr-rewrite-binary/app/src/main/res/drawable/ic_launcher_background.xml +++ /dev/null @@ -1,170 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/gradle-plugin/src/test/fixtures/attr-rewrite-binary/app/src/main/res/drawable/ic_launcher_foreground.xml b/gradle-plugin/src/test/fixtures/attr-rewrite-binary/app/src/main/res/drawable/ic_launcher_foreground.xml deleted file mode 100644 index 2b068d1..0000000 --- a/gradle-plugin/src/test/fixtures/attr-rewrite-binary/app/src/main/res/drawable/ic_launcher_foreground.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/gradle-plugin/src/test/fixtures/attr-rewrite-binary/app/src/main/res/layout/activity_main.xml b/gradle-plugin/src/test/fixtures/attr-rewrite-binary/app/src/main/res/layout/activity_main.xml deleted file mode 100644 index 16313e0..0000000 --- a/gradle-plugin/src/test/fixtures/attr-rewrite-binary/app/src/main/res/layout/activity_main.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/gradle-plugin/src/test/fixtures/attr-rewrite-binary/app/src/main/res/layout/content_main.xml b/gradle-plugin/src/test/fixtures/attr-rewrite-binary/app/src/main/res/layout/content_main.xml deleted file mode 100644 index e416e1c..0000000 --- a/gradle-plugin/src/test/fixtures/attr-rewrite-binary/app/src/main/res/layout/content_main.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - \ No newline at end of file diff --git a/gradle-plugin/src/test/fixtures/attr-rewrite-binary/app/src/main/res/layout/fragment_first.xml b/gradle-plugin/src/test/fixtures/attr-rewrite-binary/app/src/main/res/layout/fragment_first.xml deleted file mode 100644 index 5683660..0000000 --- a/gradle-plugin/src/test/fixtures/attr-rewrite-binary/app/src/main/res/layout/fragment_first.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - -