foundry.gradle.GradleExt.kt Maven / Gradle / Ivy
/*
* Copyright (C) 2022 Slack Technologies, LLC
*
* 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
*
* https://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.
*/
@file:Suppress("TooManyFunctions")
package foundry.gradle
import com.android.builder.model.AndroidProject
import java.util.concurrent.atomic.AtomicBoolean
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
import kotlin.reflect.javaType
import kotlin.reflect.typeOf
import org.gradle.api.Action
import org.gradle.api.DomainObjectSet
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.internal.project.ProjectInternal
import org.gradle.api.model.ObjectFactory
import org.gradle.api.plugins.AppliedPlugin
import org.gradle.api.plugins.ExtensionContainer
import org.gradle.api.plugins.PluginManager
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.MapProperty
import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider
import org.gradle.api.provider.SetProperty
import org.gradle.api.reflect.TypeOf
import org.gradle.api.tasks.TaskContainer
import org.gradle.api.tasks.TaskProvider
import org.gradle.internal.service.ServiceRegistry
/*
* A set of utility functions that check and cache project information stored in extensions.
*/
private const val IS_ANDROID = "foundry.project.ext.isAndroid"
private const val IS_ANDROID_APPLICATION = "foundry.project.ext.isAndroidApplication"
private const val IS_ANDROID_LIBRARY = "foundry.project.ext.isAndroidLibrary"
private const val IS_ANDROID_TEST = "foundry.project.ext.isAndroidTest"
private const val IS_USING_KAPT = "foundry.project.ext.isUsingKapt"
private const val IS_USING_KSP = "foundry.project.ext.isUsingKsp"
private const val IS_USING_MOSHI_IR = "foundry.project.ext.isUsingMoshiIr"
private const val IS_KOTLIN = "foundry.project.ext.isKotlin"
private const val IS_KOTLIN_ANDROID = "foundry.project.ext.isKotlinAndroid"
private const val IS_KOTLIN_JVM = "foundry.project.ext.isKotlinJvm"
private const val IS_KOTLIN_MULTIPLATFORM = "foundry.project.ext.isKotlinMultiplatform"
private const val IS_JAVA_LIBRARY = "foundry.project.ext.isJavaLibrary"
private const val IS_JAVA = "foundry.project.ext.isJava"
internal val Project.isRootProject: Boolean
get() = rootProject === this
internal val Project.isJava: Boolean
get() {
return getOrComputeExt(IS_JAVA) { isJavaLibrary || project.pluginManager.hasPlugin("java") }
}
internal val Project.isJavaLibrary: Boolean
get() {
return getOrComputeExt(IS_JAVA_LIBRARY) { project.pluginManager.hasPlugin("java-library") }
}
internal val Project.isKotlin: Boolean
get() {
return getOrComputeExt(IS_KOTLIN) { isKotlinAndroid || isKotlinJvm }
}
internal val Project.isKotlinAndroid: Boolean
get() {
return getOrComputeExt(IS_KOTLIN_ANDROID) {
project.pluginManager.hasPlugin("org.jetbrains.kotlin.android")
}
}
internal val Project.isKotlinJvm: Boolean
get() {
return getOrComputeExt(IS_KOTLIN_JVM) {
project.pluginManager.hasPlugin("org.jetbrains.kotlin.jvm")
}
}
internal val Project.isKotlinMultiplatform: Boolean
get() {
return getOrComputeExt(IS_KOTLIN_MULTIPLATFORM) {
project.pluginManager.hasPlugin("org.jetbrains.kotlin.multiplatform")
}
}
internal val Project.isUsingKapt: Boolean
get() {
return getOrComputeExt(IS_USING_KAPT) {
project.pluginManager.hasPlugin("org.jetbrains.kotlin.kapt")
}
}
internal val Project.isUsingKsp: Boolean
get() {
return getOrComputeExt(IS_USING_KSP) {
project.pluginManager.hasPlugin("com.google.devtools.ksp")
}
}
internal val Project.isUsingMoshiGradle: Boolean
get() {
return getOrComputeExt(IS_USING_MOSHI_IR) {
project.pluginManager.hasPlugin("dev.zacsweers.moshix")
}
}
internal val Project.isAndroidApplication: Boolean
get() {
return getOrComputeExt(IS_ANDROID_APPLICATION) { plugins.hasPlugin("com.android.application") }
}
internal val Project.isAndroidLibrary: Boolean
get() {
return getOrComputeExt(IS_ANDROID_LIBRARY) { plugins.hasPlugin("com.android.library") }
}
internal val Project.isAndroidTest: Boolean
get() {
return getOrComputeExt(IS_ANDROID_TEST) { plugins.hasPlugin("com.android.test") }
}
internal val Project.isAndroid: Boolean
get() {
return getOrComputeExt(IS_ANDROID) { isAndroidApplication || isAndroidLibrary || isAndroidTest }
}
internal fun Project.getOrComputeExt(key: String, valueCalculator: () -> T): T {
@Suppress("UNCHECKED_CAST")
return (extensions.findByName(key) as? T)
?: run {
val value = valueCalculator()
extensions.add(key, value)
return value
}
}
/** Lifts an action into another action, reusing this action instance as an input to [into]. */
internal fun Action.liftIntoAction(into: R.(task: Action) -> Unit): Action {
return Action { into(this@liftIntoAction) }
}
/** Lifts an action into another action, reusing this action instance as an input to [into]. */
internal fun Action.liftIntoFunction(into: R.(task: Action) -> Unit): R.() -> Unit {
return { into(this@liftIntoFunction) }
}
internal inline fun TaskContainer.register(
name: String,
configuration: Action,
): TaskProvider = register(name, T::class.java, configuration)
internal inline fun Project.configure(action: Action) {
extensions.getByType().apply(action::execute)
}
internal inline fun ExtensionContainer.findByType(): T? {
// Gradle, Kotlin, and Java all have different notions of what a "type" is.
// I'm sorry
return findByType(TypeOf.typeOf(typeOf().javaType))
}
internal inline fun TaskContainer.configureEach(noinline action: T.() -> Unit) {
withType(T::class.java).configureEach(action)
}
internal inline fun ExtensionContainer.getByType(): T {
// Gradle, Kotlin, and Java all have different notions of what a "type" is.
// I'm sorry
return getByType(TypeOf.typeOf(typeOf().javaType))
}
@Suppress("SpreadOperator")
public fun TaskProvider.dependsOn(
vararg tasks: TaskProvider
): TaskProvider {
if (tasks.isEmpty().not()) {
configure { dependsOn(*tasks) }
}
return this
}
internal operator fun ExtensionContainer.set(key: String, value: Any) {
add(key, value)
}
internal fun PluginManager.onFirst(
pluginIds: Iterable,
body: AppliedPlugin.(id: String) -> Unit,
) {
once {
for (id in pluginIds) {
withPlugin(id) { onFirst { body(id) } }
}
}
}
internal inline fun once(body: OnceCheck.() -> Unit) {
contract { callsInPlace(body, InvocationKind.EXACTLY_ONCE) }
OnceCheck().body()
}
@JvmInline
internal value class OnceCheck(val once: AtomicBoolean = AtomicBoolean(false)) {
inline val isActive: Boolean
get() = once.compareAndSet(false, true)
inline fun onFirst(body: () -> Unit) {
if (isActive) {
body()
}
}
}
/**
* Returns true if this execution of Gradle is for an Android Studio Gradle Sync. We're considering
* both the no-task invocation of Gradle that AS uses to build its model, and the invocation of
* "generateXSources" for each project that follows it. (We may want to track these in the future
* too, but for now they're pretty noisy.)
*/
public val Project.isSyncing: Boolean
get() =
invokedFromIde &&
(findProperty(AndroidProject.PROPERTY_BUILD_MODEL_ONLY) == "true" ||
findProperty(AndroidProject.PROPERTY_GENERATE_SOURCES_ONLY) == "true")
// Note that we don't reference the AndroidProject property because this constant moved in AGP 7.2
public val Project.invokedFromIde: Boolean
get() = hasProperty("android.injected.invoked.from.ide")
internal inline fun ObjectFactory.newInstance(vararg parameters: Any): T {
return newInstance(T::class.java, *parameters)
}
internal inline fun ObjectFactory.property(): Property {
return property(T::class.java)
}
internal inline fun ObjectFactory.setProperty(): SetProperty {
return setProperty(E::class.java)
}
internal inline fun ObjectFactory.listProperty(): ListProperty {
return listProperty(E::class.java)
}
internal inline fun ObjectFactory.mapProperty():
MapProperty {
return mapProperty(K::class.java, V::class.java)
}
internal inline fun ObjectFactory.domainObjectSet(): DomainObjectSet {
return domainObjectSet(E::class.java)
}
internal inline fun Project.serviceOf(): T =
(this as ProjectInternal).services.get()
internal inline fun ServiceRegistry.get(): T = this[T::class.java]
@Suppress("UNCHECKED_CAST")
internal inline fun TaskContainer.registerOrConfigure(
taskName: String,
crossinline configureAction: T.() -> Unit,
): TaskProvider =
when (taskName) {
in names -> named(taskName) as TaskProvider
else -> register(taskName, T::class.java)
}.apply { configure { configureAction() } }
/** Returns a provider that is the inverse of this. */
internal fun Provider.not(): Provider = map { !it }
© 2015 - 2024 Weber Informatics LLC | Privacy Policy