Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Automatically add features based upon androidx dependencies #353

Merged
merged 4 commits into from
Sep 30, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 12 additions & 14 deletions tangle-gradle-plugin/api/tangle-gradle-plugin.api
Original file line number Diff line number Diff line change
@@ -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 <init> (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
}

Expand All @@ -20,13 +18,13 @@ public class tangle/inject/gradle/TanglePlugin : org/gradle/api/plugins/BasePlug

public class tangle/inject/gradle/ViewModelOptions {
public fun <init> (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
}

Original file line number Diff line number Diff line change
@@ -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)
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -51,56 +59,18 @@ public abstract class TangleExtension @Inject constructor(
public fun viewModelOptions(action: Action<ViewModelOptions>) {
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 <reified T> ObjectFactory.property(initialValue: T): ReadWriteProperty<Any, T> =
object : ReadWriteProperty<Any, T> {
internal fun ObjectFactory.property(): ReadWriteProperty<Any, Boolean?> =
object : ReadWriteProperty<Any, Boolean?> {

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)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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()
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,15 @@ public abstract class BasePluginTest : HermitJUnit5() {
}

@Language("RegExp")
public fun BuildResult.deps(): List<String> = output
public fun BuildResult.tangleDeps(): List<String> = 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) {
Expand Down
Loading