Skip to content

Commit

Permalink
Add mutations based on the AGP model
Browse files Browse the repository at this point in the history
  • Loading branch information
h0tk3y committed Sep 30, 2024
1 parent 3c612c3 commit a213df9
Show file tree
Hide file tree
Showing 4 changed files with 199 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,14 @@ object MutationUtils {
registerMutationDefinition(SetNamespaceMutation)
registerMutationDefinition(addLibraryDependencyMutation)
registerMutationDefinition(addApplicationDependencyMutation)
registerMutationDefinition(addTestingDependencyMutation)

// AGP
registerMutationDefinition(agpAddDependency)
registerMutationDefinition(agpAddTestDependency)
registerMutationDefinition(agpAddAndroidTestDependency)
registerMutationDefinition(enableCompose)
registerMutationDefinition(addBuildConfigField)
registerMutationDefinition(setAgpNamespaceMutation)

// Common
registerMutationDefinition(addCommonLibraryDependencyMutation)
Expand Down
171 changes: 171 additions & 0 deletions mutations-demo/src/main/kotlin/AgpMutationDefinitions.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
package org.gradle.client.demo.mutations

import org.gradle.declarative.dsl.schema.AnalysisSchema
import org.gradle.internal.declarativedsl.dom.mutation.*
import org.gradle.internal.declarativedsl.dom.mutation.ModelMutation.SetPropertyValue
import org.gradle.internal.declarativedsl.schemaUtils.propertyNamed
import org.gradle.internal.declarativedsl.schemaUtils.singleFunctionNamed

interface AgpMutationDefinition : MutationDefinition {
override fun isCompatibleWithSchema(projectAnalysisSchema: AnalysisSchema): Boolean =
projectAnalysisSchema.hasAgp()
}

val agpAddDependency = AddDependencyMutation(
"org.gradle.client.demo.mutations.addDependency.agp.implementation",
AnalysisSchema::hasAgp,
{ ScopeLocation.fromTopLevel().inObjectsOfType(agpAndroidLibrary) },
{ agpAndroidLibrary.singleFunctionNamed("dependenciesDcl") }
)

val agpAddTestDependency = run {
val mutation = AddDependencyMutation(
"org.gradle.client.demo.mutations.addDependency.agp.testImplementation",
AnalysisSchema::hasAgp,
{ ScopeLocation.fromTopLevel().inObjectsOfType(agpAndroidLibrary) },
{ agpAndroidLibrary.singleFunctionNamed("dependenciesDcl") },
{ notation -> elementFromString("testImplementation(\"${notation.value}\")") }
)

object : MutationDefinition by mutation {
override val name: String
get() = "Add a test dependency"
}
}

val agpAddAndroidTestDependency = run {
val mutation = AddDependencyMutation(
"org.gradle.client.demo.mutations.addDependency.agp.androidTestImplementation",
AnalysisSchema::hasAgp,
{ ScopeLocation.fromTopLevel().inObjectsOfType(agpAndroidLibrary) },
{ agpAndroidLibrary.singleFunctionNamed("dependenciesDcl") },
{ notation -> elementFromString("androidTestImplementation(\"${notation.value}\")") }
)

object : MutationDefinition by mutation {
override val name: String
get() = "Add a device test dependency"
}
}

val enableCompose = run {
val addComposeDependency = AddDependencyMutation(
"",
AnalysisSchema::hasAgp,
{ ScopeLocation.fromTopLevel().inObjectsOfType(agpAndroidLibrary) },
{ agpAndroidLibrary.singleFunctionNamed("dependenciesDcl") },
{ _ -> elementFromString("implementation(\"androidx.compose.material3:material3:1.3.0\")") }
)

object : AgpMutationDefinition {
override val name: String
get() = "Enable Compose"

override val description: String
get() = "Enable Jetpack Compose in this project"

override val id: String
get() = "org.gradle.client.demo.mutations.agp.compose"

override val parameters: List<MutationParameter<*>>
get() = emptyList()

override fun defineModelMutationSequence(projectAnalysisSchema: AnalysisSchema) = with(projectAnalysisSchema) {
addComposeDependency.defineModelMutationSequence(projectAnalysisSchema) + listOf(
ModelMutationRequest(
ScopeLocation.fromTopLevel().inObjectsOfType(agpAndroidLibrary),
ModelMutation.AddConfiguringBlockIfAbsent(
agpAndroidLibrary.singleFunctionNamed("buildFeatures")
)
),
ModelMutationRequest(
ScopeLocation.fromTopLevel().inObjectsOfType(agpAndroidLibrary)
.inObjectsConfiguredBy(agpAndroidLibrary.singleFunctionNamed("buildFeatures")),
SetPropertyValue(
buildFeatures.propertyNamed("compose"),
NewValueNodeProvider.Constant(valueFromString("true")!!)
)
)
)
}
}
}

val addBuildConfigField = object : AgpMutationDefinition {
override val id: String
get() = "org.gradle.client.demo.mutations.agp.buildConfig.addField"
override val description: String
get() = "Add a field to the build config"
override val name: String
get() = "Add a build config field"

private val typeArg =
MutationParameter("Field type", "build config field type", MutationParameterKind.StringParameter)
private val keyArg = MutationParameter("Field key", "field key", MutationParameterKind.StringParameter)
private val valueArg = MutationParameter("Field value", "field value", MutationParameterKind.StringParameter)

override val parameters: List<MutationParameter<*>>
get() {
return listOf(
typeArg,
keyArg,
valueArg
)
}

override fun defineModelMutationSequence(projectAnalysisSchema: AnalysisSchema) = with(projectAnalysisSchema) {
listOf(
ModelMutationRequest(
ScopeLocation.fromTopLevel().inObjectsOfType(agpAndroidLibrary),
ModelMutation.AddConfiguringBlockIfAbsent(
agpAndroidLibrary.singleFunctionNamed("buildFeatures")
)
),
ModelMutationRequest(
ScopeLocation.fromTopLevel().inObjectsOfType(agpAndroidLibrary)
.inObjectsConfiguredBy(agpAndroidLibrary.singleFunctionNamed("buildFeatures")),
SetPropertyValue(
buildFeatures.propertyNamed("buildConfig"),
NewValueNodeProvider.Constant(valueFromString("true")!!)
)
),
ModelMutationRequest(
ScopeLocation.fromTopLevel().inObjectsOfType(agpAndroidLibrary)
.inObjectsConfiguredBy(agpAndroidLibrary.singleFunctionNamed("defaultConfig")),
ModelMutation.AddNewElement(NewElementNodeProvider.ArgumentBased { args ->
val type = args[typeArg]
val key = args[keyArg]
val value = args[valueArg]
elementFromString("buildConfigField(\"$type\", \"$key\", \"$value\")")!!
})
)
)
}
}

val setAgpNamespaceMutation = object : AgpMutationDefinition {
override val id: String = "org.gradle.client.demo.mutations.agp.namespace"
override val name: String = "Set the namespace"
override val description: String = "Update the namespace in the project"

private val newNamespaceParam =
MutationParameter("New namespace", "New value for the namespace", MutationParameterKind.StringParameter)

override val parameters: List<MutationParameter<*>>
get() = listOf(newNamespaceParam)

override fun defineModelMutationSequence(projectAnalysisSchema: AnalysisSchema): List<ModelMutationRequest> =
with(projectAnalysisSchema) {
listOf(
ModelMutationRequest(
ScopeLocation.inAnyScope().inObjectsOfType(agpAndroidLibrary),
SetPropertyValue(
agpAndroidLibrary.propertyNamed("namespace"),
NewValueNodeProvider.ArgumentBased { args ->
valueFromString("\"" + args[newNamespaceParam] + "\"")!!
}
),
)
)
}
}
16 changes: 16 additions & 0 deletions mutations-demo/src/main/kotlin/AgpSchemaAccessors.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package org.gradle.client.demo.mutations

import org.gradle.declarative.dsl.schema.AnalysisSchema
import org.gradle.declarative.dsl.schema.DataClass

fun AnalysisSchema.hasAgp(): Boolean =
dataClassesByFqName.keys.any { it.qualifiedName == "com.android.build.gradle.LibraryExtensionInternal" }

val AnalysisSchema.agpAndroidLibrary: DataClass
get() = typeByFqn("com.android.build.gradle.LibraryExtensionInternal")

val AnalysisSchema.dependenciesExtension: DataClass
get() = typeByFqn("com.android.build.gradle.internal.DependenciesExtension")

val AnalysisSchema.buildFeatures: DataClass
get() = typeByFqn("com.android.build.api.dsl.BuildFeatures")
6 changes: 4 additions & 2 deletions mutations-demo/src/main/kotlin/CommonSchemaAccessors.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package org.gradle.client.demo.mutations

import org.gradle.declarative.dsl.schema.AnalysisSchema
import org.gradle.declarative.dsl.schema.DataClass
import org.gradle.internal.declarativedsl.dom.DeclarativeDocument
import org.gradle.internal.declarativedsl.dom.mutation.*
import org.gradle.internal.declarativedsl.schemaUtils.propertyNamed

Expand Down Expand Up @@ -30,11 +31,12 @@ class AddDependencyMutation(
val isCompatible: AnalysisSchema.() -> Boolean,
private val dependenciesOwnerScope: AnalysisSchema.() -> ScopeLocation,
private val dependenciesConfiguringFunction: AnalysisSchema.() -> TypedMember.TypedFunction,
private val element: (Lazy<String>) -> DeclarativeDocument.DocumentNode.ElementNode? = { elementFromString("implementation(\"${it.value}\")") },
) : CommonPrototypeMutationDefinition {
override val name: String = "Add a dependency"
override val description: String = "Add a dependency to the dependencies block"

override fun isCompatibleWithSchema(projectAnalysisSchema: AnalysisSchema): Boolean =
override fun isCompatibleWithSchema(projectAnalysisSchema: AnalysisSchema): Boolean =
isCompatible(projectAnalysisSchema)

val dependencyCoordinatesParam =
Expand All @@ -60,7 +62,7 @@ class AddDependencyMutation(
scopeForDependenciesBlock.inObjectsConfiguredBy(dependenciesFunction),
ModelMutation.AddNewElement(
NewElementNodeProvider.ArgumentBased { args ->
elementFromString("implementation(\"" + args[dependencyCoordinatesParam] + "\")")!!
element(lazy { args[dependencyCoordinatesParam] })!!
}
)
)
Expand Down

0 comments on commit a213df9

Please sign in to comment.