Check supportv4 library presence on the spot

This commit is contained in:
Yan Zhulanow
2015-08-20 15:35:52 +03:00
parent de59b79deb
commit a2096ab0be
8 changed files with 42 additions and 85 deletions
@@ -19,50 +19,34 @@ package org.jetbrains.kotlin.android
import org.jetbrains.kotlin.gradle.plugin.KotlinGradleSubplugin import org.jetbrains.kotlin.gradle.plugin.KotlinGradleSubplugin
import org.gradle.api import org.gradle.api
import com.android.build.gradle.BaseExtension import com.android.build.gradle.BaseExtension
import java.io.File
import org.gradle.api.tasks.compile.AbstractCompile import org.gradle.api.tasks.compile.AbstractCompile
import org.jetbrains.kotlin.gradle.plugin.SubpluginOption import org.jetbrains.kotlin.gradle.plugin.SubpluginOption
public class AndroidSubplugin : KotlinGradleSubplugin { public class AndroidSubplugin : KotlinGradleSubplugin {
override fun getExtraArguments(project: api.Project, task: AbstractCompile): List<SubpluginOption>? { override fun getExtraArguments(project: api.Project, task: AbstractCompile): List<SubpluginOption>? {
val androidExtension = project.getExtensions().getByName("android") as? BaseExtension val androidExtension = project.extensions.getByName("android") as? BaseExtension ?: return null
if (androidExtension == null) return null val sourceSets = androidExtension.sourceSets
val sourceSets = androidExtension.getSourceSets()
val mainSourceSet = sourceSets.getByName("main") val mainSourceSet = sourceSets.getByName("main")
val resourceDirs = mainSourceSet.getRes().getSrcDirs() val resourceDirs = mainSourceSet.res.srcDirs
val manifestFile = mainSourceSet.getManifest().getSrcFile() val manifestFile = mainSourceSet.manifest.srcFile
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 (resourceDirs.isNotEmpty()) { if (resourceDirs.isNotEmpty()) {
val resourceDirOptions = resourceDirs.map { resourceDir -> val resourceDirOptions = resourceDirs.map { resourceDir ->
resourceDir.listFiles { it.isDirectory() && it.name.startsWith("layout") }?.forEach { task.source(it) } resourceDir.listFiles { it.isDirectory && it.name.startsWith("layout") }?.forEach { task.source(it) }
SubpluginOption("androidRes", resourceDir.getAbsolutePath()) SubpluginOption("androidRes", resourceDir.absolutePath)
} }
return listOf( return listOf(SubpluginOption("androidManifest", manifestFile.absolutePath)) + resourceDirOptions
SubpluginOption("androidManifest", manifestFile.getAbsolutePath()),
SubpluginOption("supportV4", supportV4Property)
) + resourceDirOptions
} }
return null return null
} }
override fun getPluginName(): String { override fun getPluginName() = "org.jetbrains.kotlin.android"
return "org.jetbrains.kotlin.android"
}
override fun getGroupName(): String { override fun getGroupName() = "org.jetbrains.kotlin"
return "org.jetbrains.kotlin"
}
override fun getArtifactName(): String { override fun getArtifactName() = "kotlin-android-extensions"
return "kotlin-android-extensions"
}
} }
@@ -38,7 +38,6 @@ import org.jetbrains.kotlin.psi.JetFile
public object AndroidConfigurationKeys { public object AndroidConfigurationKeys {
public val ANDROID_RES_PATH: CompilerConfigurationKey<List<String>> = CompilerConfigurationKey.create<List<String>>("android resources search path") 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 ANDROID_MANIFEST: CompilerConfigurationKey<String> = CompilerConfigurationKey.create<String>("android manifest file")
public val SUPPORT_V4: CompilerConfigurationKey<String> = CompilerConfigurationKey.create<String>("'true' if compiled with support-v4 library")
} }
public class AndroidCommandLineProcessor : CommandLineProcessor { public class AndroidCommandLineProcessor : CommandLineProcessor {
@@ -47,12 +46,11 @@ public class AndroidCommandLineProcessor : CommandLineProcessor {
public val RESOURCE_PATH_OPTION: CliOption = CliOption("androidRes", "<path>", "Android resources path", allowMultipleOccurrences = true) 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 MANIFEST_FILE_OPTION: CliOption = CliOption("androidManifest", "<path>", "Android manifest file")
public val SUPPORT_V4_OPTION: CliOption = CliOption("supportV4", "<path>", "Support android-v4 library", required = false)
} }
override val pluginId: String = ANDROID_COMPILER_PLUGIN_ID override val pluginId: String = ANDROID_COMPILER_PLUGIN_ID
override val pluginOptions: Collection<CliOption> = listOf(RESOURCE_PATH_OPTION, MANIFEST_FILE_OPTION, SUPPORT_V4_OPTION) override val pluginOptions: Collection<CliOption> = listOf(RESOURCE_PATH_OPTION, MANIFEST_FILE_OPTION)
override fun processOption(option: CliOption, value: String, configuration: CompilerConfiguration) { override fun processOption(option: CliOption, value: String, configuration: CompilerConfiguration) {
when (option) { when (option) {
@@ -62,7 +60,6 @@ public class AndroidCommandLineProcessor : CommandLineProcessor {
configuration.put(AndroidConfigurationKeys.ANDROID_RES_PATH, paths) configuration.put(AndroidConfigurationKeys.ANDROID_RES_PATH, paths)
} }
MANIFEST_FILE_OPTION -> configuration.put(AndroidConfigurationKeys.ANDROID_MANIFEST, 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}") else -> throw CliOptionProcessingException("Unknown option: ${option.name}")
} }
} }
@@ -80,10 +77,9 @@ public class AndroidComponentRegistrar : ComponentRegistrar {
public override fun registerProjectComponents(project: MockProject, configuration: CompilerConfiguration) { public override fun registerProjectComponents(project: MockProject, configuration: CompilerConfiguration) {
val androidResPath = configuration.get(AndroidConfigurationKeys.ANDROID_RES_PATH) val androidResPath = configuration.get(AndroidConfigurationKeys.ANDROID_RES_PATH)
val androidManifest = configuration.get(AndroidConfigurationKeys.ANDROID_MANIFEST) val androidManifest = configuration.get(AndroidConfigurationKeys.ANDROID_MANIFEST)
val supportV4 = configuration.get(AndroidConfigurationKeys.SUPPORT_V4) ?: "false"
if (androidResPath != null && androidManifest != null) { if (androidResPath != null && androidManifest != null) {
val xmlProcessor = CliSyntheticFileGenerator(project, androidManifest, androidResPath, supportV4 == "true") val xmlProcessor = CliSyntheticFileGenerator(project, androidManifest, androidResPath)
project.registerService(javaClass<SyntheticFileGenerator>(), xmlProcessor) project.registerService(javaClass<SyntheticFileGenerator>(), xmlProcessor)
project.registerService(javaClass<AndroidLayoutXmlFileManager>(), CliAndroidLayoutXmlFileManager(project, androidManifest, androidResPath)) project.registerService(javaClass<AndroidLayoutXmlFileManager>(), CliAndroidLayoutXmlFileManager(project, androidManifest, androidResPath))
@@ -30,19 +30,15 @@ import kotlin.properties.Delegates
public open class CliSyntheticFileGenerator( public open class CliSyntheticFileGenerator(
project: Project, project: Project,
private val manifestPath: String, private val manifestPath: String,
private val resDirectories: List<String>, private val resDirectories: List<String>
private val supportV4: Boolean
) : SyntheticFileGenerator(project) { ) : SyntheticFileGenerator(project) {
private val javaPsiFacade: JavaPsiFacade by lazy { JavaPsiFacade.getInstance(project) } private val javaPsiFacade: JavaPsiFacade by lazy { JavaPsiFacade.getInstance(project) }
private val projectScope: GlobalSearchScope by lazy { GlobalSearchScope.allScope(project) } private val projectScope: GlobalSearchScope by lazy { GlobalSearchScope.allScope(project) }
private val cachedJetFiles by lazy { private val cachedJetFiles by lazy {
generateSyntheticJetFiles(generateSyntheticFiles(true, projectScope)) val supportV4 = supportV4Available(javaPsiFacade, projectScope)
} generateSyntheticJetFiles(generateSyntheticFiles(true, projectScope, supportV4))
override fun supportV4(): Boolean {
return supportV4
} }
override val layoutXmlFileManager: CliAndroidLayoutXmlFileManager by Delegates.lazy { override val layoutXmlFileManager: CliAndroidLayoutXmlFileManager by Delegates.lazy {
@@ -39,12 +39,14 @@ public abstract class SyntheticFileGenerator(protected val project: Project) {
public abstract val layoutXmlFileManager: AndroidLayoutXmlFileManager public abstract val layoutXmlFileManager: AndroidLayoutXmlFileManager
protected abstract fun supportV4(): Boolean
public abstract fun getSyntheticFiles(): List<JetFile> public abstract fun getSyntheticFiles(): List<JetFile>
protected fun generateSyntheticFiles(generateCommonFiles: Boolean = true, scope: GlobalSearchScope): List<AndroidSyntheticFile> { protected fun generateSyntheticFiles(
val commonFiles = if (generateCommonFiles) generateCommonFiles() else listOf() generateCommonFiles: Boolean,
scope: GlobalSearchScope,
supportV4: Boolean
): List<AndroidSyntheticFile> {
val commonFiles = if (generateCommonFiles) generateCommonFiles(supportV4) else listOf()
return layoutXmlFileManager.getLayoutXmlFiles().flatMap { entry -> return layoutXmlFileManager.getLayoutXmlFiles().flatMap { entry ->
val files = entry.getValue() val files = entry.getValue()
@@ -52,20 +54,22 @@ public abstract class SyntheticFileGenerator(protected val project: Project) {
val layoutName = entry.getKey() val layoutName = entry.getKey()
val mainLayoutFile = renderMainLayoutFile(layoutName, resources) val mainLayoutFile = renderMainLayoutFile(layoutName, resources, supportV4)
val viewLayoutFile = renderViewLayoutFile(layoutName, resources) val viewLayoutFile = renderViewLayoutFile(layoutName, resources)
listOf(mainLayoutFile, viewLayoutFile) listOf(mainLayoutFile, viewLayoutFile)
}.filterNotNull() + commonFiles }.filterNotNull() + commonFiles
} }
private fun generateCommonFiles(): List<AndroidSyntheticFile> { private fun generateCommonFiles(supportV4: Boolean): List<AndroidSyntheticFile> {
val renderSyntheticFile = renderSyntheticFile("clearCache") { val renderSyntheticFile = renderSyntheticFile("clearCache") {
writePackage(AndroidConst.SYNTHETIC_PACKAGE) writePackage(AndroidConst.SYNTHETIC_PACKAGE)
writeAndroidImports() writeAndroidImports()
writeClearCacheFunction(AndroidConst.ACTIVITY_FQNAME) writeClearCacheFunction(AndroidConst.ACTIVITY_FQNAME)
writeClearCacheFunction(AndroidConst.FRAGMENT_FQNAME) writeClearCacheFunction(AndroidConst.FRAGMENT_FQNAME)
if (supportV4()) writeClearCacheFunction(AndroidConst.SUPPORT_FRAGMENT_FQNAME) if (supportV4) {
writeClearCacheFunction(AndroidConst.SUPPORT_FRAGMENT_FQNAME)
}
} }
val clearCacheFile = renderSyntheticFile val clearCacheFile = renderSyntheticFile
@@ -74,11 +78,10 @@ public abstract class SyntheticFileGenerator(protected val project: Project) {
protected abstract fun extractLayoutResources(files: List<PsiFile>, scope: GlobalSearchScope): List<AndroidResource> protected abstract fun extractLayoutResources(files: List<PsiFile>, scope: GlobalSearchScope): List<AndroidResource>
private fun renderMainLayoutFile(layoutName: String, resources: List<AndroidResource>): AndroidSyntheticFile { private fun renderMainLayoutFile(layoutName: String, resources: List<AndroidResource>, supportV4: Boolean): AndroidSyntheticFile {
return renderLayoutFile(layoutName + AndroidConst.LAYOUT_POSTFIX, return renderLayoutFile(layoutName + AndroidConst.LAYOUT_POSTFIX, escapeAndroidIdentifier(layoutName), resources) {
escapeAndroidIdentifier(layoutName), resources) {
val properties = it.mainProperties.toArrayList() val properties = it.mainProperties.toArrayList()
if (supportV4()) properties.addAll(it.mainPropertiesForSupportV4) if (supportV4) properties.addAll(it.mainPropertiesForSupportV4)
properties properties
} }
} }
@@ -155,6 +158,10 @@ public abstract class SyntheticFileGenerator(protected val project: Project) {
return null return null
} }
protected fun supportV4Available(javaPsiFacade: JavaPsiFacade, scope: GlobalSearchScope): Boolean {
return javaPsiFacade.findClasses(AndroidConst.SUPPORT_FRAGMENT_FQNAME, scope).isNotEmpty()
}
protected fun filterDuplicates(resources: List<AndroidResource>): List<AndroidResource> { protected fun filterDuplicates(resources: List<AndroidResource>): List<AndroidResource> {
val resourceMap = linkedMapOf<String, AndroidResource>() val resourceMap = linkedMapOf<String, AndroidResource>()
val resourcesToExclude = hashSetOf<String>() val resourcesToExclude = hashSetOf<String>()
@@ -47,9 +47,9 @@ class CliSyntheticFileGeneratorForConversionTest(
project: Project, project: Project,
manifestPath: String, manifestPath: String,
resDirectories: List<String>, resDirectories: List<String>,
supportV4: Boolean private val supportV4: Boolean
) : CliSyntheticFileGenerator(project, manifestPath, resDirectories, supportV4) { ) : CliSyntheticFileGenerator(project, manifestPath, resDirectories) {
fun gen(scope: GlobalSearchScope) = generateSyntheticFiles(false, scope) fun gen(scope: GlobalSearchScope) = generateSyntheticFiles(false, scope, supportV4)
} }
fun UsefulTestCase.createAndroidTestEnvironment( fun UsefulTestCase.createAndroidTestEnvironment(
@@ -61,7 +61,7 @@ fun UsefulTestCase.createAndroidTestEnvironment(
configuration.put(AndroidConfigurationKeys.ANDROID_RES_PATH, resPaths) configuration.put(AndroidConfigurationKeys.ANDROID_RES_PATH, resPaths)
configuration.put(AndroidConfigurationKeys.ANDROID_MANIFEST, manifestPath) configuration.put(AndroidConfigurationKeys.ANDROID_MANIFEST, manifestPath)
val myEnvironment = KotlinCoreEnvironment.createForTests(getTestRootDisposable()!!, configuration, EnvironmentConfigFiles.JVM_CONFIG_FILES) val myEnvironment = KotlinCoreEnvironment.createForTests(testRootDisposable, configuration, EnvironmentConfigFiles.JVM_CONFIG_FILES)
val project = myEnvironment.project val project = myEnvironment.project
val declarationsProvider = AndroidTestExternalDeclarationsProvider(project, resPaths, manifestPath, supportV4) val declarationsProvider = AndroidTestExternalDeclarationsProvider(project, resPaths, manifestPath, supportV4)
@@ -72,5 +72,5 @@ fun UsefulTestCase.createAndroidTestEnvironment(
} }
fun getResPaths(path: String): List<String> { fun getResPaths(path: String): List<String> {
return File(path).listFiles { it.name.startsWith("res") && it.isDirectory() }!!.map { "$path${it.name}/" } return File(path).listFiles { it.name.startsWith("res") && it.isDirectory }!!.map { "$path${it.name}/" }
} }
@@ -23,7 +23,6 @@ import com.intellij.psi.impl.PsiTreeChangePreprocessor
import com.intellij.psi.search.GlobalSearchScope import com.intellij.psi.search.GlobalSearchScope
import com.intellij.psi.util.CachedValue import com.intellij.psi.util.CachedValue
import com.intellij.psi.util.CachedValueProvider.Result import com.intellij.psi.util.CachedValueProvider.Result
import org.jetbrains.kotlin.android.synthetic.AndroidConst
import org.jetbrains.kotlin.android.synthetic.idea.AndroidPsiTreeChangePreprocessor import org.jetbrains.kotlin.android.synthetic.idea.AndroidPsiTreeChangePreprocessor
import org.jetbrains.kotlin.android.synthetic.idea.AndroidXmlVisitor import org.jetbrains.kotlin.android.synthetic.idea.AndroidXmlVisitor
import org.jetbrains.kotlin.android.synthetic.parseAndroidResource import org.jetbrains.kotlin.android.synthetic.parseAndroidResource
@@ -33,23 +32,16 @@ import org.jetbrains.kotlin.psi.JetFile
class IDESyntheticFileGenerator(val module: Module) : SyntheticFileGenerator(module.project) { class IDESyntheticFileGenerator(val module: Module) : SyntheticFileGenerator(module.project) {
private val supportV4: Boolean
private val javaPsiFacade: JavaPsiFacade by lazy { JavaPsiFacade.getInstance(module.project) } private val javaPsiFacade: JavaPsiFacade by lazy { JavaPsiFacade.getInstance(module.project) }
private val moduleScope: GlobalSearchScope by lazy { module.getModuleWithDependenciesAndLibrariesScope(false) } private val moduleScope: GlobalSearchScope by lazy { module.getModuleWithDependenciesAndLibrariesScope(false) }
private val cachedJetFiles: CachedValue<List<JetFile>> by lazy { private val cachedJetFiles: CachedValue<List<JetFile>> by lazy {
cachedValue { cachedValue {
Result.create(generateSyntheticJetFiles(generateSyntheticFiles(true, moduleScope)), psiTreeChangePreprocessor) val supportV4 = supportV4Available(javaPsiFacade, moduleScope)
Result.create(generateSyntheticJetFiles(generateSyntheticFiles(true, moduleScope, supportV4)), psiTreeChangePreprocessor)
} }
} }
init {
supportV4 = javaPsiFacade.findClasses(AndroidConst.SUPPORT_FRAGMENT_FQNAME, moduleScope).isNotEmpty()
}
override fun supportV4() = supportV4
override val layoutXmlFileManager: IDEAndroidLayoutXmlFileManager = IDEAndroidLayoutXmlFileManager(module) override val layoutXmlFileManager: IDEAndroidLayoutXmlFileManager = IDEAndroidLayoutXmlFileManager(module)
private val psiTreeChangePreprocessor by lazy { private val psiTreeChangePreprocessor by lazy {
@@ -31,7 +31,7 @@ public abstract class AbstractParserResultEqualityTest : KotlinAndroidTestCase()
"$path${it.name}/" "$path${it.name}/"
} }
val cliParser = CliSyntheticFileGenerator(project, "$path../AndroidManifest.xml", resDirs, false) val cliParser = CliSyntheticFileGenerator(project, "$path../AndroidManifest.xml", resDirs)
val ideParser = IDESyntheticFileGenerator(ModuleManager.getInstance(project).getModules()[0]) val ideParser = IDESyntheticFileGenerator(ModuleManager.getInstance(project).getModules()[0])
val cliResult = cliParser.getSyntheticFiles().joinToString("\n\n") val cliResult = cliParser.getSyntheticFiles().joinToString("\n\n")
@@ -23,9 +23,6 @@ import org.jetbrains.jps.model.module.JpsModule
import java.io.File import java.io.File
import org.jetbrains.jps.android.AndroidJpsUtil import org.jetbrains.jps.android.AndroidJpsUtil
import com.intellij.util.PathUtil 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 { public class KotlinAndroidJpsPlugin : KotlinJpsCompilerArgumentsProvider {
override fun getExtraArguments(moduleBuildTarget: ModuleBuildTarget, context: CompileContext): List<String> { override fun getExtraArguments(moduleBuildTarget: ModuleBuildTarget, context: CompileContext): List<String> {
@@ -33,27 +30,13 @@ public class KotlinAndroidJpsPlugin : KotlinJpsCompilerArgumentsProvider {
val pluginId = ANDROID_COMPILER_PLUGIN_ID val pluginId = ANDROID_COMPILER_PLUGIN_ID
val resPath = getAndroidResPath(module) val resPath = getAndroidResPath(module)
val manifestFile = getAndroidManifest(module) val manifestFile = getAndroidManifest(module)
val supportV4 = if (isSupportV4LibraryAttached(module)) "true" else "false"
return if (resPath != null && manifestFile != null) listOf( return if (resPath != null && manifestFile != null) listOf(
getPluginOptionString(pluginId, RESOURCE_PATH_OPTION_NAME, resPath), 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() 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<String> { override fun getClasspath(moduleBuildTarget: ModuleBuildTarget, context: CompileContext): List<String> {
val inJar = File(PathUtil.getJarPathForClass(javaClass)).isFile() val inJar = File(PathUtil.getJarPathForClass(javaClass)).isFile()
val manifestFile = getAndroidManifest(moduleBuildTarget.getModule()) val manifestFile = getAndroidManifest(moduleBuildTarget.getModule())
@@ -88,7 +71,6 @@ public class KotlinAndroidJpsPlugin : KotlinJpsCompilerArgumentsProvider {
private val RESOURCE_PATH_OPTION_NAME = "androidRes" private val RESOURCE_PATH_OPTION_NAME = "androidRes"
private val MANIFEST_FILE_OPTION_NAME = "androidManifest" private val MANIFEST_FILE_OPTION_NAME = "androidManifest"
private val SUPPORT_V4_OPTION_NAME = "supportV4"
private fun getPluginOptionString(pluginId: String, key: String, value: String): String { private fun getPluginOptionString(pluginId: String, key: String, value: String): String {
return "plugin:$pluginId:$key=$value" return "plugin:$pluginId:$key=$value"