diff --git a/compiler/testData/cli/jvm/pluginSimpleUsage.out b/compiler/testData/cli/jvm/pluginSimpleUsage.out index 87abb501352..6120b1bfeb6 100644 --- a/compiler/testData/cli/jvm/pluginSimpleUsage.out +++ b/compiler/testData/cli/jvm/pluginSimpleUsage.out @@ -3,5 +3,6 @@ ERROR: Required plugin option not present: org.jetbrains.kotlin.android:androidR Plugin "org.jetbrains.kotlin.android" usage: androidRes Android resources path (required) androidManifest Android manifest file (required) + supportV4 Support android-v4 library COMPILATION_ERROR \ No newline at end of file diff --git a/libraries/tools/kotlin-android-extensions/src/main/kotlin/org/jetbrains/kotlin/android/AndroidSubplugin.kt b/libraries/tools/kotlin-android-extensions/src/main/kotlin/org/jetbrains/kotlin/android/AndroidSubplugin.kt index 054309459b5..2bd021789e3 100644 --- a/libraries/tools/kotlin-android-extensions/src/main/kotlin/org/jetbrains/kotlin/android/AndroidSubplugin.kt +++ b/libraries/tools/kotlin-android-extensions/src/main/kotlin/org/jetbrains/kotlin/android/AndroidSubplugin.kt @@ -36,11 +36,16 @@ public class AndroidSubplugin : KotlinGradleSubplugin { val resourceDir = mainSourceSet.getRes().getSrcDirs().firstOrNull() val manifestFile = mainSourceSet.getManifest().getSrcFile() + val compileDependencies = project.getConfigurations().getByName("compile").getFiles() + val supportV4 = compileDependencies?.filter { it.name.startsWith("support-v4-") }?.isNotEmpty() ?: false + val supportV4Property = if (supportV4) "true" else "false" + if (resourceDir != null) { resourceDir.listFiles { it.isDirectory() && it.name.startsWith("layout") }?.forEach { task.source(it) } return listOf( SubpluginOption("androidRes", resourceDir.getAbsolutePath()), - SubpluginOption("androidManifest", manifestFile.getAbsolutePath()) + SubpluginOption("androidManifest", manifestFile.getAbsolutePath()), + SubpluginOption("supportV4", supportV4Property) ) } diff --git a/plugins/android-compiler-plugin/src/AndroidComponentRegistrar.kt b/plugins/android-compiler-plugin/src/AndroidComponentRegistrar.kt index 35bc2c66983..f193efe4f4e 100644 --- a/plugins/android-compiler-plugin/src/AndroidComponentRegistrar.kt +++ b/plugins/android-compiler-plugin/src/AndroidComponentRegistrar.kt @@ -38,6 +38,8 @@ public object AndroidConfigurationKeys { public val ANDROID_RES_PATH: CompilerConfigurationKey = CompilerConfigurationKey.create("android resources search path") public val ANDROID_MANIFEST: CompilerConfigurationKey = CompilerConfigurationKey.create("android manifest file") + + public val SUPPORT_V4: CompilerConfigurationKey = CompilerConfigurationKey.create("'true' if compiled with support-v4 library") } public class AndroidCommandLineProcessor : CommandLineProcessor { @@ -46,16 +48,18 @@ public class AndroidCommandLineProcessor : CommandLineProcessor { public val RESOURCE_PATH_OPTION: CliOption = CliOption("androidRes", "", "Android resources path") public val MANIFEST_FILE_OPTION: CliOption = CliOption("androidManifest", "", "Android manifest file") + public val SUPPORT_V4_OPTION: CliOption = CliOption("supportV4", "", "Support android-v4 library", required = false) } override val pluginId: String = ANDROID_COMPILER_PLUGIN_ID - override val pluginOptions: Collection = listOf(RESOURCE_PATH_OPTION, MANIFEST_FILE_OPTION) + override val pluginOptions: Collection = listOf(RESOURCE_PATH_OPTION, MANIFEST_FILE_OPTION, SUPPORT_V4_OPTION) override fun processOption(option: CliOption, value: String, configuration: CompilerConfiguration) { when (option) { RESOURCE_PATH_OPTION -> configuration.put(AndroidConfigurationKeys.ANDROID_RES_PATH, value) MANIFEST_FILE_OPTION -> configuration.put(AndroidConfigurationKeys.ANDROID_MANIFEST, value) + SUPPORT_V4_OPTION -> configuration.put(AndroidConfigurationKeys.SUPPORT_V4, value) else -> throw CliOptionProcessingException("Unknown option: ${option.name}") } } @@ -73,9 +77,13 @@ 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 supportV4 = configuration.get(AndroidConfigurationKeys.SUPPORT_V4) ?: "false" if (androidResPath != null && androidManifest != null) { - project.registerService(javaClass(), CliAndroidUIXmlProcessor(project, androidManifest, androidResPath)) + val xmlProcessor = CliAndroidUIXmlProcessor(project, androidManifest, androidResPath) + if (supportV4 == "true") xmlProcessor.supportV4 = true + + project.registerService(javaClass(), xmlProcessor) project.registerService(javaClass(), CliAndroidResourceManager(project, androidManifest, androidResPath)) ExternalDeclarationsProvider.registerExtension(project, CliAndroidDeclarationsProvider(project)) diff --git a/plugins/android-compiler-plugin/src/AndroidExpressionCodegenExtension.kt b/plugins/android-compiler-plugin/src/AndroidExpressionCodegenExtension.kt index 4f6bf3d347b..0c8487a8708 100644 --- a/plugins/android-compiler-plugin/src/AndroidExpressionCodegenExtension.kt +++ b/plugins/android-compiler-plugin/src/AndroidExpressionCodegenExtension.kt @@ -44,12 +44,16 @@ import org.jetbrains.org.objectweb.asm.Type import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter private enum class AndroidClassType(val internalClassName: String, val supportsCache: Boolean = false) { - ACTIVITY : AndroidClassType("android/app/Activity", true) - FRAGMENT : AndroidClassType("android/app/Fragment", true) - VIEW : AndroidClassType("android/view/View") + ACTIVITY : AndroidClassType(AndroidConst.ACTIVITY_FQNAME.innerName, true) + FRAGMENT : AndroidClassType(AndroidConst.FRAGMENT_FQNAME.innerName, true) + SUPPORT_FRAGMENT : AndroidClassType(AndroidConst.SUPPORT_FRAGMENT_FQNAME.innerName, true) + VIEW : AndroidClassType(AndroidConst.VIEW_FQNAME.innerName) UNKNOWN : AndroidClassType("") } +private val String.innerName: String + get() = replace('.', '/') + public class AndroidExpressionCodegenExtension : ExpressionCodegenExtension { companion object { private val PROPERTY_NAME = "_\$_findViewCache" @@ -145,7 +149,7 @@ public class AndroidExpressionCodegenExtension : ExpressionCodegenExtension { v.getstatic(androidPackage.replace(".", "/") + "/R\$id", descriptor.getName().asString(), "I") v.invokevirtual(androidClassType.internalClassName, "findViewById", "(I)Landroid/view/View;", false) } - AndroidClassType.FRAGMENT -> { + AndroidClassType.FRAGMENT, AndroidClassType.SUPPORT_FRAGMENT -> { receiver.put(Type.getType("L${androidClassType.internalClassName};"), v) v.invokevirtual(androidClassType.internalClassName, "getView", "()Landroid/view/View;", false) v.getstatic(androidPackage.replace(".", "/") + "/R\$id", descriptor.getName().asString(), "I") @@ -169,9 +173,10 @@ public class AndroidExpressionCodegenExtension : ExpressionCodegenExtension { private fun getClassType(descriptor: ClassifierDescriptor): AndroidClassType { fun getClassTypeInternal(name: String): AndroidClassType? = when (name) { - "android.app.Activity" -> AndroidClassType.ACTIVITY - "android.app.Fragment" -> AndroidClassType.FRAGMENT - "android.view.View" -> AndroidClassType.VIEW + AndroidConst.ACTIVITY_FQNAME -> AndroidClassType.ACTIVITY + AndroidConst.FRAGMENT_FQNAME -> AndroidClassType.FRAGMENT + AndroidConst.SUPPORT_FRAGMENT_FQNAME -> AndroidClassType.SUPPORT_FRAGMENT + AndroidConst.VIEW_FQNAME -> AndroidClassType.VIEW else -> null } @@ -291,7 +296,7 @@ public class AndroidExpressionCodegenExtension : ExpressionCodegenExtension { loadId() iv.invokevirtual(className, "findViewById", "(I)Landroid/view/View;", false) } - AndroidClassType.FRAGMENT -> { + AndroidClassType.FRAGMENT, AndroidClassType.SUPPORT_FRAGMENT -> { iv.invokevirtual(className, "getView", "()Landroid/view/View;", false) loadId() iv.invokevirtual("android/view/View", "findViewById", "(I)Landroid/view/View;", false) diff --git a/plugins/android-compiler-plugin/src/org/jetbrains/kotlin/lang/resolve/android/AndroidConst.kt b/plugins/android-compiler-plugin/src/org/jetbrains/kotlin/lang/resolve/android/AndroidConst.kt index d6a2967af19..923e3ef5334 100644 --- a/plugins/android-compiler-plugin/src/org/jetbrains/kotlin/lang/resolve/android/AndroidConst.kt +++ b/plugins/android-compiler-plugin/src/org/jetbrains/kotlin/lang/resolve/android/AndroidConst.kt @@ -41,6 +41,12 @@ public object AndroidConst { val CLEAR_FUNCTION_NAME = "clearFindViewByIdCache" + val VIEW_FQNAME = "android.view.View" + val ACTIVITY_FQNAME = "android.app.Activity" + val FRAGMENT_FQNAME = "android.app.Fragment" + val SUPPORT_V4_PACKAGE = "android.support.v4" + val SUPPORT_FRAGMENT_FQNAME = "$SUPPORT_V4_PACKAGE.app.Fragment" + val IGNORED_XML_WIDGET_TYPES = setOf("requestFocus", "merge", "tag", "check", "fragment") val ESCAPED_IDENTIFIERS = (JetTokens.KEYWORDS.getTypes() + JetTokens.SOFT_KEYWORDS.getTypes()) diff --git a/plugins/android-compiler-plugin/src/org/jetbrains/kotlin/lang/resolve/android/AndroidUIXmlProcessor.kt b/plugins/android-compiler-plugin/src/org/jetbrains/kotlin/lang/resolve/android/AndroidUIXmlProcessor.kt index 19bef63c64c..f66875eb666 100644 --- a/plugins/android-compiler-plugin/src/org/jetbrains/kotlin/lang/resolve/android/AndroidUIXmlProcessor.kt +++ b/plugins/android-compiler-plugin/src/org/jetbrains/kotlin/lang/resolve/android/AndroidUIXmlProcessor.kt @@ -66,6 +66,8 @@ public abstract class AndroidUIXmlProcessor(protected val project: Project) { protected abstract val cachedSources: CachedValue> + var supportV4 = false + private val cachedJetFiles: CachedValue> by Delegates.lazy { cachedValue { val psiManager = PsiManager.getInstance(project) @@ -90,8 +92,9 @@ public abstract class AndroidUIXmlProcessor(protected val project: Project) { val clearCacheFile = renderSyntheticFile("clearCache") { writePackage(AndroidConst.SYNTHETIC_PACKAGE) writeAndroidImports() - writeClearCacheFunction("android.app.Activity") - writeClearCacheFunction("android.app.Fragment") + writeClearCacheFunction(AndroidConst.ACTIVITY_FQNAME) + writeClearCacheFunction(AndroidConst.FRAGMENT_FQNAME) + if (supportV4) writeClearCacheFunction(AndroidConst.SUPPORT_FRAGMENT_FQNAME) } listOf(clearCacheFile, @@ -109,6 +112,7 @@ public abstract class AndroidUIXmlProcessor(protected val project: Project) { val mainLayoutFile = renderMainLayoutFile(layoutName, resources) val viewLayoutFile = renderViewLayoutFile(layoutName, resources) + listOf(mainLayoutFile, viewLayoutFile) }.filterNotNull() + commonFiles } @@ -138,7 +142,9 @@ public abstract class AndroidUIXmlProcessor(protected val project: Project) { for (res in resources) { properties(res).forEach { - writeSyntheticProperty(it.first, res, it.second) + if (supportV4 || !it.first.startsWith(AndroidConst.SUPPORT_V4_PACKAGE)) { + writeSyntheticProperty(it.first, res, it.second) + } } } } diff --git a/plugins/android-compiler-plugin/src/org/jetbrains/kotlin/lang/resolve/android/androidResources.kt b/plugins/android-compiler-plugin/src/org/jetbrains/kotlin/lang/resolve/android/androidResources.kt index 665c803f592..7a5e6dcecfe 100644 --- a/plugins/android-compiler-plugin/src/org/jetbrains/kotlin/lang/resolve/android/androidResources.kt +++ b/plugins/android-compiler-plugin/src/org/jetbrains/kotlin/lang/resolve/android/androidResources.kt @@ -31,7 +31,8 @@ public abstract class AndroidResource(val id: String) { public class AndroidWidget(id: String, override val className: String) : AndroidResource(id) { override val mainProperties = listOf( "android.app.Activity" to "findViewById(0)", - "android.app.Fragment" to "getView().findViewById(0)") + "android.app.Fragment" to "getView().findViewById(0)", + "android.support.v4.app.Fragment" to "getView().findViewById(0)") override val viewProperties = listOf("android.view.View" to "findViewById(0)") diff --git a/plugins/android-idea-plugin/src/org/jetbrains/kotlin/plugin/android/IDEAndroidUIXmlProcessor.kt b/plugins/android-idea-plugin/src/org/jetbrains/kotlin/plugin/android/IDEAndroidUIXmlProcessor.kt index 9458b6fc84b..49432fe9456 100644 --- a/plugins/android-idea-plugin/src/org/jetbrains/kotlin/plugin/android/IDEAndroidUIXmlProcessor.kt +++ b/plugins/android-idea-plugin/src/org/jetbrains/kotlin/plugin/android/IDEAndroidUIXmlProcessor.kt @@ -18,6 +18,7 @@ package org.jetbrains.kotlin.plugin.android import com.intellij.openapi.components.ServiceManager import com.intellij.openapi.module.Module +import com.intellij.psi.JavaPsiFacade import org.jetbrains.kotlin.plugin.android.IDEAndroidResourceManager import com.intellij.psi.PsiFile import org.jetbrains.kotlin.plugin.android.AndroidXmlVisitor @@ -29,6 +30,12 @@ import com.intellij.psi.util.CachedValueProvider.Result class IDEAndroidUIXmlProcessor(val module: Module) : AndroidUIXmlProcessor(module.getProject()) { + init { + val scope = module.getModuleWithDependenciesAndLibrariesScope(false) + supportV4 = JavaPsiFacade.getInstance(module.getProject()) + .findClasses(AndroidConst.SUPPORT_FRAGMENT_FQNAME, scope).isNotEmpty() + } + override val resourceManager: IDEAndroidResourceManager = IDEAndroidResourceManager(module) private val psiTreeChangePreprocessor by Delegates.lazy { diff --git a/plugins/android-jps-plugin/src/KotlinAndroidJpsPlugin.kt b/plugins/android-jps-plugin/src/KotlinAndroidJpsPlugin.kt index b7bd4fc3e62..66945231713 100644 --- a/plugins/android-jps-plugin/src/KotlinAndroidJpsPlugin.kt +++ b/plugins/android-jps-plugin/src/KotlinAndroidJpsPlugin.kt @@ -23,6 +23,9 @@ import org.jetbrains.jps.model.module.JpsModule import java.io.File import org.jetbrains.jps.android.AndroidJpsUtil import com.intellij.util.PathUtil +import org.jetbrains.jps.model.library.JpsOrderRootType +import org.jetbrains.jps.model.module.JpsLibraryDependency +import org.jetbrains.jps.model.module.JpsModuleDependency public class KotlinAndroidJpsPlugin : KotlinJpsCompilerArgumentsProvider { override fun getExtraArguments(moduleBuildTarget: ModuleBuildTarget, context: CompileContext): List { @@ -30,12 +33,27 @@ public class KotlinAndroidJpsPlugin : KotlinJpsCompilerArgumentsProvider { val pluginId = ANDROID_COMPILER_PLUGIN_ID val resPath = getAndroidResPath(module) val manifestFile = getAndroidManifest(module) + val supportV4 = if (isSupportV4LibraryAttached(module)) "true" else "false" + return if (resPath != null && manifestFile != null) listOf( getPluginOptionString(pluginId, RESOURCE_PATH_OPTION_NAME, resPath), - getPluginOptionString(pluginId, MANIFEST_FILE_OPTION_NAME, manifestFile)) + getPluginOptionString(pluginId, MANIFEST_FILE_OPTION_NAME, manifestFile), + getPluginOptionString(pluginId, SUPPORT_V4_OPTION_NAME, supportV4)) else listOf() } + private fun isSupportV4LibraryAttached(module: JpsModule): Boolean { + return module.getDependenciesList().getDependencies().any { dep -> + when (dep) { + is JpsLibraryDependency -> + dep.getLibrary()?.getFiles(JpsOrderRootType.COMPILED)?.any { + it.name.startsWith("support-v4") && it.extension.toUpperCase() == "JAR" + } ?: false + else -> false + } + } + } + override fun getClasspath(moduleBuildTarget: ModuleBuildTarget, context: CompileContext): List { val inJar = File(PathUtil.getJarPathForClass(javaClass)).isFile() val manifestFile = getAndroidManifest(moduleBuildTarget.getModule()) @@ -72,6 +90,7 @@ public class KotlinAndroidJpsPlugin : KotlinJpsCompilerArgumentsProvider { private val RESOURCE_PATH_OPTION_NAME = "androidRes" private val MANIFEST_FILE_OPTION_NAME = "androidManifest" + private val SUPPORT_V4_OPTION_NAME = "supportV4" private fun getPluginOptionString(pluginId: String, key: String, value: String): String { return "plugin:$pluginId:$key=$value"