diff --git a/BUILD b/BUILD
index 6a909033..7974b625 100644
--- a/BUILD
+++ b/BUILD
@@ -169,3 +169,7 @@ recipe_test(
recipe_test(
name = "appendToScopedArtifacts",
)
+
+recipe_test(
+ name = "disableTests",
+)
diff --git a/recipes/disableTests/README.md b/recipes/disableTests/README.md
new file mode 100644
index 00000000..d4b672b4
--- /dev/null
+++ b/recipes/disableTests/README.md
@@ -0,0 +1,42 @@
+# Disable tests using the variant API
+
+This sample shows how to disable both unit tests and Android tests on a project using the variant API. These properties
+are set on sub-types of [`VariantBuilder`](https://developer.android.com/reference/tools/gradle-api/current/com/android/build/api/variant/VariantBuilder), for both unit tests and Android tests.
+
+This recipe contains the following directories:
+
+| Module | Content |
+|----------------------------|-------------------------------------------------------------|
+| [build-logic](build-logic) | Contains the Project plugin that is the core of the recipe. |
+| [app](app) | An Android application that has the plugin applied. |
+
+The [build-logic](build-logic) sub-project contains the [`CustomPlugin`](build-logic/plugins/src/main/kotlin/CustomPlugin.kt) and [`CheckTestStatusClass`](build-logic/plugins/src/main/kotlin/CheckTestStatusClass.kt).
+
+[`CustomPlugin`](build-logic/plugins/src/main/kotlin/CustomPlugin.kt) uses the [VariantBuilder] object to disable to the tests on different variants. These properties
+are then passed into the [`CheckTestStatusTask`](build-logic/plugins/src/main/kotlin/CheckTestStatusTask.kt) to validate
+they are turned off for each variant. Below is an example usage of disabling the Android test type:
+
+```
+androidComponents.beforeVariants(androidComponents.selector().withBuildType("debug")) { variantBuilder ->
+ (variantBuilder as? HasDeviceTestsBuilder)?.deviceTests?.get(DeviceTestBuilder.ANDROID_TEST_TYPE)?.enable = false
+}
+```
+
+The variant builder must be a sub-type of `HasDeviceTestsBuilder` here to access the property. In this example, we
+query the `deviceTests` property for the Android tests, and disable them for the debug build type.
+
+For host tests, the sub-type `HasHostTestsBuilder` is required, and we query the host test types for the unit test
+entry. This is disabled using the following snippet:
+
+```
+androidComponents.beforeVariants(androidComponents.selector().withBuildType("release")) { variantBuilder ->
+ (variantBuilder as? HasHostTestsBuilder)?.hostTests?.get(HostTestBuilder.UNIT_TEST_TYPE)?.enable = false
+}
+```
+
+This will disable the unit tests for the release build type.
+
+## To Run
+To execute example you need to enter command:
+
+`./gradlew :app:checkDebugTestStatus` and `./gradlew :app:checkReleaseTestStatus`
diff --git a/recipes/disableTests/app/build.gradle.kts b/recipes/disableTests/app/build.gradle.kts
new file mode 100644
index 00000000..7ab0a1ab
--- /dev/null
+++ b/recipes/disableTests/app/build.gradle.kts
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * 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.
+ */
+
+plugins {
+ alias(libs.plugins.android.application)
+ alias(libs.plugins.kotlin.android)
+ id("android.recipes.disable_tests")
+}
+
+android {
+ namespace = "com.example.android.recipes.disable_tests"
+ compileSdk = $COMPILE_SDK
+ defaultConfig {
+ minSdk = $MINIMUM_SDK
+ targetSdk = $COMPILE_SDK
+ }
+}
+
+java {
+ toolchain {
+ languageVersion.set(JavaLanguageVersion.of(17))
+ }
+}
\ No newline at end of file
diff --git a/recipes/disableTests/app/src/main/AndroidManifest.xml b/recipes/disableTests/app/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..8b7fed3d
--- /dev/null
+++ b/recipes/disableTests/app/src/main/AndroidManifest.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/recipes/disableTests/build-logic/gradle.properties b/recipes/disableTests/build-logic/gradle.properties
new file mode 100644
index 00000000..3dcf88f0
--- /dev/null
+++ b/recipes/disableTests/build-logic/gradle.properties
@@ -0,0 +1,2 @@
+# Gradle properties are not passed to included builds https://github.com/gradle/gradle/issues/2534
+org.gradle.parallel=true
diff --git a/recipes/disableTests/build-logic/gradle/libs.versions.toml b/recipes/disableTests/build-logic/gradle/libs.versions.toml
new file mode 100644
index 00000000..d009d769
--- /dev/null
+++ b/recipes/disableTests/build-logic/gradle/libs.versions.toml
@@ -0,0 +1,9 @@
+[versions]
+androidGradlePlugin = $AGP_VERSION
+kotlin = $KOTLIN_VERSION
+
+[libraries]
+android-gradlePlugin-api = { group = "com.android.tools.build", name = "gradle-api", version.ref = "androidGradlePlugin" }
+
+[plugins]
+kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
\ No newline at end of file
diff --git a/recipes/disableTests/build-logic/plugins/build.gradle.kts b/recipes/disableTests/build-logic/plugins/build.gradle.kts
new file mode 100644
index 00000000..864c9920
--- /dev/null
+++ b/recipes/disableTests/build-logic/plugins/build.gradle.kts
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * 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.
+ */
+
+plugins {
+ `java-gradle-plugin`
+ alias(libs.plugins.kotlin.jvm)
+}
+
+java {
+ toolchain {
+ languageVersion.set(JavaLanguageVersion.of(17))
+ }
+}
+
+dependencies {
+ compileOnly(libs.android.gradlePlugin.api)
+ implementation(gradleKotlinDsl())
+}
+
+gradlePlugin {
+ plugins {
+ create("customPlugin") {
+ id = "android.recipes.disable_tests"
+ implementationClass = "CustomPlugin"
+ }
+ }
+}
diff --git a/recipes/disableTests/build-logic/plugins/src/main/kotlin/CheckTestStatusTask.kt b/recipes/disableTests/build-logic/plugins/src/main/kotlin/CheckTestStatusTask.kt
new file mode 100644
index 00000000..d35ad2dd
--- /dev/null
+++ b/recipes/disableTests/build-logic/plugins/src/main/kotlin/CheckTestStatusTask.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * 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.
+ */
+
+import org.gradle.api.DefaultTask
+import org.gradle.api.file.DirectoryProperty
+import org.gradle.api.tasks.OutputDirectory
+import org.gradle.api.provider.Property
+import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.TaskAction
+import java.lang.RuntimeException
+
+abstract class CheckTestStatusTask : DefaultTask() {
+
+ // In order for the task to be up-to-date when the inputs have not changed,
+ // the task must declare an output, even if it's not used. Tasks with no
+ // output are always run regardless of whether the inputs changed
+ @get:OutputDirectory
+ abstract val output: DirectoryProperty
+
+ @get:Input
+ abstract val unitTestEnabled: Property
+
+ @get:Input
+ abstract val androidTestEnabled: Property
+
+ @get:Input
+ abstract val variantName: Property
+
+ @TaskAction
+ fun taskAction() {
+ if (variantName.get() == "debug" && (!unitTestEnabled.get() || androidTestEnabled.get())) {
+ throw RuntimeException("The debug variant should have Android tests disabled.")
+ }
+ // The Android test is disabled by default for the release variant, so we can validate that
+ // both unit tests and Android tests are disabled
+ if (variantName.get() == "release" && (androidTestEnabled.get() || unitTestEnabled.get())) {
+ throw RuntimeException("The release variant should have unit tests disabled.")
+ }
+ }
+}
diff --git a/recipes/disableTests/build-logic/plugins/src/main/kotlin/CustomPlugin.kt b/recipes/disableTests/build-logic/plugins/src/main/kotlin/CustomPlugin.kt
new file mode 100644
index 00000000..78b6a680
--- /dev/null
+++ b/recipes/disableTests/build-logic/plugins/src/main/kotlin/CustomPlugin.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * 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.
+ */
+
+import com.android.build.api.variant.AndroidComponentsExtension
+import com.android.build.api.variant.DeviceTestBuilder
+import com.android.build.api.variant.HasDeviceTests
+import com.android.build.api.variant.HasDeviceTestsBuilder
+import com.android.build.api.variant.HasHostTests
+import com.android.build.api.variant.HasHostTestsBuilder
+import com.android.build.api.variant.HostTestBuilder
+import com.android.build.gradle.BasePlugin
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+import org.gradle.kotlin.dsl.register
+import org.gradle.configurationcache.extensions.capitalized
+
+/**
+ * This custom plugin will register a callback that is applied to all variants for all Android plugins.
+ */
+class CustomPlugin : Plugin {
+ override fun apply(project: Project) {
+
+ // Registers a callback on the Android base plugin.
+ // This allows the CustomPlugin to work whether it's applied before or after the base plugin.
+ project.plugins.withType(BasePlugin::class.java) {
+
+ val androidComponents = project.extensions.getByType(AndroidComponentsExtension::class.java)
+
+ // Disable tests
+ // We will disable the Android tests for the debug variant, and disable unit tests for release
+ androidComponents.beforeVariants(androidComponents.selector().withBuildType("debug")) { variantBuilder ->
+ // Query the device tests by name to find the Android tests, and disable them
+ // This is not available on all sub types of Variant- only those implementing HasDeviceTestsBuilder
+ // supports device tests
+ (variantBuilder as? HasDeviceTestsBuilder)?.deviceTests?.get(
+ DeviceTestBuilder.ANDROID_TEST_TYPE
+ )?.enable = false
+ }
+ androidComponents.beforeVariants(androidComponents.selector().withBuildType("release")) { variantBuilder ->
+ // There are multiple host test types, and they can all be disabled, or they can be queried and
+ // disabled individually by name like below for unit tests
+ // This is not available on all sub types of Variant- only those implementing HasHostTestsBuilder
+ // supports host tests
+ (variantBuilder as? HasHostTestsBuilder)?.hostTests?.get(HostTestBuilder.UNIT_TEST_TYPE)?.enable = false
+ }
+
+ // -- Verification --
+ // the following is just to validate the recipe and is not actually part of the recipe itself
+ androidComponents.onVariants { variant ->
+ val taskName = "check${variant.name.capitalized()}TestStatus"
+ project.tasks.register(taskName) {
+ // If the unit tests are disabled, the unit test type will not be present in the host tests
+ unitTestEnabled.set(
+ (variant as? HasHostTests)?.hostTests?.get(HostTestBuilder.UNIT_TEST_TYPE) != null
+ )
+ // If the Android tests are disabled, the Android test type will not be present in the device tests
+ androidTestEnabled.set(
+ (variant as? HasDeviceTests)?.deviceTests?.get(DeviceTestBuilder.ANDROID_TEST_TYPE) != null
+ )
+ variantName.set(variant.name)
+ output.set(project.layout.buildDirectory.dir("intermediates/$taskName"))
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/recipes/disableTests/build-logic/settings.gradle.kts b/recipes/disableTests/build-logic/settings.gradle.kts
new file mode 100644
index 00000000..e2e5e9e5
--- /dev/null
+++ b/recipes/disableTests/build-logic/settings.gradle.kts
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * 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.
+ */
+
+rootProject.name = "build-logic"
+
+pluginManagement {
+ repositories {
+ $AGP_REPOSITORY
+ $PLUGIN_REPOSITORIES
+ }
+}
+
+dependencyResolutionManagement {
+ repositories {
+ $AGP_REPOSITORY
+ $DEPENDENCY_REPOSITORIES
+ }
+}
+
+include(":plugins")
diff --git a/recipes/disableTests/build.gradle.kts b/recipes/disableTests/build.gradle.kts
new file mode 100644
index 00000000..68e631f2
--- /dev/null
+++ b/recipes/disableTests/build.gradle.kts
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * 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.
+ */
+
+plugins {
+ alias(libs.plugins.android.application) apply false
+ alias(libs.plugins.kotlin.android) apply false
+}
diff --git a/recipes/disableTests/gradle.properties b/recipes/disableTests/gradle.properties
new file mode 100644
index 00000000..55cce922
--- /dev/null
+++ b/recipes/disableTests/gradle.properties
@@ -0,0 +1,23 @@
+# Project-wide Gradle settings.
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+org.gradle.parallel=true
+# AndroidX package structure to make it clearer which packages are bundled with the
+# Android operating system, and which are packaged with your app's APK
+# https://developer.android.com/topic/libraries/support-library/androidx-rn
+android.useAndroidX=true
+# Kotlin code style for this project: "official" or "obsolete":
+kotlin.code.style=official
+# Enables namespacing of each library's R class so that its R class includes only the
+# resources declared in the library itself and none from the library's dependencies,
+# thereby reducing the size of the R class for that library
+android.nonTransitiveRClass=true
diff --git a/recipes/disableTests/gradle/libs.versions.toml b/recipes/disableTests/gradle/libs.versions.toml
new file mode 100644
index 00000000..e2285f69
--- /dev/null
+++ b/recipes/disableTests/gradle/libs.versions.toml
@@ -0,0 +1,7 @@
+[versions]
+androidGradlePlugin = $AGP_VERSION
+kotlin = $KOTLIN_VERSION
+
+[plugins]
+android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" }
+kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
diff --git a/recipes/disableTests/recipe_metadata.toml b/recipes/disableTests/recipe_metadata.toml
new file mode 100644
index 00000000..3849ef4e
--- /dev/null
+++ b/recipes/disableTests/recipe_metadata.toml
@@ -0,0 +1,37 @@
+# optional (if present and non-blank) name to use in the index
+indexName = ""
+# optional (if present and non-blank) folder name to use when converting recipe in RELEASE mode
+destinationFolder = ""
+
+description ="""
+ This sample shows how to use VariantBuilder properties to disable tests in a project
+ """
+
+[agpVersion]
+min = "8.7.0-alpha08"
+
+# Relevant Gradle tasks to run per recipe
+[gradleTasks]
+tasks = [
+ ":app:checkDebugTestStatus",
+ ":app:checkReleaseTestStatus"
+]
+
+# All the relevant metadata fields to create an index based on language/API/etc
+[indexMetadata]
+index = [
+ "Call chains/androidComponents.beforeVariants {}",
+ "Call chains/androidComponents.onVariants {}",
+ "APIs/AndroidComponentsExtension.beforeVariants()",
+ "APIs/AndroidComponentsExtension.onVariants()",
+ "Call chains/HasHostTestsBuilder.hostTests.get().enable",
+ "Call chains/HasDeviceTestsBuilder.deviceTests.get().enable",
+ "Call chains/HasHostTests.hostTests.get()",
+ "Call chains/HasDeviceTests.deviceTests.get()",
+ "APIs/HasDeviceTestsBuilder.deviceTests",
+ "APIs/HasDeviceTests.deviceTests",
+ "APIs/HasHostTestsBuilder.hostTests",
+ "APIs/HasHostTests.hostTests",
+ "HostTestBuilder.UNIT_TEST_TYPE",
+ "DeviceTestBuilder.ANDROID_TEST_TYPE"
+]
diff --git a/recipes/disableTests/settings.gradle.kts b/recipes/disableTests/settings.gradle.kts
new file mode 100644
index 00000000..608d62da
--- /dev/null
+++ b/recipes/disableTests/settings.gradle.kts
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * 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.
+ */
+
+rootProject.name = "disableTests"
+
+pluginManagement {
+ includeBuild("build-logic")
+ repositories {
+ $AGP_REPOSITORY
+ $PLUGIN_REPOSITORIES
+ }
+}
+
+dependencyResolutionManagement {
+ repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
+ repositories {
+ $AGP_REPOSITORY
+ $DEPENDENCY_REPOSITORIES
+ }
+}
+
+include(":app")
diff --git a/recipes/transformDirectory/build-logic/plugins/src/main/kotlin/CustomPlugin.kt b/recipes/transformDirectory/build-logic/plugins/src/main/kotlin/CustomPlugin.kt
index 08b16246..ad375322 100644
--- a/recipes/transformDirectory/build-logic/plugins/src/main/kotlin/CustomPlugin.kt
+++ b/recipes/transformDirectory/build-logic/plugins/src/main/kotlin/CustomPlugin.kt
@@ -17,12 +17,10 @@
import com.android.build.api.artifact.SingleArtifact
import com.android.build.api.variant.ApplicationAndroidComponentsExtension
import com.android.build.gradle.AppPlugin
-import org.gradle.api.DefaultTask
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.tasks.OutputDirectory
-import org.gradle.api.tasks.TaskAction
import org.gradle.kotlin.dsl.register
import org.gradle.configurationcache.extensions.capitalized