diff --git a/tangle-gradle-plugin/api/tangle-gradle-plugin.api b/tangle-gradle-plugin/api/tangle-gradle-plugin.api index ddeb9de6..d66219ba 100644 --- a/tangle-gradle-plugin/api/tangle-gradle-plugin.api +++ b/tangle-gradle-plugin/api/tangle-gradle-plugin.api @@ -1,12 +1,10 @@ public abstract class tangle/inject/gradle/TangleExtension { - public static final field FRAGMENTS_ENABLED Z - public static final field WORK_ENABLED Z public fun (Lorg/gradle/api/model/ObjectFactory;)V - public final fun getFragmentsEnabled ()Z + public final fun getFragmentsEnabled ()Ljava/lang/Boolean; public final fun getViewModelOptions ()Ltangle/inject/gradle/ViewModelOptions; - public final fun getWorkEnabled ()Z - public final fun setFragmentsEnabled (Z)V - public final fun setWorkEnabled (Z)V + public final fun getWorkEnabled ()Ljava/lang/Boolean; + public final fun setFragmentsEnabled (Ljava/lang/Boolean;)V + public final fun setWorkEnabled (Ljava/lang/Boolean;)V public final fun viewModelOptions (Lorg/gradle/api/Action;)V } @@ -20,13 +18,13 @@ public class tangle/inject/gradle/TanglePlugin : org/gradle/api/plugins/BasePlug public class tangle/inject/gradle/ViewModelOptions { public fun (Lorg/gradle/api/model/ObjectFactory;)V - public final fun getActivitiesEnabled ()Z - public final fun getComposeEnabled ()Z - public final fun getEnabled ()Z - public final fun getFragmentsEnabled ()Z - public final fun setActivitiesEnabled (Z)V - public final fun setComposeEnabled (Z)V - public final fun setEnabled (Z)V - public final fun setFragmentsEnabled (Z)V + public final fun getActivitiesEnabled ()Ljava/lang/Boolean; + public final fun getComposeEnabled ()Ljava/lang/Boolean; + public final fun getEnabled ()Ljava/lang/Boolean; + public final fun getFragmentsEnabled ()Ljava/lang/Boolean; + public final fun setActivitiesEnabled (Ljava/lang/Boolean;)V + public final fun setComposeEnabled (Ljava/lang/Boolean;)V + public final fun setEnabled (Ljava/lang/Boolean;)V + public final fun setFragmentsEnabled (Ljava/lang/Boolean;)V } diff --git a/tangle-gradle-plugin/src/main/kotlin/tangle/inject/gradle/AndroidDependencyConfig.kt b/tangle-gradle-plugin/src/main/kotlin/tangle/inject/gradle/AndroidDependencyConfig.kt new file mode 100644 index 00000000..97c54584 --- /dev/null +++ b/tangle-gradle-plugin/src/main/kotlin/tangle/inject/gradle/AndroidDependencyConfig.kt @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2021 Rick Busarow + * 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 tangle.inject.gradle + +import org.gradle.api.Project +import org.gradle.api.artifacts.Configuration +import org.gradle.api.artifacts.ExternalModuleDependency + +internal data class AndroidDependencyConfig( + val configName: String, + val activities: Boolean, + val compose: Boolean, + val fragments: Boolean, + val viewModels: Boolean, + val workManager: Boolean +) + +private const val ACTIVITY_GROUP = "androidx.activity" +private const val COMPOSE_GROUP = "androidx.compose.ui" +private const val FRAGMENT_GROUP = "androidx.fragment" +private const val WORKMANAGER_GROUP = "androidx.work" +private const val LIFECYCLE_GROUP = "androidx.lifecycle" +private const val VIEWMODEL_MODULE_PREFIX = "lifecycle-viewmodel" + +internal fun Project.projectAndroidDependencyConfigs() = configurations + .filterNot { it.name == "ktlintRuleset" } + .mapNotNull { config -> + + config.androidDependencyConfigOrNull() + } + +private fun Configuration.androidDependencyConfigOrNull(): AndroidDependencyConfig? { + var activities = false + var compose = false + var fragments = false + var viewModels = false + var workManager = false + + val deps = dependencies + .withType(org.gradle.api.artifacts.ExternalModuleDependency::class.java) + .takeIf { it.isNotEmpty() } + ?: return null + + deps.forEach { dependency -> + + if (dependency.isActivities()) { + activities = true + return@forEach + } + if (dependency.isCompose()) { + compose = true + return@forEach + } + if (dependency.isFragments()) { + fragments = true + return@forEach + } + if (dependency.isViewModels()) { + viewModels = true + return@forEach + } + if (dependency.isWorkManager()) { + workManager = true + return@forEach + } + } + + return AndroidDependencyConfig( + configName = name, + activities = activities, + compose = compose, + fragments = fragments, + viewModels = viewModels, + workManager = workManager + ) +} + +private fun ExternalModuleDependency.isActivities(): Boolean = group == ACTIVITY_GROUP +private fun ExternalModuleDependency.isCompose(): Boolean = group == COMPOSE_GROUP +private fun ExternalModuleDependency.isFragments(): Boolean = group == FRAGMENT_GROUP +private fun ExternalModuleDependency.isWorkManager(): Boolean = group == WORKMANAGER_GROUP +private fun ExternalModuleDependency.isViewModels(): Boolean = + group == LIFECYCLE_GROUP && module.name.startsWith(VIEWMODEL_MODULE_PREFIX) diff --git a/tangle-gradle-plugin/src/main/kotlin/tangle/inject/gradle/TangleExtension.kt b/tangle-gradle-plugin/src/main/kotlin/tangle/inject/gradle/TangleExtension.kt index 225cdc3a..2e7cbcec 100644 --- a/tangle-gradle-plugin/src/main/kotlin/tangle/inject/gradle/TangleExtension.kt +++ b/tangle-gradle-plugin/src/main/kotlin/tangle/inject/gradle/TangleExtension.kt @@ -29,16 +29,24 @@ public abstract class TangleExtension @Inject constructor( /** * Fragment code generation and API's enabled * - * default value is true + * If this property is set, then Tangle will use that setting regardless of what Androidx + * dependencies are in the classpath. + * + * If this property is not set, Tangle will automatically enable its Fragment dependencies if the + * module declares any `androidx.fragment` group dependencies. */ - public var fragmentsEnabled: Boolean by objectFactory.property(FRAGMENTS_ENABLED) + public var fragmentsEnabled: Boolean? by objectFactory.property() /** * Worker/WorkManager code generation and API's enabled * - * default value is true + * If this property is set, then Tangle will use that setting regardless of what Androidx + * dependencies are in the classpath. + * + * If this property is not set, Tangle will automatically enable its Worker/WorkManager + * dependencies if the module declares any `androidx.work` group dependencies. */ - public var workEnabled: Boolean by objectFactory.property(WORK_ENABLED) + public var workEnabled: Boolean? by objectFactory.property() /** * ViewModel configuration options @@ -51,56 +59,18 @@ public abstract class TangleExtension @Inject constructor( public fun viewModelOptions(action: Action) { action.execute(viewModelOptions) } - - internal companion object { - const val FRAGMENTS_ENABLED = true - const val WORK_ENABLED = true - } -} - -public open class ViewModelOptions @Inject constructor( - objectFactory: ObjectFactory -) { - - /** - * ViewModel code generation enabled - * - * default value is true - */ - public var enabled: Boolean by objectFactory.property(true) - - /** - * Activity ViewModel API's enabled - * - * default value is true - */ - public var activitiesEnabled: Boolean by objectFactory.property(true) - - /** - * Compose ViewModel API's enabled - * - * default value is false - */ - public var composeEnabled: Boolean by objectFactory.property(false) - - /** - * Fragment ViewModel API's enabled - * - * default value is true - */ - public var fragmentsEnabled: Boolean by objectFactory.property(true) } -internal inline fun ObjectFactory.property(initialValue: T): ReadWriteProperty = - object : ReadWriteProperty { +internal fun ObjectFactory.property(): ReadWriteProperty = + object : ReadWriteProperty { - val delegate = property(T::class.java).convention(initialValue) + val delegate = property(Boolean::class.java) - override fun getValue(thisRef: Any, property: KProperty<*>): T { - return delegate.get() + override fun getValue(thisRef: Any, property: KProperty<*>): Boolean? { + return delegate.orNull } - override fun setValue(thisRef: Any, property: KProperty<*>, value: T) { + override fun setValue(thisRef: Any, property: KProperty<*>, value: Boolean?) { delegate.set(value) } } diff --git a/tangle-gradle-plugin/src/main/kotlin/tangle/inject/gradle/TanglePlugin.kt b/tangle-gradle-plugin/src/main/kotlin/tangle/inject/gradle/TanglePlugin.kt index a2eeb8f5..9d540d46 100644 --- a/tangle-gradle-plugin/src/main/kotlin/tangle/inject/gradle/TanglePlugin.kt +++ b/tangle-gradle-plugin/src/main/kotlin/tangle/inject/gradle/TanglePlugin.kt @@ -42,38 +42,48 @@ public open class TanglePlugin : BasePlugin() { ) } - target.pluginManager.withPlugin(ANVIL_ID) { + target.addImplementation("tangle-api") + target.addAnvil("tangle-compiler") - target.addImplementation("tangle-api") + target.addFeatureDependencies(extension) + } + } - if (extension.fragmentsEnabled) { - target.addImplementation("tangle-fragment-api") - target.addAnvil("tangle-fragment-compiler") - } + private fun Project.addFeatureDependencies( + extension: TangleExtension + ) { + + val viewModelOptions = extension.viewModelOptions - if (extension.workEnabled) { - target.addImplementation("tangle-work-api") - target.addAnvil("tangle-work-compiler") + projectAndroidDependencyConfigs() + .forEach { config -> + + if (extension.fragmentsEnabled ?: config.fragments) { + addImplementation("tangle-fragment-api") + addAnvil("tangle-fragment-compiler") } - val viewModelOptions = extension.viewModelOptions + if (extension.workEnabled ?: config.workManager) { + addImplementation("tangle-work-api") + addAnvil("tangle-work-compiler") + } - if (viewModelOptions.enabled) { - target.addImplementation("tangle-viewmodel-api") - target.addAnvil("tangle-viewmodel-compiler") + if (viewModelOptions.enabled ?: config.viewModels) { + addImplementation("tangle-viewmodel-api") + addAnvil("tangle-viewmodel-compiler") - if (viewModelOptions.activitiesEnabled) { - target.addImplementation("tangle-viewmodel-activity") + if (viewModelOptions.activitiesEnabled ?: config.activities) { + addImplementation("tangle-viewmodel-activity") } - if (viewModelOptions.composeEnabled) { - target.addImplementation("tangle-viewmodel-compose") + + if (viewModelOptions.composeEnabled ?: config.compose) { + addImplementation("tangle-viewmodel-compose") } - if (viewModelOptions.fragmentsEnabled) { - target.addImplementation("tangle-viewmodel-fragment") + if (viewModelOptions.fragmentsEnabled ?: config.fragments) { + addImplementation("tangle-viewmodel-fragment") } } } - } } private fun Project.addAnvil(name: String) { diff --git a/tangle-gradle-plugin/src/main/kotlin/tangle/inject/gradle/ViewModelOptions.kt b/tangle-gradle-plugin/src/main/kotlin/tangle/inject/gradle/ViewModelOptions.kt new file mode 100644 index 00000000..65de5c7c --- /dev/null +++ b/tangle-gradle-plugin/src/main/kotlin/tangle/inject/gradle/ViewModelOptions.kt @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2021 Rick Busarow + * 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 tangle.inject.gradle + +import org.gradle.api.model.ObjectFactory +import javax.inject.Inject + +public open class ViewModelOptions @Inject constructor( + objectFactory: ObjectFactory +) { + + /** + * ViewModel code generation and API's enabled + * + * If this property is set, then Tangle will use that setting regardless of what Androidx + * dependencies are in the classpath. + * + * If this property is not set, Tangle will automatically enable its ViewModel dependencies if the + * module declares any `androidx.lifecycle:lifecycle-viewmodel*` group dependencies. + */ + public var enabled: Boolean? by objectFactory.property() + + /** + * Activity ViewModel API's enabled + * + * If this property is set, then Tangle will use that setting regardless of what Androidx + * dependencies are in the classpath. + * + * If this property is not set, Tangle will automatically enable its ViewModel-Activity + * dependencies if ViewModel code generation is enabled via [enabled] and the module declares any + * `androidx.activity` group dependencies. + */ + public var activitiesEnabled: Boolean? by objectFactory.property() + + /** + * Compose ViewModel API's enabled + * + * If this property is set, then Tangle will use that setting regardless of what Androidx + * dependencies are in the classpath. + * + * If this property is not set, Tangle will automatically enable its ViewModel-Compose + * dependencies if ViewModel code generation is enabled via [enabled] and the module declares any + * `androidx.compose.ui` group dependencies. + */ + public var composeEnabled: Boolean? by objectFactory.property() + + /** + * Fragment ViewModel API's enabled + * + * If this property is set, then Tangle will use that setting regardless of what Androidx + * dependencies are in the classpath. + * + * If this property is not set, Tangle will automatically enable its ViewModel-Fragment + * dependencies if ViewModel code generation is enabled via [enabled] and the module declares any + * `androidx.fragment` group dependencies. + */ + public var fragmentsEnabled: Boolean? by objectFactory.property() +} diff --git a/tangle-gradle-plugin/src/test/kotlin/tangle/inject/gradle/BasePluginTest.kt b/tangle-gradle-plugin/src/test/kotlin/tangle/inject/gradle/BasePluginTest.kt index baeba259..92be5979 100644 --- a/tangle-gradle-plugin/src/test/kotlin/tangle/inject/gradle/BasePluginTest.kt +++ b/tangle-gradle-plugin/src/test/kotlin/tangle/inject/gradle/BasePluginTest.kt @@ -49,13 +49,15 @@ public abstract class BasePluginTest : HermitJUnit5() { } @Language("RegExp") - public fun BuildResult.deps(): List = output + public fun BuildResult.tangleDeps(): List = output .replace("[\\s\\S]*> Task :module:\\S*\\s*".toRegex(), "") .replace( - "\\s*BUILD SUCCESSFUL in \\d*[m]*s\\s*\\d* actionable task: \\d* executed\\s*".toRegex(), "" + "\\s*BUILD SUCCESSFUL in .*\\s*\\d* actionable task: \\d* executed\\s*".toRegex(), "" ) .lines() .filterNot { it.isBlank() } + .filterNot { it.startsWith("api androidx") } + .filterNot { it.contains("com.squareup.anvil") } .sorted() public fun GradleRunner.shouldFailWithMessage(expectedMessage: String) { diff --git a/tangle-gradle-plugin/src/test/kotlin/tangle/inject/gradle/TanglePluginTest.kt b/tangle-gradle-plugin/src/test/kotlin/tangle/inject/gradle/TanglePluginTest.kt index f3762ef1..8b70589a 100644 --- a/tangle-gradle-plugin/src/test/kotlin/tangle/inject/gradle/TanglePluginTest.kt +++ b/tangle-gradle-plugin/src/test/kotlin/tangle/inject/gradle/TanglePluginTest.kt @@ -86,11 +86,20 @@ public class TanglePluginTest : BasePluginTest() { } } + dependencies { + $activities + $fragments + $viewModels + $compose + $workManager + } + ${listDepsTasks()} """.trimIndent() ) - build("deps").deps() shouldBe listOf( + build("deps").tangleDeps() shouldBe listOf( + "anvil com.rickbusarow.tangle:tangle-compiler", "anvil com.rickbusarow.tangle:tangle-fragment-compiler", "anvil com.rickbusarow.tangle:tangle-viewmodel-compiler", "anvil com.rickbusarow.tangle:tangle-work-compiler", @@ -98,6 +107,7 @@ public class TanglePluginTest : BasePluginTest() { "implementation com.rickbusarow.tangle:tangle-fragment-api", "implementation com.rickbusarow.tangle:tangle-viewmodel-activity", "implementation com.rickbusarow.tangle:tangle-viewmodel-api", + "implementation com.rickbusarow.tangle:tangle-viewmodel-compose", "implementation com.rickbusarow.tangle:tangle-viewmodel-fragment", "implementation com.rickbusarow.tangle:tangle-work-api" ) @@ -125,19 +135,29 @@ public class TanglePluginTest : BasePluginTest() { } tangle { - fragmentsEnabled = false // default is true + fragmentsEnabled = false // default is null + } + + dependencies { + $activities + $fragments + $viewModels + $compose + $workManager } ${listDepsTasks()} """.trimIndent() ) - build("deps").deps() shouldBe listOf( + build("deps").tangleDeps() shouldBe listOf( + "anvil com.rickbusarow.tangle:tangle-compiler", "anvil com.rickbusarow.tangle:tangle-viewmodel-compiler", "anvil com.rickbusarow.tangle:tangle-work-compiler", "implementation com.rickbusarow.tangle:tangle-api", "implementation com.rickbusarow.tangle:tangle-viewmodel-activity", "implementation com.rickbusarow.tangle:tangle-viewmodel-api", + "implementation com.rickbusarow.tangle:tangle-viewmodel-compose", "implementation com.rickbusarow.tangle:tangle-viewmodel-fragment", "implementation com.rickbusarow.tangle:tangle-work-api" ) @@ -165,24 +185,305 @@ public class TanglePluginTest : BasePluginTest() { } tangle { - workEnabled = false // default is true + workEnabled = false // default is null + } + + dependencies { + $activities + $fragments + $viewModels + $compose + $workManager } ${listDepsTasks()} """.trimIndent() ) - build("deps").deps() shouldBe listOf( + build("deps").tangleDeps() shouldBe listOf( + "anvil com.rickbusarow.tangle:tangle-compiler", "anvil com.rickbusarow.tangle:tangle-fragment-compiler", "anvil com.rickbusarow.tangle:tangle-viewmodel-compiler", "implementation com.rickbusarow.tangle:tangle-api", "implementation com.rickbusarow.tangle:tangle-fragment-api", "implementation com.rickbusarow.tangle:tangle-viewmodel-activity", "implementation com.rickbusarow.tangle:tangle-viewmodel-api", + "implementation com.rickbusarow.tangle:tangle-viewmodel-compose", "implementation com.rickbusarow.tangle:tangle-viewmodel-fragment" ) } + @TestFactory + fun `only base compiler and api should be enabled without corresponding androidx dependencies`() = + test { + + //language=kotlin + module( + """ + plugins { + id("com.android.library") + kotlin("android") + id("com.rickbusarow.tangle") + } + + android { + compileSdk = 30 + + defaultConfig { + minSdk = 23 + targetSdk = 30 + } + } + + dependencies { + } + + ${listDepsTasks()} + """.trimIndent() + ) + + build("deps").tangleDeps() shouldBe listOf( + "anvil com.rickbusarow.tangle:tangle-compiler", + "implementation com.rickbusarow.tangle:tangle-api" + ) + } + + @TestFactory + fun `fragment compiler and api should be automatically enabled with androidx fragment dependencies`() = + test { + + //language=kotlin + module( + """ + plugins { + id("com.android.library") + kotlin("android") + id("com.rickbusarow.tangle") + } + + android { + compileSdk = 30 + + defaultConfig { + minSdk = 23 + targetSdk = 30 + } + } + + dependencies { + $fragments + } + + ${listDepsTasks()} + """.trimIndent() + ) + + build("deps").tangleDeps() shouldBe listOf( + "anvil com.rickbusarow.tangle:tangle-compiler", + "anvil com.rickbusarow.tangle:tangle-fragment-compiler", + "implementation com.rickbusarow.tangle:tangle-api", + "implementation com.rickbusarow.tangle:tangle-fragment-api" + ) + } + + @TestFactory + fun `viewmodel compiler and api should be automatically enabled with androidx viewmodel dependencies`() = + test { + + //language=kotlin + module( + """ + plugins { + id("com.android.library") + kotlin("android") + id("com.rickbusarow.tangle") + } + + android { + compileSdk = 30 + + defaultConfig { + minSdk = 23 + targetSdk = 30 + } + } + + dependencies { + $viewModels + } + + ${listDepsTasks()} + """.trimIndent() + ) + + build("deps").tangleDeps() shouldBe listOf( + "anvil com.rickbusarow.tangle:tangle-compiler", + "anvil com.rickbusarow.tangle:tangle-viewmodel-compiler", + "implementation com.rickbusarow.tangle:tangle-api", + "implementation com.rickbusarow.tangle:tangle-viewmodel-api" + ) + } + + @TestFactory + fun `viewmodel activity api should be automatically enabled with androidx viewmodel and activity dependencies`() = + test { + + //language=kotlin + module( + """ + plugins { + id("com.android.library") + kotlin("android") + id("com.rickbusarow.tangle") + } + + android { + compileSdk = 30 + + defaultConfig { + minSdk = 23 + targetSdk = 30 + } + } + + dependencies { + $activities + $viewModels + } + + ${listDepsTasks()} + """.trimIndent() + ) + + build("deps").tangleDeps() shouldBe listOf( + "anvil com.rickbusarow.tangle:tangle-compiler", + "anvil com.rickbusarow.tangle:tangle-viewmodel-compiler", + "implementation com.rickbusarow.tangle:tangle-api", + "implementation com.rickbusarow.tangle:tangle-viewmodel-activity", + "implementation com.rickbusarow.tangle:tangle-viewmodel-api" + ) + } + + @TestFactory + fun `viewmodel fragment api should be automatically enabled with androidx viewmodel and fragment dependencies`() = + test { + + //language=kotlin + module( + """ + plugins { + id("com.android.library") + kotlin("android") + id("com.rickbusarow.tangle") + } + + android { + compileSdk = 30 + + defaultConfig { + minSdk = 23 + targetSdk = 30 + } + } + + dependencies { + $fragments + $viewModels + } + + ${listDepsTasks()} + """.trimIndent() + ) + + build("deps").tangleDeps() shouldBe listOf( + "anvil com.rickbusarow.tangle:tangle-compiler", + "anvil com.rickbusarow.tangle:tangle-fragment-compiler", + "anvil com.rickbusarow.tangle:tangle-viewmodel-compiler", + "implementation com.rickbusarow.tangle:tangle-api", + "implementation com.rickbusarow.tangle:tangle-fragment-api", + "implementation com.rickbusarow.tangle:tangle-viewmodel-api", + "implementation com.rickbusarow.tangle:tangle-viewmodel-fragment" + ) + } + + @TestFactory + fun `viewmodel compose api should be automatically enabled with androidx viewmodel and compose dependencies`() = + test { + + //language=kotlin + module( + """ + plugins { + id("com.android.library") + kotlin("android") + id("com.rickbusarow.tangle") + } + + android { + compileSdk = 30 + + defaultConfig { + minSdk = 23 + targetSdk = 30 + } + } + + dependencies { + $compose + $viewModels + } + + ${listDepsTasks()} + """.trimIndent() + ) + + build("deps").tangleDeps() shouldBe listOf( + "anvil com.rickbusarow.tangle:tangle-compiler", + "anvil com.rickbusarow.tangle:tangle-viewmodel-compiler", + "implementation com.rickbusarow.tangle:tangle-api", + "implementation com.rickbusarow.tangle:tangle-viewmodel-api", + "implementation com.rickbusarow.tangle:tangle-viewmodel-compose" + ) + } + + @TestFactory + fun `work compiler and api should be automatically enabled with androidx work dependencies`() = + test { + + //language=kotlin + module( + """ + plugins { + id("com.android.library") + kotlin("android") + id("com.rickbusarow.tangle") + } + + android { + compileSdk = 30 + + defaultConfig { + minSdk = 23 + targetSdk = 30 + } + } + + dependencies { + $workManager + } + + ${listDepsTasks()} + """.trimIndent() + ) + + build("deps").tangleDeps() shouldBe listOf( + "anvil com.rickbusarow.tangle:tangle-compiler", + "anvil com.rickbusarow.tangle:tangle-work-compiler", + "implementation com.rickbusarow.tangle:tangle-api", + "implementation com.rickbusarow.tangle:tangle-work-api" + ) + } + @TestFactory fun `disabling viewModels in config should disable its dependencies`() = test { @@ -206,15 +507,24 @@ public class TanglePluginTest : BasePluginTest() { tangle { viewModelOptions { - enabled = false // default is true + enabled = false // default is null } } + dependencies { + $activities + $fragments + $viewModels + $compose + $workManager + } + ${listDepsTasks()} """.trimIndent() ) - build("deps").deps() shouldBe listOf( + build("deps").tangleDeps() shouldBe listOf( + "anvil com.rickbusarow.tangle:tangle-compiler", "anvil com.rickbusarow.tangle:tangle-fragment-compiler", "anvil com.rickbusarow.tangle:tangle-work-compiler", "implementation com.rickbusarow.tangle:tangle-api", @@ -247,15 +557,24 @@ public class TanglePluginTest : BasePluginTest() { tangle { viewModelOptions { - fragmentsEnabled = false // default is true + fragmentsEnabled = false // default is null } } + dependencies { + $activities + $fragments + $viewModels + $compose + $workManager + } + ${listDepsTasks()} """.trimIndent() ) - build("deps").deps() shouldBe listOf( + build("deps").tangleDeps() shouldBe listOf( + "anvil com.rickbusarow.tangle:tangle-compiler", "anvil com.rickbusarow.tangle:tangle-fragment-compiler", "anvil com.rickbusarow.tangle:tangle-viewmodel-compiler", "anvil com.rickbusarow.tangle:tangle-work-compiler", @@ -263,6 +582,7 @@ public class TanglePluginTest : BasePluginTest() { "implementation com.rickbusarow.tangle:tangle-fragment-api", "implementation com.rickbusarow.tangle:tangle-viewmodel-activity", "implementation com.rickbusarow.tangle:tangle-viewmodel-api", + "implementation com.rickbusarow.tangle:tangle-viewmodel-compose", "implementation com.rickbusarow.tangle:tangle-work-api" ) } @@ -291,28 +611,38 @@ public class TanglePluginTest : BasePluginTest() { tangle { viewModelOptions { - activitiesEnabled = false // default is true + activitiesEnabled = false // default is null } } + dependencies { + $activities + $fragments + $viewModels + $compose + $workManager + } + ${listDepsTasks()} """.trimIndent() ) - build("deps").deps() shouldBe listOf( + build("deps").tangleDeps() shouldBe listOf( + "anvil com.rickbusarow.tangle:tangle-compiler", "anvil com.rickbusarow.tangle:tangle-fragment-compiler", "anvil com.rickbusarow.tangle:tangle-viewmodel-compiler", "anvil com.rickbusarow.tangle:tangle-work-compiler", "implementation com.rickbusarow.tangle:tangle-api", "implementation com.rickbusarow.tangle:tangle-fragment-api", "implementation com.rickbusarow.tangle:tangle-viewmodel-api", + "implementation com.rickbusarow.tangle:tangle-viewmodel-compose", "implementation com.rickbusarow.tangle:tangle-viewmodel-fragment", - "implementation com.rickbusarow.tangle:tangle-work-api" + "implementation com.rickbusarow.tangle:tangle-work-api", ) } @TestFactory - fun `enabling viewModels compose in config should disable the viewmodel compose api dependency`() = + fun `disabling viewModels compose in config should disable the viewmodel compose api dependency`() = test { //language=kotlin @@ -335,15 +665,24 @@ public class TanglePluginTest : BasePluginTest() { tangle { viewModelOptions { - composeEnabled = true // default is false + composeEnabled = false // default is null } } + dependencies { + $activities + $fragments + $viewModels + $compose + $workManager + } + ${listDepsTasks()} """.trimIndent() ) - build("deps").deps() shouldBe listOf( + build("deps").tangleDeps() shouldBe listOf( + "anvil com.rickbusarow.tangle:tangle-compiler", "anvil com.rickbusarow.tangle:tangle-fragment-compiler", "anvil com.rickbusarow.tangle:tangle-viewmodel-compiler", "anvil com.rickbusarow.tangle:tangle-work-compiler", @@ -351,7 +690,6 @@ public class TanglePluginTest : BasePluginTest() { "implementation com.rickbusarow.tangle:tangle-fragment-api", "implementation com.rickbusarow.tangle:tangle-viewmodel-activity", "implementation com.rickbusarow.tangle:tangle-viewmodel-api", - "implementation com.rickbusarow.tangle:tangle-viewmodel-compose", "implementation com.rickbusarow.tangle:tangle-viewmodel-fragment", "implementation com.rickbusarow.tangle:tangle-work-api" ) @@ -370,7 +708,7 @@ public class TanglePluginTest : BasePluginTest() { tangle { viewModelOptions { - composeEnabled = true // default is false + composeEnabled = true // default is null } } @@ -399,4 +737,12 @@ public class TanglePluginTest : BasePluginTest() { } } """.trimIndent() + + public companion object { + private const val activities = "api(\"androidx.activity:activity:1.3.1\")" + private const val compose = "api(\"androidx.compose.ui:ui:1.0.2\")" + private const val fragments = "api(\"androidx.fragment:fragment:1.3.6\")" + private const val viewModels = "api(\"androidx.lifecycle:lifecycle-viewmodel:2.3.1\")" + private const val workManager = "api(\"androidx.work:work-runtime:2.6.0\")" + } } diff --git a/website/docs/configuration.mdx b/website/docs/configuration.mdx index 65332805..9b6227b2 100644 --- a/website/docs/configuration.mdx +++ b/website/docs/configuration.mdx @@ -10,117 +10,17 @@ import TabItem from '@theme/TabItem'; ## Gradle -The simple way to apply Tangle is to just apply the gradle plugin. It will automatically add the -Anvil plugin and Tangle dependencies. +The simple way to apply Tangle is to just [apply the Gradle plugin](gradle-plugin). -You can also just add dependencies yourself, without applying the plugin. - -Note that Tangle is specifically for Android and has Android-specific dependencies, -so it should only be added to Android modules. +You can also just add dependencies yourself, without applying the plugin: - - -```kotlin -// settings.gradle.kts - -pluginManagement { - repositories { - gradlePluginPortal() - } -} -``` - -```kotlin -// root project build.gradle.kts - -plugins { - // add Tangle and Anvil versions to the project's classpath - id("com.squareup.anvil") version apply false - id("com.rickbusarow.tangle") version "0.13.2" apply false -} -``` - -```kotlin -// any Android module's build.gradle.kts - -plugins { - id("android-library") // or application, etc. - kotlin("android") - id("com.rickbusarow.tangle") version "0.13.2" -} - -// optional -tangle { - fragmentsEnabled = true // default is true - workEnabled = true // default is true - - viewModelOptions { - enabled = true // default is true - activitiesEnabled = true // default is true - composeEnabled = true // default is false - fragmentsEnabled = true // default is true - } -} -``` - - - - - -```groovy -// settings.gradle - -pluginManagement { - repositories { - gradlePluginPortal() - } -} -``` - -```groovy -// root project build.gradle - -plugins { - // add Tangle and Anvil versions to the project's classpath - id 'com.squareup.anvil' version apply false - id 'com.rickbusarow.tangle' version "0.13.2" apply false -} -``` - -```groovy -// any Android module's build.gradle - -plugins { - id 'android-library' // or application, etc. - kotlin("android") - id 'com.rickbusarow.tangle' -} - -// optional -tangle { - fragmentsEnabled true // default is true - workEnabled true // default is true - - viewModelOptions { - enabled true // default is true - activitiesEnabled true // default is true - composeEnabled true // default is false - fragmentsEnabled true // default is true - } -} -``` - - - ```kotlin diff --git a/website/docs/fragments/fragments.mdx b/website/docs/fragments/fragments.mdx index b5dd366a..09e3270e 100644 --- a/website/docs/fragments/fragments.mdx +++ b/website/docs/fragments/fragments.mdx @@ -33,7 +33,7 @@ plugins { } tangle { - fragmentsEnabled = true // default is true + fragmentsEnabled = true // default is null } ``` @@ -51,7 +51,7 @@ plugins { // optional tangle { - fragmentsEnabled true // default is true + fragmentsEnabled true // default is null } ``` diff --git a/website/docs/gradle-plugin.mdx b/website/docs/gradle-plugin.mdx new file mode 100644 index 00000000..49769a0f --- /dev/null +++ b/website/docs/gradle-plugin.mdx @@ -0,0 +1,184 @@ +--- +id: gradle-plugin +sidebar_label: Gradle Plugin +title: Gradle Plugin + +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +The simplest way to apply Tangle is via the Gradle plugin. + +The plugin will automatically apply the Anvil compiler plugin and all required Tangle extensions. +By default, the plugin will automatically determine which Tangle dependencies to add by inspecting +the module's Androidx dependencies, and adding the corresponding Tangle features. + +For example, if a project has declared a Fragments dependency like so: + +```kotlin +dependencies { + api("androidx.fragment:fragment") +} +``` + +Then Tangle will add the tangle-fragment dependencies: +- com.rickbusarow.tangle:tangle-fragment-api:0.13.2 +- com.rickbusarow.tangle:tangle-fragment-compiler:0.13.2 + + + + + + +```kotlin +// settings.gradle.kts + +pluginManagement { + repositories { + gradlePluginPortal() + } +} +``` + +```kotlin +// root project build.gradle.kts + +plugins { + // add Tangle and Anvil versions to the project's classpath + id("com.squareup.anvil") version apply false + id("com.rickbusarow.tangle") version "0.13.2" apply false +} +``` + +```kotlin +// any Android module's build.gradle.kts + +plugins { + id("android-library") // or application, etc. + kotlin("android") + id("com.rickbusarow.tangle") version "0.13.2" +} +``` + + + + + +```groovy +// settings.gradle + +pluginManagement { + repositories { + gradlePluginPortal() + } +} +``` + +```groovy +// root project build.gradle + +plugins { + // add Tangle and Anvil versions to the project's classpath + id 'com.squareup.anvil' version apply false + id 'com.rickbusarow.tangle' version "0.13.2" apply false +} +``` + +```groovy +// any Android module's build.gradle + +plugins { + id 'android-library' // or application, etc. + kotlin("android") + id 'com.rickbusarow.tangle' +} +``` + + + + + +## Explicitly defining behavior + +This automatic behavior may be overridden by using the `tangle { ... }` configuration block. + +These settings are prioritized ahead of the automatic configuration. Note that explicitly setting +a feature to `true` (enabled) will force the plugin to add dependencies and compiler extensions +which probably aren't needed. This functionality mostly exists for its ability to *disable* the +Tangle functionality. + + + + + +```kotlin +// any Android module's build.gradle.kts + +plugins { + id("android-library") // or application, etc. + kotlin("android") + id("com.rickbusarow.tangle") version "0.13.2" +} + +// optional +tangle { + // enables the Fragments feature regardless of the project's dependencies + fragmentsEnabled = true // default is null + + // disables the Work/WorkManager feature regardless of the project's dependencies + workEnabled = false // default is null + + viewModelOptions { + enabled = true // default is null + activitiesEnabled = true // default is null + composeEnabled = true // default is null + fragmentsEnabled = true // default is null + } +} +``` + + + + + +```groovy +// any Android module's build.gradle + +plugins { + id 'android-library' // or application, etc. + kotlin("android") + id 'com.rickbusarow.tangle' +} + +// optional +tangle { + // enables the Fragments feature regardless of the project's dependencies + fragmentsEnabled = true // default is null + + // disables the Work/WorkManager feature regardless of the project's dependencies + workEnabled = false // default is null + + viewModelOptions { + enabled true // default is null + activitiesEnabled true // default is null + composeEnabled true // default is null + fragmentsEnabled true // default is null + } +} +``` + + + + + diff --git a/website/sidebars.js b/website/sidebars.js index 22ecdc0b..3fa6b901 100644 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -16,6 +16,7 @@ module.exports = { Docs: [ "configuration", + "gradle-plugin", "extending-anvil", "member-injection", {