Support variants in Android Extensions (compiler plugin)
This commit is contained in:
+23
-18
@@ -25,10 +25,7 @@ import org.jetbrains.kotlin.android.synthetic.codegen.AndroidExpressionCodegenEx
|
||||
import org.jetbrains.kotlin.android.synthetic.codegen.AndroidOnDestroyClassBuilderInterceptorExtension
|
||||
import org.jetbrains.kotlin.android.synthetic.diagnostic.AndroidExtensionPropertiesCallChecker
|
||||
import org.jetbrains.kotlin.android.synthetic.diagnostic.DefaultErrorMessagesAndroid
|
||||
import org.jetbrains.kotlin.android.synthetic.res.AndroidLayoutXmlFileManager
|
||||
import org.jetbrains.kotlin.android.synthetic.res.CliAndroidLayoutXmlFileManager
|
||||
import org.jetbrains.kotlin.android.synthetic.res.CliSyntheticFileGenerator
|
||||
import org.jetbrains.kotlin.android.synthetic.res.SyntheticFileGenerator
|
||||
import org.jetbrains.kotlin.android.synthetic.res.*
|
||||
import org.jetbrains.kotlin.codegen.extensions.ClassBuilderInterceptorExtension
|
||||
import org.jetbrains.kotlin.codegen.extensions.ExpressionCodegenExtension
|
||||
import org.jetbrains.kotlin.compiler.plugin.CliOption
|
||||
@@ -47,30 +44,30 @@ import org.jetbrains.kotlin.resolve.TargetPlatform
|
||||
import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatform
|
||||
|
||||
public object AndroidConfigurationKeys {
|
||||
public val ANDROID_RES_PATH: CompilerConfigurationKey<List<String>> = CompilerConfigurationKey.create<List<String>>("android resources search path")
|
||||
public val ANDROID_MANIFEST: CompilerConfigurationKey<String> = CompilerConfigurationKey.create<String>("android manifest file")
|
||||
public val VARIANT: CompilerConfigurationKey<List<String>> = CompilerConfigurationKey.create<List<String>>("Android build variant")
|
||||
public val PACKAGE: CompilerConfigurationKey<String> = CompilerConfigurationKey.create<String>("application package fq name")
|
||||
}
|
||||
|
||||
public class AndroidCommandLineProcessor : CommandLineProcessor {
|
||||
companion object {
|
||||
public val ANDROID_COMPILER_PLUGIN_ID: String = "org.jetbrains.kotlin.android"
|
||||
|
||||
public val RESOURCE_PATH_OPTION: CliOption = CliOption("androidRes", "<path>", "Android resources path", allowMultipleOccurrences = true)
|
||||
public val MANIFEST_FILE_OPTION: CliOption = CliOption("androidManifest", "<path>", "Android manifest file")
|
||||
public val VARIANT_OPTION: CliOption = CliOption("variant", "<name;path>", "Android build variant", allowMultipleOccurrences = true)
|
||||
public val PACKAGE_OPTION: CliOption = CliOption("package", "<fq name>", "Application package")
|
||||
}
|
||||
|
||||
override val pluginId: String = ANDROID_COMPILER_PLUGIN_ID
|
||||
|
||||
override val pluginOptions: Collection<CliOption> = listOf(RESOURCE_PATH_OPTION, MANIFEST_FILE_OPTION)
|
||||
override val pluginOptions: Collection<CliOption> = listOf(VARIANT_OPTION, PACKAGE_OPTION)
|
||||
|
||||
override fun processOption(option: CliOption, value: String, configuration: CompilerConfiguration) {
|
||||
when (option) {
|
||||
RESOURCE_PATH_OPTION -> {
|
||||
val paths = configuration.getList(AndroidConfigurationKeys.ANDROID_RES_PATH).toArrayList()
|
||||
VARIANT_OPTION -> {
|
||||
val paths = configuration.getList(AndroidConfigurationKeys.VARIANT).toArrayList()
|
||||
paths.add(value)
|
||||
configuration.put(AndroidConfigurationKeys.ANDROID_RES_PATH, paths)
|
||||
configuration.put(AndroidConfigurationKeys.VARIANT, paths)
|
||||
}
|
||||
MANIFEST_FILE_OPTION -> configuration.put(AndroidConfigurationKeys.ANDROID_MANIFEST, value)
|
||||
PACKAGE_OPTION -> configuration.put(AndroidConfigurationKeys.PACKAGE, value)
|
||||
else -> throw CliOptionProcessingException("Unknown option: ${option.name}")
|
||||
}
|
||||
}
|
||||
@@ -86,14 +83,16 @@ public class CliAndroidDeclarationsProvider(private val project: Project) : Exte
|
||||
public class AndroidComponentRegistrar : ComponentRegistrar {
|
||||
|
||||
public override fun registerProjectComponents(project: MockProject, configuration: CompilerConfiguration) {
|
||||
val androidResPath = configuration.get(AndroidConfigurationKeys.ANDROID_RES_PATH)
|
||||
val androidManifest = configuration.get(AndroidConfigurationKeys.ANDROID_MANIFEST)
|
||||
val applicationPackage = configuration.get(AndroidConfigurationKeys.PACKAGE)
|
||||
val variants = configuration.get(AndroidConfigurationKeys.VARIANT)?.map { parseVariant(it) }?.filterNotNull() ?: emptyList()
|
||||
|
||||
if (androidResPath != null && androidManifest != null) {
|
||||
val xmlProcessor = CliSyntheticFileGenerator(project, androidManifest, androidResPath)
|
||||
if (variants.isNotEmpty() && !applicationPackage.isNullOrBlank()) {
|
||||
val xmlProcessor = CliSyntheticFileGenerator(project, applicationPackage!!, variants)
|
||||
|
||||
project.registerService(SyntheticFileGenerator::class.java, xmlProcessor)
|
||||
project.registerService(AndroidLayoutXmlFileManager::class.java, CliAndroidLayoutXmlFileManager(project, androidManifest, androidResPath))
|
||||
|
||||
val layoutXmlFileManager = CliAndroidLayoutXmlFileManager(project, applicationPackage, variants)
|
||||
project.registerService(AndroidLayoutXmlFileManager::class.java, layoutXmlFileManager)
|
||||
|
||||
ExternalDeclarationsProvider.registerExtension(project, CliAndroidDeclarationsProvider(project))
|
||||
ExpressionCodegenExtension.registerExtension(project, AndroidExpressionCodegenExtension())
|
||||
@@ -102,6 +101,12 @@ public class AndroidComponentRegistrar : ComponentRegistrar {
|
||||
ClassBuilderInterceptorExtension.registerExtension(project, AndroidOnDestroyClassBuilderInterceptorExtension())
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseVariant(s: String): AndroidVariant? {
|
||||
val parts = s.split(';')
|
||||
if (parts.size < 2) return null
|
||||
return AndroidVariant(parts[0], parts.drop(0))
|
||||
}
|
||||
}
|
||||
|
||||
public class AndroidExtensionPropertiesComponentContainerContributor : StorageComponentContainerContributor {
|
||||
|
||||
+2
-2
@@ -34,7 +34,7 @@ open class Context(val buffer: StringBuffer = StringBuffer(), private var indent
|
||||
indentDepth--
|
||||
if (indentDepth < 0)
|
||||
throw InvalidIndent(indentDepth)
|
||||
currentIndent = currentIndent.substring(0, currentIndent.length() - indentUnit.length())
|
||||
currentIndent = currentIndent.substring(0, currentIndent.length - indentUnit.length)
|
||||
}
|
||||
|
||||
public open fun write(what: String) {
|
||||
@@ -57,7 +57,7 @@ open class Context(val buffer: StringBuffer = StringBuffer(), private var indent
|
||||
|
||||
|
||||
public fun trim(num: Int) {
|
||||
buffer.delete(buffer.length() - num, buffer.length())
|
||||
buffer.delete(buffer.length - num, buffer.length())
|
||||
}
|
||||
|
||||
public fun fork(newBuffer: StringBuffer = StringBuffer(),
|
||||
|
||||
+1
-1
@@ -32,7 +32,7 @@ class KotlinStringWriter : KotlinWriter {
|
||||
body.writeln("val $name: $retType")
|
||||
body.incIndent()
|
||||
body.write("get() ")
|
||||
if (getterBody.size() > 1) {
|
||||
if (getterBody.size > 1) {
|
||||
body.writeNoIndent("{\n")
|
||||
body.incIndent()
|
||||
for (stmt in getterBody) {
|
||||
|
||||
+18
-8
@@ -27,15 +27,25 @@ import com.intellij.psi.PsiManager
|
||||
import org.jetbrains.kotlin.psi.KtProperty
|
||||
import java.util.*
|
||||
|
||||
public abstract class AndroidLayoutXmlFileManager(val project: Project) {
|
||||
class AndroidVariantData(val variant: AndroidVariant, private val layouts: Map<String, List<PsiFile>>): Map<String, List<PsiFile>> by layouts
|
||||
class AndroidModuleData(val module: AndroidModule, private val variants: List<AndroidVariantData>): Iterable<AndroidVariantData> by variants {
|
||||
companion object {
|
||||
val EMPTY = AndroidModuleData(AndroidModule("android", listOf()), listOf())
|
||||
}
|
||||
}
|
||||
|
||||
public abstract val androidModuleInfo: AndroidModuleInfo?
|
||||
abstract class AndroidLayoutXmlFileManager(val project: Project) {
|
||||
|
||||
public abstract val androidModule: AndroidModule?
|
||||
|
||||
public open fun propertyToXmlAttributes(property: KtProperty): List<PsiElement> = listOf()
|
||||
|
||||
public fun getLayoutXmlFiles(): Map<String, List<PsiFile>> {
|
||||
val info = androidModuleInfo ?: return mapOf()
|
||||
public fun getLayoutXmlFiles(): AndroidModuleData {
|
||||
val androidModule = androidModule ?: return AndroidModuleData.EMPTY
|
||||
return AndroidModuleData(androidModule, androidModule.variants.map { getVariantData(it) })
|
||||
}
|
||||
|
||||
public fun getVariantData(variant: AndroidVariant): AndroidVariantData {
|
||||
val psiManager = PsiManager.getInstance(project)
|
||||
val fileManager = VirtualFileManager.getInstance()
|
||||
|
||||
@@ -53,11 +63,11 @@ public abstract class AndroidLayoutXmlFileManager(val project: Project) {
|
||||
return allChildren
|
||||
}
|
||||
|
||||
val resDirectories = info.resDirectories.map { fileManager.findFileByUrl("file://$it") }
|
||||
val resDirectories = variant.resDirectories.map { fileManager.findFileByUrl("file://$it") }
|
||||
val allChildren = resDirectories.flatMap { it?.getAllChildren() ?: listOf() }
|
||||
|
||||
val allLayoutFiles = allChildren.filter { it.parent.name.startsWith("layout") && it.name.toLowerCase().endsWith(".xml") }
|
||||
val allLayoutPsiFiles = allLayoutFiles.fold(ArrayList<PsiFile>(allLayoutFiles.size())) { list, file ->
|
||||
val allLayoutPsiFiles = allLayoutFiles.fold(ArrayList<PsiFile>(allLayoutFiles.size)) { list, file ->
|
||||
val psiFile = psiManager.findFile(file)
|
||||
if (psiFile != null && psiFile.parent != null) {
|
||||
list += psiFile
|
||||
@@ -67,9 +77,9 @@ public abstract class AndroidLayoutXmlFileManager(val project: Project) {
|
||||
|
||||
val layoutNameToXmlFiles = allLayoutPsiFiles
|
||||
.groupBy { it.name.substringBeforeLast('.') }
|
||||
.mapValues { it.getValue().sortedBy { it.parent!!.name.length() } }
|
||||
.mapValues { it.value.sortedBy { it.parent!!.name.length } }
|
||||
|
||||
return layoutNameToXmlFiles
|
||||
return AndroidVariantData(variant, layoutNameToXmlFiles)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
+3
-32
@@ -17,23 +17,16 @@
|
||||
package org.jetbrains.kotlin.android.synthetic.res
|
||||
|
||||
import com.intellij.openapi.project.Project
|
||||
import org.jetbrains.kotlin.android.synthetic.toMap
|
||||
import org.xml.sax.Attributes
|
||||
import org.xml.sax.helpers.DefaultHandler
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
import javax.xml.parsers.SAXParser
|
||||
import javax.xml.parsers.SAXParserFactory
|
||||
|
||||
public class CliAndroidLayoutXmlFileManager(
|
||||
project: Project,
|
||||
private val manifestPath: String,
|
||||
private val resDirectories: List<String>
|
||||
private val applicationPackage: String,
|
||||
private val variants: List<AndroidVariant>
|
||||
) : AndroidLayoutXmlFileManager(project) {
|
||||
|
||||
override val androidModuleInfo by lazy {
|
||||
AndroidModuleInfo(getApplicationPackage(manifestPath), resDirectories)
|
||||
}
|
||||
override val androidModule by lazy { AndroidModule(applicationPackage, variants) }
|
||||
|
||||
val saxParser: SAXParser = initSAX()
|
||||
|
||||
@@ -43,26 +36,4 @@ public class CliAndroidLayoutXmlFileManager(
|
||||
return saxFactory.newSAXParser()
|
||||
}
|
||||
|
||||
private fun getApplicationPackage(manifestPath: String): String {
|
||||
try {
|
||||
val manifestXml = File(manifestPath)
|
||||
var applicationPackage: String = ""
|
||||
try {
|
||||
saxParser.parse(FileInputStream(manifestXml), object : DefaultHandler() {
|
||||
override fun startElement(uri: String, localName: String, qName: String, attributes: Attributes) {
|
||||
if (localName == "manifest")
|
||||
applicationPackage = attributes.toMap()["package"] ?: ""
|
||||
}
|
||||
})
|
||||
}
|
||||
catch (e: Exception) {
|
||||
throw e
|
||||
}
|
||||
return applicationPackage
|
||||
}
|
||||
catch (e: Exception) {
|
||||
throw SyntheticFileGenerator.NoAndroidManifestFound()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+3
-3
@@ -20,17 +20,17 @@ import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.psi.PsiElementFinder
|
||||
import com.intellij.psi.PsiFile
|
||||
import java.io.ByteArrayInputStream
|
||||
import com.intellij.psi.impl.PsiElementFinderImpl
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import org.jetbrains.kotlin.android.synthetic.AndroidConst
|
||||
import org.jetbrains.kotlin.android.synthetic.AndroidXmlHandler
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import java.io.ByteArrayInputStream
|
||||
|
||||
public open class CliSyntheticFileGenerator(
|
||||
project: Project,
|
||||
private val manifestPath: String,
|
||||
private val resDirectories: List<String>
|
||||
private val variants: List<AndroidVariant>
|
||||
) : SyntheticFileGenerator(project) {
|
||||
|
||||
private val cachedJetFiles by lazy {
|
||||
@@ -40,7 +40,7 @@ public open class CliSyntheticFileGenerator(
|
||||
}
|
||||
|
||||
override val layoutXmlFileManager: CliAndroidLayoutXmlFileManager by lazy {
|
||||
CliAndroidLayoutXmlFileManager(project, manifestPath, resDirectories)
|
||||
CliAndroidLayoutXmlFileManager(project, manifestPath, variants)
|
||||
}
|
||||
|
||||
public override fun getSyntheticFiles(): List<KtFile> = cachedJetFiles
|
||||
|
||||
+48
-21
@@ -47,16 +47,18 @@ public abstract class SyntheticFileGenerator(protected val project: Project) {
|
||||
protected open fun generateSyntheticFiles(generateCommonFiles: Boolean, supportV4: Boolean): List<AndroidSyntheticFile> {
|
||||
val commonFiles = if (generateCommonFiles) generateCommonFiles(supportV4) else listOf()
|
||||
|
||||
return layoutXmlFileManager.getLayoutXmlFiles().flatMap { entry ->
|
||||
val files = entry.getValue()
|
||||
val resources = extractLayoutResources(files)
|
||||
return layoutXmlFileManager.getLayoutXmlFiles().flatMap { variantData ->
|
||||
variantData.flatMap { entry ->
|
||||
val files = entry.value
|
||||
val resources = extractLayoutResources(files)
|
||||
|
||||
val layoutName = entry.getKey()
|
||||
val layoutName = entry.key
|
||||
|
||||
val mainLayoutFile = renderMainLayoutFile(layoutName, resources, supportV4)
|
||||
val viewLayoutFile = renderViewLayoutFile(layoutName, resources)
|
||||
|
||||
listOf(mainLayoutFile, viewLayoutFile)
|
||||
arrayListOf<AndroidSyntheticFile>().apply {
|
||||
this += renderMainLayoutFiles(variantData.variant, layoutName, resources, supportV4)
|
||||
this += renderViewLayoutFiles(variantData.variant, layoutName, resources)
|
||||
}
|
||||
}
|
||||
}.filterNotNull() + commonFiles
|
||||
}
|
||||
|
||||
@@ -79,34 +81,59 @@ public abstract class SyntheticFileGenerator(protected val project: Project) {
|
||||
|
||||
protected abstract fun checkIfClassExist(fqName: String): Boolean
|
||||
|
||||
private fun renderMainLayoutFile(layoutName: String, resources: List<AndroidResource>, supportV4: Boolean): AndroidSyntheticFile {
|
||||
return renderLayoutFile(layoutName + AndroidConst.LAYOUT_POSTFIX, escapeAndroidIdentifier(layoutName), resources) {
|
||||
private fun renderMainLayoutFiles(
|
||||
variant: AndroidVariant,
|
||||
layoutName: String,
|
||||
resources: List<AndroidResource>,
|
||||
supportV4: Boolean
|
||||
): List<AndroidSyntheticFile> {
|
||||
return renderLayoutFile(variant, layoutName + AndroidConst.LAYOUT_POSTFIX, escapeAndroidIdentifier(layoutName), resources) {
|
||||
val properties = it.mainProperties.toArrayList()
|
||||
if (supportV4) properties.addAll(it.mainPropertiesForSupportV4)
|
||||
properties
|
||||
}
|
||||
}
|
||||
|
||||
private fun renderViewLayoutFile(layoutName: String, resources: List<AndroidResource>): AndroidSyntheticFile {
|
||||
return renderLayoutFile(layoutName + AndroidConst.VIEW_LAYOUT_POSTFIX,
|
||||
private fun renderViewLayoutFiles(
|
||||
variant: AndroidVariant,
|
||||
layoutName: String,
|
||||
resources: List<AndroidResource>
|
||||
): List<AndroidSyntheticFile> {
|
||||
return renderLayoutFile(variant, layoutName + AndroidConst.VIEW_LAYOUT_POSTFIX,
|
||||
escapeAndroidIdentifier(layoutName) + ".view", resources) { it.viewProperties }
|
||||
}
|
||||
|
||||
private fun renderLayoutFile(
|
||||
variant: AndroidVariant,
|
||||
filename: String,
|
||||
packageSegment: String,
|
||||
layoutName: String,
|
||||
resources: List<AndroidResource>,
|
||||
properties: (AndroidResource) -> List<Pair<String, String>>): AndroidSyntheticFile {
|
||||
return renderSyntheticFile(filename) {
|
||||
writePackage(AndroidConst.SYNTHETIC_PACKAGE + "." + packageSegment)
|
||||
writeAndroidImports()
|
||||
properties: (AndroidResource) -> List<Pair<String, String>>): List<AndroidSyntheticFile> {
|
||||
fun render(defaultVariant: Boolean = false): AndroidSyntheticFile {
|
||||
val fullFilename = (if (defaultVariant) "" else "${variant.name}_") + filename
|
||||
return renderSyntheticFile(fullFilename) {
|
||||
val packageName = if (defaultVariant)
|
||||
AndroidConst.SYNTHETIC_PACKAGE + "." + layoutName
|
||||
else
|
||||
AndroidConst.SYNTHETIC_PACKAGE + '.' + variant.name + '.' + layoutName
|
||||
|
||||
for (res in resources) {
|
||||
properties(res).forEach { property ->
|
||||
writeSyntheticProperty(property.first, res, property.second)
|
||||
writePackage(packageName)
|
||||
writeAndroidImports()
|
||||
|
||||
for (res in resources) {
|
||||
properties(res).forEach { property ->
|
||||
if (defaultVariant) {
|
||||
val deprecatedText = "Use the property from the 'main' variant instead"
|
||||
val import = AndroidConst.SYNTHETIC_PACKAGE + '.' + variant.name + '.' + layoutName + '.' + res.id
|
||||
writeText("@Deprecated(\"$deprecatedText\", ReplaceWith(\"${res.id}\", \"$import\"))")
|
||||
}
|
||||
writeSyntheticProperty(property.first, res, property.second)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return if (variant.isMainVariant) listOf(render(), render(true)) else listOf(render())
|
||||
}
|
||||
|
||||
private fun renderSyntheticFile(filename: String, init: KotlinStringWriter.() -> Unit): AndroidSyntheticFile {
|
||||
@@ -199,7 +226,7 @@ public abstract class SyntheticFileGenerator(protected val project: Project) {
|
||||
|
||||
protected fun generateSyntheticJetFiles(files: List<AndroidSyntheticFile>): List<KtFile> {
|
||||
val psiManager = PsiManager.getInstance(project)
|
||||
val applicationPackage = layoutXmlFileManager.androidModuleInfo?.applicationPackage
|
||||
val applicationPackage = layoutXmlFileManager.androidModule?.applicationPackage
|
||||
|
||||
return files.mapIndexed { index, syntheticFile ->
|
||||
val fileName = AndroidConst.SYNTHETIC_FILENAME_PREFIX + syntheticFile.name + ".kt"
|
||||
|
||||
+10
-4
@@ -18,13 +18,19 @@ package org.jetbrains.kotlin.android.synthetic.res
|
||||
|
||||
import org.jetbrains.kotlin.android.synthetic.AndroidConst
|
||||
|
||||
public class AndroidModuleInfo(val applicationPackage: String, resDirectories: List<String>) {
|
||||
class AndroidVariant(val name: String, val resDirectories: List<String>) {
|
||||
val packageName: String = name
|
||||
val isMainVariant: Boolean
|
||||
get() = name == "main"
|
||||
|
||||
override fun equals(other: Any?) = other is AndroidModuleInfo && applicationPackage == other.applicationPackage
|
||||
companion object {
|
||||
fun createMainVariant(resDirectories: List<String>) = AndroidVariant("main", resDirectories)
|
||||
}
|
||||
}
|
||||
|
||||
public class AndroidModule(val applicationPackage: String, val variants: List<AndroidVariant>) {
|
||||
override fun equals(other: Any?) = other is AndroidModule && applicationPackage == other.applicationPackage
|
||||
override fun hashCode() = applicationPackage.hashCode()
|
||||
|
||||
val resDirectories = resDirectories.sorted()
|
||||
}
|
||||
|
||||
public abstract class AndroidResource(val id: String) {
|
||||
|
||||
Reference in New Issue
Block a user