Skip to content
This repository has been archived by the owner on Dec 5, 2023. It is now read-only.

Commit

Permalink
feat(kts): enhance codegen to add multiple dependencies
Browse files Browse the repository at this point in the history
This means that now you can quickly add multiple dependencies in the `dependencies {}` block using shorthand function, which eliminates the need for repetitive configuration function calls:
```kotlin
dependencies {
  implementationOf(
    "com.example:library:1.0.0",
    "foo.bar:common:0.1.0",
    "a.b:c:0.1.0" config {
      isTransitive = true
    },
  )
}
```
Instead of:
```kotlin
dependencies {
  implementation("com.example:library:1.0.0")
  implementation("foo.bar:common:0.1.0")
  implementation("a.b:c:0.1.0") {
    isTransitive = true
  }
}
```
  • Loading branch information
chachako committed Aug 29, 2023
1 parent da139c8 commit 7fa72a7
Show file tree
Hide file tree
Showing 9 changed files with 427 additions and 5 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Copyright 2023 the original author or authors.
*
* 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.meowool.cradle

import org.gradle.api.Action

/**
* Represents a dependency with configuration.
*
* @author chachako
*/
data class ConfigurableDependency(
/**
* The dependency notation to be configured by [configuration].
*/
val notation: Any,

/**
* The configuration action to be applied to the dependency [notation].
*/
val configuration: Action<Any>,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*
* Copyright 2023 the original author or authors.
*
* 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.meowool.cradle;

import org.gradle.api.artifacts.Dependency
import org.gradle.api.artifacts.ExternalModuleDependency
import org.gradle.api.provider.Provider
import org.gradle.api.provider.ProviderConvertible

/**
* Provides advanced DSL to configure dependencies.
*
* @author chachako
*/
interface ConfigurableDependencyScope {
/**
* Add [configuration] for this dependency.
*
* Example:
* ```
* "com.example:library:1.0" config {
* transitive = false
* }
* ```
*/
infix fun String.config(configuration: ExternalModuleDependency.() -> Unit) = ConfigurableDependency(this) {
configuration.invoke(this as ExternalModuleDependency)
}

/**
* Add [configuration] for this dependency.
*
* ```
* provider { "com.example:library:1.0" } config {
* transitive = false
* }
* ```
*/
infix fun Provider<*>.config(configuration: ExternalModuleDependency.() -> Unit) = ConfigurableDependency(this) {
configuration.invoke(this as ExternalModuleDependency)
}

/**
* Add [configuration] for this dependency.
*
* ```
* dependency config {
* transitive = false
* }
* ```
*/
infix fun ProviderConvertible<*>.config(configuration: ExternalModuleDependency.() -> Unit) = ConfigurableDependency(this) {
configuration.invoke(this as ExternalModuleDependency)
}

/**
* Add [configuration] for this dependency.
*
* ```
* project(":core") config {
* transitive = false
* }
* ```
*/
infix fun <T : Dependency> T.config(configuration: T.() -> Unit) = ConfigurableDependency(this) {
@Suppress("UNCHECKED_CAST")
configuration.invoke(this as T)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package org.gradle.kotlin.dsl

import com.meowool.cradle.ConfigurableDependencyScope
import org.gradle.api.Action
import org.gradle.api.Incubating
import org.gradle.api.NamedDomainObjectProvider
Expand All @@ -38,7 +39,7 @@ import org.gradle.kotlin.dsl.support.delegates.DependencyHandlerDelegate
open class DependencyHandlerScope
private constructor(
val dependencies: DependencyHandler
) : DependencyHandlerDelegate() {
) : DependencyHandlerDelegate(), ConfigurableDependencyScope {

companion object {
fun of(dependencies: DependencyHandler): DependencyHandlerScope =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,12 @@

package org.gradle.kotlin.dsl.accessors

import kotlinx.metadata.Flag
import kotlinx.metadata.KmType
import kotlinx.metadata.KmVariance
import kotlinx.metadata.flagsOf
import kotlinx.metadata.jvm.JvmMethodSignature
import org.gradle.api.artifacts.Dependency
import org.gradle.api.reflect.TypeOf
import org.gradle.internal.deprecation.ConfigurationDeprecationType
import org.gradle.internal.hash.Hashing.hashString
Expand Down Expand Up @@ -47,8 +50,10 @@ import org.gradle.kotlin.dsl.support.bytecode.publicStaticSyntheticMethod
import org.gradle.kotlin.dsl.support.bytecode.newPropertyOf
import org.gradle.kotlin.dsl.support.bytecode.newTypeParameterOf
import org.gradle.kotlin.dsl.support.bytecode.newValueParameterOf
import org.gradle.kotlin.dsl.support.bytecode.newVarargValueParameterOf
import org.gradle.kotlin.dsl.support.bytecode.nullable
import org.gradle.kotlin.dsl.support.bytecode.providerOfStar
import org.gradle.kotlin.dsl.support.bytecode.publicStaticVarargMethod
import org.gradle.kotlin.dsl.support.uppercaseFirstChar
import org.jetbrains.org.objectweb.asm.MethodVisitor

Expand Down Expand Up @@ -115,6 +120,79 @@ fun fragmentsForConfiguration(accessor: Accessor.ForConfiguration): Fragments =
"(Lorg/gradle/api/artifacts/dsl/DependencyHandler;Ljava/lang/Object;)Lorg/gradle/api/artifacts/Dependency;"
)
),
// @cradle extension
AccessorFragment(
source = name.run {
"""
/**
* Adds all dependencies to the '$original' configuration.
*
* ## Introduction
*
* This shorthand function allows you to add multiple dependencies to the same
* configuration at once, without the need to repeatedly call the same configuration
* multiple times.
*
* Do this:
*
* ```
* ${original}Of(
* "com.example:library:1.0.0",
* "foo.bar:common:0.1.0",
* "a.b:c:0.1.0" config {
* isTransitive = true
* },
* )
* ```
*
* Instead of:
*
* ```
* $original("com.example:library:1.0.0")
* $original("foo.bar:common:0.1.0")
* $original("a.b:c:0.1.0") {
* isTransitive = true
* }
* ```
*
* @param dependencyNotations notation list for all dependencies to be added.
*
* @see DependencyHandlerScope.config
* @see DependencyHandler.add
*
* @author chachako
*/$deprecationBlock
fun DependencyHandler.`${kotlinIdentifier}Of`(vararg dependencyNotations: Any): List<Dependency?> =
addAllDependencyNotationsTo(this, "$stringLiteral", dependencyNotations)
"""
},
bytecode = {
publicStaticMaybeDeprecatedVarargMethod(signature, config) {
ALOAD(0)
LDC(name.original)
ALOAD(1)
invokeRuntime(
"addAllDependencyNotationsTo",
"(L${GradleTypeName.dependencyHandler};Ljava/lang/String;[Ljava/lang/Object;)Ljava/util/List;"
)
ARETURN()
}
},
metadata = {
kmPackage.functions += newFunctionOf(
flags = functionFlags,
receiverType = GradleType.dependencyHandler,
returnType = KotlinType.list<Dependency>(flagsOf(Flag.Type.IS_NULLABLE)),
name = "${propertyName}Of",
valueParameters = listOf(newVarargValueParameterOf("dependencyNotations", KotlinType.any)),
signature = signature
)
},
signature = JvmMethodSignature(
"${propertyName}Of",
"(Lorg/gradle/api/artifacts/dsl/DependencyHandler;[Ljava/lang/Object;)Ljava/util/List;"
)
),
AccessorFragment(
source = name.run {
"""
Expand Down Expand Up @@ -966,3 +1044,25 @@ fun BytecodeFragmentScope.publicStaticMaybeDeprecatedMethod(
publicStaticMethod(jvmMethodSignature, signature, exceptions, false, {}, methodBody)
}
}


private
fun BytecodeFragmentScope.publicStaticMaybeDeprecatedVarargMethod(
jvmMethodSignature: JvmMethodSignature,
config: ConfigurationEntry<AccessorNameSpec>,
signature: String? = null,
exceptions: Array<String>? = null,
methodBody: MethodVisitor.() -> Unit
) {
if (config.hasDeclarationDeprecations()) {
publicStaticVarargMethod(
jvmMethodSignature, signature, exceptions, true,
{
kotlinDeprecation(config.getDeclarationDeprecationMessage())
},
methodBody
)
} else {
publicStaticVarargMethod(jvmMethodSignature, signature, exceptions, false, {}, methodBody)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,12 @@

package org.gradle.kotlin.dsl.accessors

import kotlinx.metadata.Flags
import kotlinx.metadata.KmType
import kotlinx.metadata.KmVariance
import org.gradle.kotlin.dsl.support.bytecode.newClassTypeOf
import org.gradle.kotlin.dsl.support.bytecode.newTypeParameterTypeOf
import org.gradle.kotlin.dsl.support.bytecode.genericTypeOf


internal
Expand All @@ -31,4 +34,32 @@ object KotlinType {
val any: KmType = newClassTypeOf("kotlin/Any")

val typeParameter: KmType = newTypeParameterTypeOf(0)

private val array: KmType = newClassTypeOf("kotlin/Array")

private val list: KmType = newClassTypeOf("kotlin/collections/List")

inline fun <reified T> array(
argumentFlags: Flags = 0,
argumentVariance: KmVariance = KmVariance.INVARIANT
) = genericTypeOf(
type = array,
argument = classOf<T>(),
variance = argumentVariance
).apply { flags = argumentFlags }

inline fun <reified T> list(
argumentFlags: Flags = 0,
argumentVariance: KmVariance = KmVariance.INVARIANT
) = genericTypeOf(
type = list,
argument = classOf<T>(),
variance = argumentVariance
).apply { flags = argumentFlags }

fun vararg(type: KmType, typeFlags: Flags = 0) = genericTypeOf(
type = array,
argument = type,
variance = KmVariance.OUT
).apply { flags = typeFlags }
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package org.gradle.kotlin.dsl.accessors.runtime

import com.meowool.cradle.ConfigurableDependency
import org.gradle.api.Action
import org.gradle.api.Project
import org.gradle.api.artifacts.Dependency
Expand All @@ -26,6 +27,7 @@ import org.gradle.api.provider.Provider
import org.gradle.api.provider.ProviderConvertible
import org.gradle.internal.Factory
import org.gradle.internal.deprecation.DeprecationLogger
import org.gradle.kotlin.dsl.*

import org.gradle.kotlin.dsl.support.mapOfNonNullValuesOf
import org.gradle.kotlin.dsl.support.uncheckedCast
Expand All @@ -52,6 +54,38 @@ fun conventionOf(target: Any): org.gradle.api.plugins.Convention = when (target)
else -> throw IllegalStateException("Object `$target` doesn't support conventions!")
}

// @cradle extension
fun addAllDependencyNotationsTo(
dependencies: DependencyHandler,
configuration: String,
dependencyNotations: Array<out Any>
): List<Dependency?> = dependencyNotations.map {
when (it) {
is ConfigurableDependency -> when (it.notation) {
is String -> addDependencyTo(dependencies, configuration, it.notation) {
it.configuration.execute(this)
}
is org.gradle.api.artifacts.ModuleDependency -> dependencies.add(configuration, it.notation) {
it.configuration.execute(this)
}
is Provider<*> -> {
addConfiguredDependencyTo(dependencies, configuration, it.notation) {
it.configuration.execute(this)
}
null
}
is ProviderConvertible<*> -> {
addConfiguredDependencyTo(dependencies, configuration, it.notation) {
it.configuration.execute(this)
}
null
}
else -> throw IllegalArgumentException("Unsupported dependency notation: $it")
}
else -> dependencies.add(configuration, it)
}
}


fun <T : Dependency> addDependencyTo(
dependencies: DependencyHandler,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,26 @@ fun ClassVisitor.publicStaticMethod(
}


internal
fun ClassVisitor.publicStaticVarargMethod(
name: String,
desc: String,
signature: String? = null,
exceptions: Array<String>? = null,
deprecated: Boolean = false,
methodBody: MethodVisitor.() -> Unit,
annotations: MethodVisitor.() -> Unit
) {
method(
Opcodes.ACC_PUBLIC
.plus(Opcodes.ACC_STATIC)
.plus(Opcodes.ACC_VARARGS)
.let { if (deprecated) it.plus(Opcodes.ACC_DEPRECATED) else it },
name, desc, signature, exceptions, annotations, methodBody
)
}


internal
fun ClassVisitor.publicMethod(
name: String,
Expand Down
Loading

0 comments on commit 7fa72a7

Please sign in to comment.