Android Extensions: Allow to disable specific features of Android Extensions (#KT-23244)
This commit is contained in:
@@ -132,6 +132,9 @@ public class CompilerConfiguration {
|
||||
else if (object instanceof Map) {
|
||||
return (T) Collections.unmodifiableMap((Map) object);
|
||||
}
|
||||
else if (object instanceof Set) {
|
||||
return (T) Collections.unmodifiableSet((Set) object);
|
||||
}
|
||||
else if (object instanceof Collection) {
|
||||
return (T) Collections.unmodifiableCollection((Collection) object);
|
||||
}
|
||||
|
||||
+29
@@ -331,6 +331,11 @@ fun getSomething() = 10
|
||||
|
||||
@Test
|
||||
fun testAndroidExtensionsManyVariants() {
|
||||
if (isLegacyAndroidGradleVersion(androidGradlePluginVersion)) {
|
||||
// Library dependencies are not supported in older versions of Android Gradle plugin (< 3.0)
|
||||
return
|
||||
}
|
||||
|
||||
val project = Project("AndroidExtensionsManyVariants", gradleVersion)
|
||||
val options = defaultBuildOptions().copy(incremental = false)
|
||||
|
||||
@@ -346,6 +351,30 @@ fun getSomething() = 10
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testAndroidExtensionsSpecificFeatures() {
|
||||
val project = Project("AndroidExtensionsSpecificFeatures", gradleVersion)
|
||||
val options = defaultBuildOptions().copy(incremental = false)
|
||||
|
||||
project.build("assemble", options = options) {
|
||||
assertFailed()
|
||||
assertContains("Unresolved reference: textView")
|
||||
}
|
||||
|
||||
File(project.projectDir, "app/build.gradle").modify { it.replace("[\"parcelize\"]", "[\"views\"]") }
|
||||
|
||||
project.build("assemble", options = options) {
|
||||
assertFailed()
|
||||
assertContains("Class 'User' is not abstract and does not implement abstract member public abstract fun writeToParcel")
|
||||
}
|
||||
|
||||
File(project.projectDir, "app/build.gradle").modify { it.replace("[\"views\"]", "[\"parcelize\", \"views\"]") }
|
||||
|
||||
project.build("assemble", options = options) {
|
||||
assertSuccessful()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testMultiplatformAndroidCompile() = with(Project("multiplatformAndroidProject", gradleVersion)) {
|
||||
setupWorkingDir()
|
||||
|
||||
+1
@@ -36,6 +36,7 @@ android {
|
||||
|
||||
androidExtensions {
|
||||
experimental = true
|
||||
features = ["views", "parcelize"]
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
+5
@@ -4,6 +4,8 @@ import android.app.Activity
|
||||
import android.os.Bundle
|
||||
import kotlinx.android.synthetic.main.activity_main.*
|
||||
import kotlinx.android.synthetic.main.layout_in_library.text_view
|
||||
import kotlinx.android.parcel.Parcelize
|
||||
import android.os.Parcelable
|
||||
|
||||
class MainActivity : Activity() {
|
||||
override fun onCreate(savedInstanceState: Bundle) {
|
||||
@@ -13,3 +15,6 @@ class MainActivity : Activity() {
|
||||
text_view
|
||||
}
|
||||
}
|
||||
|
||||
@Parcelize
|
||||
class User(val firstName: String, val lastName: String) : Parcelable
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'kotlin-android-extensions'
|
||||
|
||||
android {
|
||||
compileSdkVersion 23
|
||||
buildToolsVersion "25.0.2"
|
||||
|
||||
defaultConfig {
|
||||
applicationId "com.example.dagger.kotlin"
|
||||
minSdkVersion 14
|
||||
targetSdkVersion 23
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
}
|
||||
sourceSets {
|
||||
main.java.srcDirs += 'src/main/kotlin'
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile fileTree(dir: 'libs', include: ['*.jar'])
|
||||
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||
}
|
||||
|
||||
androidExtensions {
|
||||
experimental = true
|
||||
features = ["parcelize"]
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.androidextensions">
|
||||
|
||||
<application android:label="app_name">
|
||||
<activity
|
||||
android:label="app_name"
|
||||
android:name="com.example.androidextensions.MyActivity">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package com.example.androidextensions
|
||||
|
||||
import android.os.Bundle
|
||||
import android.app.Activity
|
||||
import kotlinx.android.synthetic.main.activity_main.textView
|
||||
import kotlinx.android.parcel.Parcelize
|
||||
import android.os.Parcelable
|
||||
|
||||
class HomeActivity : Activity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_main)
|
||||
|
||||
this.textView.setText("Hello, world!")
|
||||
}
|
||||
}
|
||||
|
||||
@Parcelize
|
||||
class User(val firstName: String, val lastName: String) : Parcelable
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<TextView android:id="@+id/textView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
</RelativeLayout>
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
<resources>
|
||||
<string name="app_name">kotlin</string>
|
||||
</resources>
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenLocal()
|
||||
jcenter()
|
||||
maven { url 'https://maven.google.com' }
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
classpath "com.android.tools.build:gradle:$android_tools_version"
|
||||
}
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
mavenLocal()
|
||||
jcenter()
|
||||
maven { url 'https://maven.google.com' }
|
||||
}
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
org.gradle.jvmargs=-ea -XX:MaxPermSize=512m
|
||||
+1
@@ -0,0 +1 @@
|
||||
include ':app'
|
||||
+16
@@ -18,6 +18,7 @@ package org.jetbrains.kotlin.gradle.internal
|
||||
|
||||
import groovy.lang.Closure
|
||||
import org.gradle.util.Configurable
|
||||
import java.util.*
|
||||
|
||||
enum class CacheImplementation(val optionName: String) {
|
||||
HASH_MAP("hashMap"),
|
||||
@@ -25,6 +26,19 @@ enum class CacheImplementation(val optionName: String) {
|
||||
NONE("none")
|
||||
}
|
||||
|
||||
enum class AndroidExtensionsFeature(val featureName: String) {
|
||||
VIEWS("views"),
|
||||
PARCELIZE("parcelize");
|
||||
|
||||
internal companion object {
|
||||
internal fun parseFeatures(features: Set<String>): SortedSet<AndroidExtensionsFeature> {
|
||||
fun find(name: String) = AndroidExtensionsFeature.values().firstOrNull { it.featureName == name }
|
||||
?: error("Can't find Android Extensions feature $name")
|
||||
return features.mapTo(sortedSetOf()) { find(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
open class AndroidExtensionsExtension : Configurable<AndroidExtensionsExtension> {
|
||||
private lateinit var onEvaluatedHandler: (AndroidExtensionsExtension) -> Unit
|
||||
|
||||
@@ -34,6 +48,8 @@ open class AndroidExtensionsExtension : Configurable<AndroidExtensionsExtension>
|
||||
|
||||
open var isExperimental: Boolean = false
|
||||
|
||||
open var features: Set<String> = AndroidExtensionsFeature.values().mapTo(mutableSetOf()) { it.featureName }
|
||||
|
||||
open var defaultCacheImplementation: CacheImplementation = CacheImplementation.HASH_MAP
|
||||
|
||||
override fun configure(closure: Closure<*>): AndroidExtensionsExtension {
|
||||
|
||||
+5
@@ -104,6 +104,7 @@ class AndroidSubplugin : KotlinGradleSubplugin<KotlinCompile> {
|
||||
val sourceSets = androidExtension.sourceSets
|
||||
|
||||
val pluginOptions = arrayListOf<SubpluginOption>()
|
||||
pluginOptions += SubpluginOption("features", AndroidExtensionsFeature.VIEWS.featureName)
|
||||
|
||||
val mainSourceSet = sourceSets.getByName("main")
|
||||
val manifestFile = mainSourceSet.manifest.srcFile
|
||||
@@ -157,6 +158,8 @@ class AndroidSubplugin : KotlinGradleSubplugin<KotlinCompile> {
|
||||
androidProjectHandler as? AbstractAndroidProjectHandler<Any?> ?: return emptyList()
|
||||
|
||||
val pluginOptions = arrayListOf<SubpluginOption>()
|
||||
pluginOptions += SubpluginOption("features",
|
||||
AndroidExtensionsFeature.parseFeatures(androidExtensionsExtension.features).joinToString(",") { it.featureName })
|
||||
|
||||
pluginOptions += SubpluginOption("experimental", "true")
|
||||
pluginOptions += SubpluginOption("defaultCacheImplementation", androidExtensionsExtension.defaultCacheImplementation.optionName)
|
||||
@@ -208,6 +211,8 @@ class AndroidSubplugin : KotlinGradleSubplugin<KotlinCompile> {
|
||||
addVariant(sourceSetName, (srcDirs - commonResDirectories).sorted())
|
||||
}
|
||||
|
||||
project.logger.warn("Blah: " + allResDirectories)
|
||||
|
||||
return wrapPluginOptions(pluginOptions, "configuration")
|
||||
}
|
||||
|
||||
|
||||
+51
-19
@@ -54,6 +54,12 @@ object AndroidConfigurationKeys {
|
||||
val PACKAGE = CompilerConfigurationKey.create<String>("application package fq name")
|
||||
val EXPERIMENTAL = CompilerConfigurationKey.create<String>("enable experimental features")
|
||||
val DEFAULT_CACHE_IMPL = CompilerConfigurationKey.create<String>("default cache implementation")
|
||||
val FEATURES = CompilerConfigurationKey.create<Set<AndroidExtensionsFeature>>("enabled features")
|
||||
}
|
||||
|
||||
enum class AndroidExtensionsFeature(val featureName: String) {
|
||||
VIEWS("views"),
|
||||
PARCELIZE("parcelize")
|
||||
}
|
||||
|
||||
class AndroidCommandLineProcessor : CommandLineProcessor {
|
||||
@@ -68,6 +74,9 @@ class AndroidCommandLineProcessor : CommandLineProcessor {
|
||||
val DEFAULT_CACHE_IMPL_OPTION = CliOption(
|
||||
"defaultCacheImplementation", "hashMap/sparseArray/none", "Default cache implementation for module", required = false)
|
||||
|
||||
val FEATURES_OPTION = CliOption(
|
||||
"features", AndroidExtensionsFeature.values().joinToString(" | "), "Enabled features", required = false)
|
||||
|
||||
/* This option is just for saving Android Extensions status in Kotlin facet. It should not be supported from CLI. */
|
||||
val ENABLED_OPTION: CliOption = CliOption("enabled", "true/false", "Enable Android Extensions", required = false)
|
||||
}
|
||||
@@ -75,7 +84,7 @@ class AndroidCommandLineProcessor : CommandLineProcessor {
|
||||
override val pluginId: String = ANDROID_COMPILER_PLUGIN_ID
|
||||
|
||||
override val pluginOptions: Collection<CliOption>
|
||||
= listOf(VARIANT_OPTION, PACKAGE_OPTION, EXPERIMENTAL_OPTION, DEFAULT_CACHE_IMPL_OPTION, CONFIGURATION)
|
||||
= listOf(VARIANT_OPTION, PACKAGE_OPTION, EXPERIMENTAL_OPTION, DEFAULT_CACHE_IMPL_OPTION, CONFIGURATION, FEATURES_OPTION)
|
||||
|
||||
override fun processOption(option: CliOption, value: String, configuration: CompilerConfiguration) {
|
||||
when (option) {
|
||||
@@ -84,6 +93,12 @@ class AndroidCommandLineProcessor : CommandLineProcessor {
|
||||
EXPERIMENTAL_OPTION -> configuration.put(AndroidConfigurationKeys.EXPERIMENTAL, value)
|
||||
DEFAULT_CACHE_IMPL_OPTION -> configuration.put(AndroidConfigurationKeys.DEFAULT_CACHE_IMPL, value)
|
||||
CONFIGURATION -> configuration.applyOptionsFrom(decodePluginOptions(value), pluginOptions)
|
||||
FEATURES_OPTION -> {
|
||||
val features = value.split(',').mapNotNullTo(mutableSetOf()) {
|
||||
name -> AndroidExtensionsFeature.values().firstOrNull { it.featureName == name }
|
||||
}
|
||||
configuration.put(AndroidConfigurationKeys.FEATURES, features)
|
||||
}
|
||||
else -> throw CliOptionProcessingException("Unknown option: ${option.name}")
|
||||
}
|
||||
}
|
||||
@@ -97,6 +112,37 @@ class AndroidComponentRegistrar : ComponentRegistrar {
|
||||
ClassBuilderInterceptorExtension.registerExtension(project, ParcelableClinitClassBuilderInterceptorExtension())
|
||||
}
|
||||
|
||||
private fun parseVariant(s: String): AndroidVariant? {
|
||||
val parts = s.split(';')
|
||||
if (parts.size < 2) return null
|
||||
return AndroidVariant(parts[0], parts.drop(1))
|
||||
}
|
||||
|
||||
fun registerViewExtensions(configuration: CompilerConfiguration, isExperimental: Boolean, project: MockProject) {
|
||||
val applicationPackage = configuration.get(AndroidConfigurationKeys.PACKAGE) ?: return
|
||||
val variants = configuration.get(AndroidConfigurationKeys.VARIANT)?.mapNotNull { parseVariant(it) } ?: return
|
||||
val globalCacheImpl = parseCacheImplementationType(configuration.get(AndroidConfigurationKeys.DEFAULT_CACHE_IMPL))
|
||||
|
||||
if (variants.isEmpty() || applicationPackage.isEmpty()) {
|
||||
return
|
||||
}
|
||||
|
||||
val layoutXmlFileManager = CliAndroidLayoutXmlFileManager(project, applicationPackage, variants)
|
||||
project.registerService(AndroidLayoutXmlFileManager::class.java, layoutXmlFileManager)
|
||||
|
||||
ExpressionCodegenExtension.registerExtension(project,
|
||||
CliAndroidExtensionsExpressionCodegenExtension(isExperimental, globalCacheImpl))
|
||||
|
||||
StorageComponentContainerContributor.registerExtension(project,
|
||||
AndroidExtensionPropertiesComponentContainerContributor())
|
||||
|
||||
ClassBuilderInterceptorExtension.registerExtension(project,
|
||||
CliAndroidOnDestroyClassBuilderInterceptorExtension(globalCacheImpl))
|
||||
|
||||
PackageFragmentProviderExtension.registerExtension(project,
|
||||
CliAndroidPackageFragmentProviderExtension(isExperimental))
|
||||
}
|
||||
|
||||
fun parseCacheImplementationType(s: String?): CacheImplementation = when (s) {
|
||||
"sparseArray" -> CacheImplementation.SPARSE_ARRAY
|
||||
"none" -> CacheImplementation.NO_CACHE
|
||||
@@ -105,31 +151,17 @@ class AndroidComponentRegistrar : ComponentRegistrar {
|
||||
}
|
||||
|
||||
override fun registerProjectComponents(project: MockProject, configuration: CompilerConfiguration) {
|
||||
val applicationPackage = configuration.get(AndroidConfigurationKeys.PACKAGE) ?: return
|
||||
val variants = configuration.get(AndroidConfigurationKeys.VARIANT)?.mapNotNull { parseVariant(it) } ?: return
|
||||
val features = configuration.get(AndroidConfigurationKeys.FEATURES) ?: AndroidExtensionsFeature.values().toSet()
|
||||
val isExperimental = configuration.get(AndroidConfigurationKeys.EXPERIMENTAL) == "true"
|
||||
val globalCacheImpl = parseCacheImplementationType(configuration.get(AndroidConfigurationKeys.DEFAULT_CACHE_IMPL))
|
||||
|
||||
if (isExperimental) {
|
||||
if (isExperimental && AndroidExtensionsFeature.PARCELIZE in features) {
|
||||
registerParcelExtensions(project)
|
||||
}
|
||||
|
||||
if (variants.isNotEmpty() && !applicationPackage.isNullOrBlank()) {
|
||||
val layoutXmlFileManager = CliAndroidLayoutXmlFileManager(project, applicationPackage!!, variants)
|
||||
project.registerService(AndroidLayoutXmlFileManager::class.java, layoutXmlFileManager)
|
||||
|
||||
ExpressionCodegenExtension.registerExtension(project, CliAndroidExtensionsExpressionCodegenExtension(isExperimental, globalCacheImpl))
|
||||
StorageComponentContainerContributor.registerExtension(project, AndroidExtensionPropertiesComponentContainerContributor())
|
||||
ClassBuilderInterceptorExtension.registerExtension(project, CliAndroidOnDestroyClassBuilderInterceptorExtension(globalCacheImpl))
|
||||
PackageFragmentProviderExtension.registerExtension(project, CliAndroidPackageFragmentProviderExtension(isExperimental))
|
||||
if (AndroidExtensionsFeature.VIEWS in features) {
|
||||
registerViewExtensions(configuration, isExperimental, project)
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseVariant(s: String): AndroidVariant? {
|
||||
val parts = s.split(';')
|
||||
if (parts.size < 2) return null
|
||||
return AndroidVariant(parts[0], parts.drop(1))
|
||||
}
|
||||
}
|
||||
|
||||
class AndroidExtensionPropertiesComponentContainerContributor : StorageComponentContainerContributor {
|
||||
|
||||
Reference in New Issue
Block a user