From 5eedbf4177eae2630b70b4c44e01903982a2718c Mon Sep 17 00:00:00 2001 From: Yan Zhulanow Date: Wed, 16 May 2018 22:54:12 +0300 Subject: [PATCH] G2K: Rewrite AndroidGradleWrapper in Kotlin --- .../gradle/internal/AndroidSubplugin.kt | 4 +- .../plugin/android/AndroidGradleWrapper.kt | 417 ++++++++---------- 2 files changed, 176 insertions(+), 245 deletions(-) diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/AndroidSubplugin.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/AndroidSubplugin.kt index 34b0ae363af..baacabf9686 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/AndroidSubplugin.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/AndroidSubplugin.kt @@ -128,7 +128,9 @@ class AndroidSubplugin : KotlinGradleSubplugin { addVariant(mainSourceSet) - val flavorSourceSets = AndroidGradleWrapper.getProductFlavorsSourceSets(androidExtension).filterNotNull() + val flavorSourceSets = androidExtension.productFlavors + .mapNotNull { androidExtension.sourceSets.findByName(it.name) } + for (sourceSet in flavorSourceSets) { addVariant(sourceSet) } diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/android/AndroidGradleWrapper.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/android/AndroidGradleWrapper.kt index 23ef8718050..53d50521233 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/android/AndroidGradleWrapper.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/android/AndroidGradleWrapper.kt @@ -1,17 +1,6 @@ /* - * Copyright 2010-2016 JetBrains s.r.o. - * - * 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. + * Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the license/LICENSE.txt file. */ package org.jetbrains.kotlin.gradle.plugin.android @@ -19,259 +8,199 @@ package org.jetbrains.kotlin.gradle.plugin.android import com.android.build.gradle.BaseExtension import com.android.build.gradle.BasePlugin import com.android.build.gradle.api.AndroidSourceSet -import com.android.build.gradle.api.ApkVariant -import com.android.build.gradle.api.BaseVariant -import com.android.build.gradle.api.TestVariant import com.android.build.gradle.internal.VariantManager import com.android.build.gradle.internal.variant.BaseVariantData -import com.android.builder.model.SourceProvider import org.gradle.api.file.ConfigurableFileTree -import org.gradle.api.internal.DefaultDomainObjectSet import org.gradle.api.tasks.compile.AbstractCompile -import org.gradle.api.tasks.util.PatternFilterable -import org.jetbrains.annotations.NotNull -import org.jetbrains.annotations.Nullable +import java.io.File +import java.lang.reflect.Field +import java.lang.reflect.InvocationTargetException +import java.lang.reflect.Method +import java.lang.reflect.Modifier -class AndroidGradleWrapper { - static def getRuntimeJars(BasePlugin basePlugin, BaseExtension baseExtension) { - if (basePlugin.getMetaClass().getMetaMethod("getRuntimeJarList")) { - return basePlugin.getRuntimeJarList() - } - else if (baseExtension.getMetaClass().getMetaMethod("getBootClasspath")) { - return baseExtension.getBootClasspath() - } - else { - return basePlugin.getBootClasspath() - } - } - - static def srcDir(Object androidSourceSet, Object kotlinDirSet) { - androidSourceSet.getJava().srcDir(kotlinDirSet) - } - - static PatternFilterable getResourceFilter(Object androidSourceSet) { - def resources = androidSourceSet.getResources() - if (resources != null) { - return resources.getFilter() - } - return null - } - - @NotNull - static String getVariantName(Object variant) { - return variant.getBuildType().getName() - } - - @Nullable - private static getJackOptions(@NotNull Object variantData) { - def variantConfiguration = variantData.variantConfiguration - if (variantConfiguration.getMetaClass().getMetaMethod("getJackOptions")) { - return variantConfiguration.getJackOptions() - } - return null - } - - static boolean isJackEnabled(@NotNull Object variantData) { - return getJackOptions(variantData)?.enabled ?: false - } - - @Nullable - static AbstractCompile getJavaTask(@NotNull Object variantData) { - return getJavaCompile(variantData) - } - - @Nullable - private static AbstractCompile getJavacTask(@NotNull Object baseVariantData) { - if (baseVariantData.getMetaClass().getMetaProperty("javacTask")) { - return baseVariantData.javacTask - } - return null - } - - @Nullable - private static AbstractCompile getJavaCompile(@NotNull Object baseVariantData) { - if (baseVariantData.getMetaClass().getMetaProperty("javaCompileTask")) { - return baseVariantData.javaCompileTask - } - else if (baseVariantData.getMetaClass().getMetaProperty("javaCompilerTask")) { - return baseVariantData.javaCompilerTask - } - return null - } - - @NotNull - static Set getJavaSrcDirs(Object androidSourceSet) { - return androidSourceSet.getJava().getSrcDirs() - } - - static def setNoJdk(Object kotlinOptionsExtension) { - kotlinOptionsExtension.noJdk = true - } - - @NotNull - static List getProductFlavorsNames(ApkVariant variant) { - return variant.getProductFlavors().iterator().collect { it.getName() } - } - - @NotNull - static List getProductFlavorsSourceSets(BaseExtension extension) { - return extension.productFlavors.iterator().collect { extension.sourceSets.findByName(it.name) } - } - - @Nullable - static Map getAnnotationProcessorOptionsFromAndroidVariant(@Nullable Object variantData) { - if (!(variantData instanceof BaseVariantData)) { - throw new IllegalArgumentException("BaseVariantData instance expected") +object AndroidGradleWrapper { + fun getRuntimeJars(basePlugin: BasePlugin, baseExtension: BaseExtension): Any? { + return basePlugin("getRuntimeJarList") ?: baseExtension("getBootClasspath") ?: basePlugin("getBootClasspath") } - def variantConfiguration = variantData.variantConfiguration - if (variantConfiguration.getMetaClass().getMetaMethod("getJavaCompileOptions")) { - def javaCompileOptions = variantConfiguration.getJavaCompileOptions() - - if (javaCompileOptions.getMetaClass().getMetaMethod("getAnnotationProcessorOptions")) { - return javaCompileOptions.getAnnotationProcessorOptions().getArguments() - } + fun isJackEnabled(variantData: Any): Boolean { + val variantConfiguration = variantData("getVariantConfiguration") + return (variantConfiguration("getUseJack") ?: variantConfiguration("getJackOptions")("isEnabled")) as? Boolean ?: false } - return null - } - - @NotNull - static DefaultDomainObjectSet getTestVariants(BaseExtension extension) { - if (extension.getMetaClass().getMetaMethod("getTestVariants")) { - return extension.getTestVariants() - } - return Collections.emptyList() - } - - @NotNull - static List getRClassFolder(BaseVariant variant) { - def list = new ArrayList() - if (variant.getMetaClass().getMetaMethod("getProcessResources")) { - list.add(variant.getProcessResources().getSourceOutputDir()) - } - else { - for (Object variantOutput : variant.getOutputs()) { - list.add(variantOutput.processResources.sourceOutputDir) - } - } - return list - } - - static VariantManager getVariantDataManager(BasePlugin plugin) { - return plugin.getVariantManager() - } - - static List getJavaSources(BaseVariantData variantData) { - def result = new LinkedHashSet() - - // user sources - List providers = variantData.variantConfiguration.getSortedSourceProviders() - for (SourceProvider provider : providers) { - result.addAll((provider as AndroidSourceSet).getJava().getSrcDirs()) + fun getJavaTask(variantData: Any): AbstractCompile? { + return getJavaCompile(variantData) } - // generated sources - def getJavaSourcesMethod = variantData.getMetaClass().getMetaMethod("getJavaSources") - if (getJavaSourcesMethod.returnType.metaClass == Object[].metaClass) { - result.addAll(variantData.getJavaSources().findAll { it instanceof File }) - } - else if (getJavaSourcesMethod.returnType.metaClass == List.metaClass) { - def fileTrees = variantData.getJavaSources().findAll { it instanceof ConfigurableFileTree } - result.addAll(fileTrees.collect { it.getDir() }) - } - else { - if (variantData.scope.getGenerateRClassTask() != null) { - result.add(variantData.scope.getRClassSourceOutputDir()) - } - - if (variantData.scope.getGenerateBuildConfigTask() != null) { - result.add(variantData.scope.getBuildConfigSourceOutputDir()) - } - - if (variantData.scope.getAidlCompileTask() != null) { - result.add(variantData.scope.getAidlSourceOutputDir()) - } - - if (variantData.scope.getGlobalScope().getExtension().getDataBinding().isEnabled()) { - result.add(variantData.scope.getClassOutputForDataBinding()) - } - - if (!variantData.variantConfiguration.getRenderscriptNdkModeEnabled() - && variantData.scope.getRenderscriptCompileTask() != null) { - result.add(variantData.scope.getRenderscriptSourceOutputDir()) - } + private fun getJavaCompile(baseVariantData: Any): AbstractCompile? { + return (baseVariantData["javaCompileTask"] ?: baseVariantData["javaCompilerTask"]) as? AbstractCompile } - def getExtraSourcesMethod = variantData.getMetaClass().getMetaMethod("getExtraGeneratedSourceFolders") - if (getExtraSourcesMethod.returnType.metaClass == List.metaClass) { - def folders = variantData.getExtraGeneratedSourceFolders() - if (folders != null) { - result.addAll(folders) - } - } - - return result.toList() - } - - @NotNull - static Map getJarToAarMapping(BaseVariantData variantData) { - def jarToLibraryArtifactMap = new HashMap() - - def libraries = getVariantLibraryDependencies(variantData) - if (libraries == null) return jarToLibraryArtifactMap - - for (lib in libraries) { - Object bundle = getLibraryArtifactFile(lib) - jarToLibraryArtifactMap[lib.jarFile] = bundle - - // local dependencies are detected as changed by gradle, because they are seem to be - // rewritten every time when bundle changes - // when local dep will actually change, record for bundle will be removed from registry - for (localDep in lib.localJars) { - if (localDep instanceof File) { - // android tools 2.2 - jarToLibraryArtifactMap[localDep] = bundle + fun getAnnotationProcessorOptionsFromAndroidVariant(variantData: Any?): Map? { + if (variantData !is BaseVariantData<*>) { + throw IllegalArgumentException("BaseVariantData instance expected") } - else if (localDep.metaClass.getMetaMethod("jarFile") != null) { - // android tools < 2.2 - jarToLibraryArtifactMap[localDep.jarFile] = bundle + + val compileOptions = variantData("getVariantConfiguration")("getJavaCompileOptions") ?: return null + + @Suppress("UNCHECKED_CAST") + return compileOptions("getAnnotationProcessorOptions")("getArguments") as? Map + } + + fun getVariantDataManager(plugin: BasePlugin): VariantManager { + return plugin("getVariantManager") as VariantManager + } + + + fun getJavaSources(variantData: BaseVariantData<*>): List { + val result = LinkedHashSet() + + // user sources + for (provider in variantData.variantConfiguration.sortedSourceProviders) { + result.addAll((provider as AndroidSourceSet).java.srcDirs) } - } + + // generated sources + val javaSources = variantData("getJavaSources") + if (javaSources is Array<*>) { + result += javaSources.filterIsInstance() + } else if (javaSources is List<*>) { + val fileTrees = javaSources.filterIsInstance() + result += fileTrees.map { it.dir } + } else { + if (variantData.scope.generateRClassTask != null) { + result += variantData.scope.rClassSourceOutputDir + } + + if (variantData.scope.generateBuildConfigTask != null) { + result += variantData.scope.buildConfigSourceOutputDir + } + + if (variantData.scope.aidlCompileTask != null) { + result += variantData.scope.aidlSourceOutputDir + } + + if (variantData.scope.globalScope.extension.dataBinding.isEnabled) { + result += variantData.scope.classOutputForDataBinding + } + + if (!variantData.variantConfiguration.renderscriptNdkModeEnabled && variantData.scope.renderscriptCompileTask != null) { + result += variantData.scope.renderscriptSourceOutputDir + } + } + + val extraSources = variantData("getExtraGeneratedSourceFolders") as? List<*> + if (extraSources != null) { + result += extraSources.filterIsInstance() + } + + return result.toList() } - return jarToLibraryArtifactMap - } + fun getJarToAarMapping(variantData: BaseVariantData<*>): Map { + val jarToLibraryArtifactMap = hashMapOf() - private static def getLibraryArtifactFile(Object lib) { - if (lib.class.name == "com.android.builder.dependency.level2.AndroidDependency") { - // android tools >= 2.3 - return lib.artifactFile - } else { - // android tools <= 2.2 - return lib.bundle - } - } + val libraries = getVariantLibraryDependencies(variantData) ?: return jarToLibraryArtifactMap - @Nullable - private static Iterable getVariantLibraryDependencies(BaseVariantData variantData) { - def variantDependency = variantData.variantDependency + for (lib in libraries) { + lib ?: continue + val bundle = getLibraryArtifactFile(lib) ?: continue + val libJarFile = lib("getJarFile") as? File ?: continue - if (variantDependency.metaClass.getMetaMethod("getAndroidDependencies") != null) { - // android tools < 2.2 - return variantDependency.getAndroidDependencies() as Iterable + jarToLibraryArtifactMap[libJarFile] = bundle + + @Suppress("UNCHECKED_CAST") + val localJarsForLib = lib("getLocalJars") as? List<*> ?: emptyList() + + // local dependencies are detected as changed by gradle, because they are seem to be + // rewritten every time when bundle changes + // when local dep will actually change, record for bundle will be removed from registry + for (localDep in localJarsForLib) { + if (localDep is File) { + // android tools 2.2 + jarToLibraryArtifactMap[localDep] = bundle + } else { + // android tools < 2.2 + val jarFile = localDep("getJarFile") as? File ?: continue + jarToLibraryArtifactMap[jarFile] = bundle + } + } + } + + return jarToLibraryArtifactMap } - def getCompileDependencies = variantDependency.metaClass.getMetaMethod("getCompileDependencies") - if (getCompileDependencies != null) { - def compileDependencies = variantDependency.getCompileDependencies() - if (compileDependencies.metaClass.getMetaMethod("getDirectAndroidDependencies") != null) { - return compileDependencies.getDirectAndroidDependencies() // android >= 2.3 - } else if (compileDependencies.metaClass.getMetaMethod("getAndroidDependencies")) { - return compileDependencies.getAndroidDependencies() // android 2.2 - } + private fun getLibraryArtifactFile(lib: Any): File? { + return if (lib.javaClass.name == "com.android.builder.dependency.level2.AndroidDependency") { + // android tools >= 2.3 + lib("getArtifactFile") as? File + } else { + // android tools <= 2.2 + lib("getBundle") as? File + } } - return null - } + private fun getVariantLibraryDependencies(variantData: BaseVariantData<*>): Iterable<*>? { + val variantDependency = variantData.variantDependency + + variantDependency("getAndroidDependencies")?.let { + // android tools < 2.2 + return it as Iterable<*> + } + + val compileDependencies = variantDependency("getCompileDependencies") ?: return null + val result = compileDependencies("getDirectAndroidDependencies") // android >= 2.3 + ?: compileDependencies("getAndroidDependencies") // android 2.2 + + return result as? Iterable<*> + } } + +private operator fun Any?.invoke(methodName: String): Any? { + if (this == null) { + return null + } + + fun Array.findMethod() = + singleOrNull { it.name == methodName && it.parameterCount == 0 && !Modifier.isStatic(it.modifiers) } + + val clazz = this::class.java + val methodToInvoke = clazz.declaredMethods.findMethod() ?: clazz.methods.findMethod() ?: return null + val oldIsAccessible = methodToInvoke.isAccessible + + try { + methodToInvoke.isAccessible = true + return methodToInvoke.invoke(this) + } catch (e: InvocationTargetException) { + throw e.targetException + } catch (e: Throwable) { + throw RuntimeException("Can't invoke method '$methodName()' on $this", e) + } finally { + methodToInvoke.isAccessible = oldIsAccessible + } +} + +private operator fun Any?.get(name: String): Any? { + if (this == null) { + return null + } + + this.invoke("get" + name.capitalize())?.let { return it } + + fun Array.findField() = singleOrNull { it.name == name && !Modifier.isStatic(it.modifiers) } + + val clazz = this::class.java + val fieldToInvoke = clazz.declaredFields.findField() ?: clazz.fields.findField() ?: return null + val oldIsAccessible = fieldToInvoke.isAccessible + + try { + fieldToInvoke.isAccessible = true + return fieldToInvoke.get(this) + } catch (e: InvocationTargetException) { + throw e.targetException + } catch (e: Throwable) { + throw RuntimeException("Couldn't get value of field '$name' in $this", e) + } finally { + fieldToInvoke.isAccessible = oldIsAccessible + } +} \ No newline at end of file