Update Room Gradle plugin APIs to not always require per-variant configurations.
Most Room users don't have per-variant schemas which means having per-variant schema directories can decrease the user experience, specially as users migrate from the current annotation processor option approach to the plugin as per-variant directories where also not needed then if not necessary. This change updates the plugin APIs to accept a global location for all variants without creating multiple directories, enabling smoother migrations but also flexible enough to configure flavors or build type schemas while still retaining the benefits of the plugin (reproducible and cacheable builds).
The API is updated to contain overloads for configuring a single schema location or per-variant locations:
```
room {
// For all variants
schemaLocation("$projectDir/schemas/")
// For a specific variant
schemaLocation("variantName", ("$projectDir/schemas/variantName")
}
```
and an example usage assuming two build flavors: ‘demo’ and ‘full’ and the two default build types ‘debug’ and ‘release’:
```
room {
// Applies to demoDebug only
schemaLocation("demoDebug", ("$projectDir/schemas/demoDebug")
// Applies to demoDebug and demoRelease
schemaLocation("demo", ("$projectDir/schemas/demo")
// Applies to demoDebug and fullDebug
schemaLocation("debug", ("$projectDir/schemas/debug")
}
```
Bug: 278266663
Test: RoomGradlePluginTest
Change-Id: I09d6f9a77d3737bef2ce73193344a8b31f5059a8
diff --git a/room/room-gradle-plugin/src/main/java/androidx/room/gradle/RoomExtension.kt b/room/room-gradle-plugin/src/main/java/androidx/room/gradle/RoomExtension.kt
index dc10b3c..bf9bd93 100644
--- a/room/room-gradle-plugin/src/main/java/androidx/room/gradle/RoomExtension.kt
+++ b/room/room-gradle-plugin/src/main/java/androidx/room/gradle/RoomExtension.kt
@@ -19,19 +19,22 @@
import javax.inject.Inject
import org.gradle.api.provider.Provider
import org.gradle.api.provider.ProviderFactory
+import org.gradle.api.tasks.TaskProvider
open class RoomExtension @Inject constructor(private val providers: ProviderFactory) {
- internal var schemaDirectory: Provider<String>? = null
-
- // TODO(b/279748243): Consider adding overload that takes `org.gradle.api.file.Director`.
+ // TODO(b/279748243): Consider adding overloads that takes `org.gradle.api.file.Directory`.
+ // User provided variant match pattern to schema location
+ internal val schemaDirectories = mutableMapOf<VariantMatchName, Provider<String>>()
+ // Used variant match pattern to its copy task. Multiple variant compile tasks can be finalized
+ // by the same copy task.
+ internal val copyTasks =
+ mutableMapOf<VariantMatchName, TaskProvider<RoomGradlePlugin.RoomSchemaCopyTask>>()
/**
* Sets the schema location where Room will output exported schema files.
*
- * The location specified will be used as the base directory for schema files that will be
- * generated per build variant. i.e. for a 'debug' build of the product flavor 'free' then a
- * schema will be generated in
- * `<schemaDirectory>/freeDebug/<database-package>/<database-version>.json`.
+ * The location specified will be used for all variants if per-variant schema locations are
+ * needed use the overloaded version of this function that takes in a `variantMatchName`.
*
* See [Export Schemas Documentation](https://developer.android.com/training/data-storage/room/migrating-db-versions#export-schemas)
*/
@@ -42,14 +45,89 @@
/**
* Sets the schema location where Room will output exported schema files.
*
- * The location specified will be used as the base directory for schema files that will be
- * generated per build variant. i.e. for a 'debug' build of the product flavor 'free' then a
- * schema will be generated in
- * `<schemaDirectory>/freeDebug/<database-package>/<database-version>.json`.
+ * The location specified will be used for all variants if per-variant schema locations are
+ * needed use the overloaded version of this function that takes in a `variantMatchName`.
*
* See [Export Schemas Documentation](https://developer.android.com/training/data-storage/room/migrating-db-versions#export-schemas)
*/
open fun schemaDirectory(path: Provider<String>) {
- schemaDirectory = path
+ schemaDirectories[ALL_VARIANTS] = path
+ }
+
+ /**
+ * Sets the schema location for a variant, flavor or build type where Room will output exported
+ * schema files.
+ *
+ * The location specified will be used for a matching variants based on the provided
+ * [variantMatchName] where it can either be a full variant name, a product flavor name or a
+ * build type name.
+ *
+ * For example, assuming two build flavors: ‘demo’ and ‘full’ and the two default build types
+ * ‘debug’ and ‘release’, then the following are valid configurations:
+ * ```
+ * room {
+ * // Applies 'demoDebug' only
+ * schemaLocation("demoDebug", ("$projectDir/schemas/demoDebug")
+ *
+ * // Applies to 'demoDebug' and 'demoRelease'
+ * schemaLocation("demo", ("$projectDir/schemas/demo")
+ *
+ * // Applies to 'demoDebug' and 'fullDebug'
+ * schemaLocation("debug", ("$projectDir/schemas/debug")
+ * }
+ * ```
+ *
+ * If per-variant schema locations are not necessary due to all variants containing the same
+ * schema, then use the overloaded version of this function that does not take in a
+ * `variantMatchName`.
+ *
+ * See [Export Schemas Documentation](https://developer.android.com/training/data-storage/room/migrating-db-versions#export-schemas)
+ */
+ open fun schemaDirectory(variantMatchName: String, path: String) {
+ schemaDirectory(variantMatchName, providers.provider { path })
+ }
+
+ /**
+ * Sets the schema location for a variant, flavor or build type where Room will output exported
+ * schema files.
+ *
+ * The location specified will be used for a matching variants based on the provided
+ * [variantMatchName] where it can either be a full variant name, a product flavor name or a
+ * build type name.
+ *
+ * For example, assuming two build flavors: ‘demo’ and ‘full’ and the two default build types
+ * ‘debug’ and ‘release’, then the following are valid configurations:
+ * ```
+ * room {
+ * // Applies 'demoDebug' only
+ * schemaLocation("demoDebug", ("$projectDir/schemas/demoDebug")
+ *
+ * // Applies to 'demoDebug' and 'demoRelease'
+ * schemaLocation("demo", ("$projectDir/schemas/demo")
+ *
+ * // Applies to 'demoDebug' and 'fullDebug'
+ * schemaLocation("debug", ("$projectDir/schemas/debug")
+ * }
+ * ```
+ *
+ * If per-variant schema locations are not necessary due to all variants containing the same
+ * schema, then use the overloaded version of this function that does not take in a
+ * `variantMatchName`.
+ *
+ * See [Export Schemas Documentation](https://developer.android.com/training/data-storage/room/migrating-db-versions#export-schemas)
+ */
+ open fun schemaDirectory(variantMatchName: String, path: Provider<String>) {
+ check(variantMatchName.isNotEmpty()) { "variantMatchName must not be empty." }
+ schemaDirectories[VariantMatchName(variantMatchName)] = path
+ }
+
+ /**
+ * Represent a full variant name (demoDebug), flavor name (demo) or build type name (debug).
+ */
+ @JvmInline
+ internal value class VariantMatchName(val actual: String)
+
+ companion object {
+ internal val ALL_VARIANTS = VariantMatchName("")
}
}
\ No newline at end of file
diff --git a/room/room-gradle-plugin/src/main/java/androidx/room/gradle/RoomGradlePlugin.kt b/room/room-gradle-plugin/src/main/java/androidx/room/gradle/RoomGradlePlugin.kt
index 9dacb6d..98794bc 100644
--- a/room/room-gradle-plugin/src/main/java/androidx/room/gradle/RoomGradlePlugin.kt
+++ b/room/room-gradle-plugin/src/main/java/androidx/room/gradle/RoomGradlePlugin.kt
@@ -16,17 +16,22 @@
package androidx.room.gradle
+import androidx.room.gradle.RoomExtension.VariantMatchName
import com.android.build.api.AndroidPluginVersion
import com.android.build.api.variant.AndroidComponentsExtension
import com.android.build.api.variant.ComponentIdentity
-import com.android.build.api.variant.Variant
+import com.android.build.api.variant.HasAndroidTest
import com.android.build.gradle.api.AndroidBasePlugin
import com.google.devtools.ksp.gradle.KspTaskJvm
+import java.io.File
+import java.security.MessageDigest
import java.util.Locale
import javax.inject.Inject
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.contract
import kotlin.io.path.Path
+import kotlin.io.path.copyTo
+import kotlin.io.path.createDirectories
import kotlin.io.path.notExists
import org.gradle.api.DefaultTask
import org.gradle.api.GradleException
@@ -48,9 +53,7 @@
import org.gradle.api.tasks.PathSensitivity
import org.gradle.api.tasks.SkipWhenEmpty
import org.gradle.api.tasks.TaskAction
-import org.gradle.api.tasks.TaskProvider
import org.gradle.api.tasks.compile.JavaCompile
-import org.gradle.configurationcache.extensions.capitalized
import org.gradle.process.CommandLineArgumentProvider
import org.gradle.work.DisableCachingByDefault
import org.jetbrains.kotlin.gradle.internal.KaptTask
@@ -78,7 +81,7 @@
project.extensions.create("room", RoomExtension::class.java)
val componentsExtension =
project.extensions.findByType(AndroidComponentsExtension::class.java)
- project.check(componentsExtension != null) {
+ project.check(componentsExtension != null, isFatal = true) {
"Could not find the Android Gradle Plugin (AGP) extension, the Room Gradle plugin " +
"should be only applied to an Android projects."
}
@@ -87,43 +90,61 @@
"version 7.3.0 or higher (found ${componentsExtension.pluginVersion})."
}
componentsExtension.onVariants { variant ->
- val locationProvider = roomExtension.schemaDirectory
- project.check(locationProvider != null) {
- "The Room Gradle plugin was applied but not schema location was specified. " +
+ project.check(roomExtension.schemaDirectories.isNotEmpty(), isFatal = true) {
+ "The Room Gradle plugin was applied but no schema location was specified. " +
"Use the `room { schemaDirectory(...) }` DSL to specify one."
}
- val schemaDirectory = locationProvider.get()
- project.check(schemaDirectory.isNotEmpty()) {
- "The schemaDirectory path must not be empty."
+ configureVariant(project, roomExtension, variant)
+ variant.unitTest?.let { configureVariant(project, roomExtension, it) }
+ if (variant is HasAndroidTest) {
+ variant.androidTest?.let { configureVariant(project, roomExtension, it) }
}
- configureVariant(project, schemaDirectory, variant)
}
}
private fun configureVariant(
project: Project,
- schemaDirectory: String,
- variant: Variant
+ roomExtension: RoomExtension,
+ variant: ComponentIdentity
) {
- val androidVariantTaskNames = AndroidVariantsTaskNames(variant.name, variant)
val configureTask: (Task, ComponentIdentity) -> RoomSchemaDirectoryArgumentProvider = {
task, variantIdentity ->
- val schemaDirectoryPath = Path(schemaDirectory, variantIdentity.name)
+ // Find schema location for variant from user declared location with priority:
+ // * Full variant name specified, e.g. `schemaLocation("demoDebug", "...")`
+ // * Flavor name, e.g. `schemaLocation("demo", "...")`
+ // * Build type name, e.g. `schemaLocation("debug", "...")`
+ // * All variants location, e.g. `schemaLocation("...")`
+ val schemaDirectories = roomExtension.schemaDirectories
+ fun <V> Map<VariantMatchName, V>.findPair(key: String) =
+ VariantMatchName(key).let { if (containsKey(it)) it to getValue(it) else null }
+ val matchedPair = schemaDirectories.findPair(variantIdentity.name)
+ ?: variantIdentity.flavorName?.let { schemaDirectories.findPair(it) }
+ ?: variantIdentity.buildType?.let { schemaDirectories.findPair(it) }
+ ?: schemaDirectories.findPair(RoomExtension.ALL_VARIANTS.actual)
+ project.check(matchedPair != null, isFatal = true) {
+ "No matching schema directory for variant '${variantIdentity.name}'."
+ }
+ val (matchedName, schemaDirectoryProvider) = matchedPair
+ val schemaDirectory = schemaDirectoryProvider.get()
+ project.check(schemaDirectory.isNotEmpty()) {
+ "The schema directory path for variant '${variantIdentity.name}' must not be empty."
+ }
+ val schemaDirectoryPath = Path(schemaDirectory)
if (schemaDirectoryPath.notExists()) {
project.check(schemaDirectoryPath.toFile().mkdirs()) {
"Unable to create directory: $schemaDirectoryPath"
}
}
+
val schemaInputDir = objectFactory.directoryProperty().apply {
set(project.file(schemaDirectoryPath))
}
-
val schemaOutputDir =
projectLayout.buildDirectory.dir("intermediates/room/schemas/${task.name}")
- val copyTask = androidVariantTaskNames.copyTasks.getOrPut(variant.name) {
+ val copyTask = roomExtension.copyTasks.getOrPut(matchedName) {
project.tasks.register(
- "copyRoomSchemas${variantIdentity.name.capitalize()}",
+ "copyRoomSchemas${matchedName.actual.capitalize()}",
RoomSchemaCopyTask::class.java
) {
it.schemaDirectory.set(schemaInputDir)
@@ -139,6 +160,7 @@
)
}
+ val androidVariantTaskNames = AndroidVariantsTaskNames(variant.name, variant)
configureJavaTasks(project, androidVariantTaskNames, configureTask)
configureKaptTasks(project, androidVariantTaskNames, configureTask)
configureKspTasks(project, androidVariantTaskNames, configureTask)
@@ -198,19 +220,16 @@
private val variantName: String,
private val variantIdentity: ComponentIdentity
) {
- // Variant name to copy task
- val copyTasks = mutableMapOf<String, TaskProvider<RoomSchemaCopyTask>>()
-
private val javaCompileName by lazy {
- "compile${variantName.capitalized()}JavaWithJavac"
+ "compile${variantName.capitalize()}JavaWithJavac"
}
private val kaptTaskName by lazy {
- "kapt${variantName.capitalized()}Kotlin"
+ "kapt${variantName.capitalize()}Kotlin"
}
private val kspTaskJvm by lazy {
- "ksp${variantName.capitalized()}Kotlin"
+ "ksp${variantName.capitalize()}Kotlin"
}
fun withJavaCompile(taskName: String) =
@@ -236,14 +255,48 @@
@TaskAction
fun copySchemas() {
+ // Map of relative path to its source file hash.
+ val copiedHashes = mutableMapOf<String, MutableMap<String, String>>()
variantSchemaOutputDirectories.files
.filter { it.exists() }
- .forEach {
- // TODO(b/278266663): Error when two same relative path schemas are found in out
- // dirs and their content is different an indicator of an inconsistency between
- // the compile tasks of the same variant.
- it.copyRecursively(schemaDirectory.get().asFile, overwrite = true)
+ .forEach { outputDir ->
+ outputDir.walkTopDown().filter { it.isFile }.forEach { schemaFile ->
+ val schemaPath = schemaFile.toPath()
+ val basePath = outputDir.toPath().relativize(schemaPath)
+ schemaPath.copyTo(
+ target = schemaDirectory.get().asFile.toPath().resolve(basePath)
+ .apply { parent?.createDirectories() },
+ overwrite = true
+ )
+ copiedHashes.getOrPut(basePath.toString()) { mutableMapOf() }
+ .put(schemaFile.sha256(), schemaPath.toString())
+ }
}
+ // Validate that if multiple schema files for the same database and version are copied
+ // to the same schema directory that they are the same in content (via checksum), as
+ // otherwise it would indicate pre-variant schemas and thus requiring pre-variant
+ // schema directories.
+ copiedHashes.filterValues { it.size > 1 }.forEach { (schemaDir, hashes) ->
+ val errorMsg = buildString {
+ appendLine(
+ "Inconsistency detected exporting schema files (checksum - source):"
+ )
+ hashes.entries.forEach {
+ appendLine(" ${it.key} - ${it.value}")
+ }
+ appendLine(
+ "The listed files differ in content but were copied into the same " +
+ "schema directory '$schemaDir'. A possible indicator that " +
+ "per-variant schema locations must be provided."
+ )
+ }
+ throw GradleException(errorMsg)
+ }
+ }
+
+ private fun File.sha256(): String {
+ return MessageDigest.getInstance("SHA-256").digest(this.readBytes())
+ .joinToString("") { "%02x".format(it) }
}
}
@@ -276,11 +329,15 @@
}
@OptIn(ExperimentalContracts::class)
- internal fun Project.check(value: Boolean, lazyMessage: () -> String) {
+ internal fun Project.check(
+ value: Boolean,
+ isFatal: Boolean = false,
+ lazyMessage: () -> String
+ ) {
contract {
returns() implies value
}
- if (isGradleSyncRunning()) return
+ if (isGradleSyncRunning() && !isFatal) return
if (!value) {
throw GradleException(lazyMessage())
}
diff --git a/room/room-gradle-plugin/src/test/java/androidx/room/gradle/RoomGradlePluginTest.kt b/room/room-gradle-plugin/src/test/java/androidx/room/gradle/RoomGradlePluginTest.kt
index 42c16d8..90ced10 100644
--- a/room/room-gradle-plugin/src/test/java/androidx/room/gradle/RoomGradlePluginTest.kt
+++ b/room/room-gradle-plugin/src/test/java/androidx/room/gradle/RoomGradlePluginTest.kt
@@ -28,10 +28,9 @@
import org.junit.Test
import org.junit.runner.RunWith
+@Suppress("JUnitMalformedDeclaration") // Using TestParameterInjector in test functions.
@RunWith(TestParameterInjector::class)
-class RoomGradlePluginTest(
- @TestParameter val backend: ProcessingBackend
-) {
+class RoomGradlePluginTest {
@get:Rule
val projectSetup = ProjectSetupRule()
@@ -39,7 +38,12 @@
projectSetup.getLibraryLatestVersionInLocalRepo("androidx/room/room-compiler")
}
- private fun setup(projectName: String, projectRoot: File = projectSetup.rootDir) {
+ private fun setup(
+ projectName: String,
+ backend: ProcessingBackend = ProcessingBackend.JAVAC,
+ projectRoot: File = projectSetup.rootDir,
+ schemaDslLines: List<String> = listOf("schemaDirectory(\"\$projectDir/schemas\")")
+ ) {
// copy test project
File("src/test/test-data/$projectName").copyRecursively(projectRoot)
@@ -60,14 +64,14 @@
""
ProcessingBackend.KAPT ->
"""
- id('kotlin-android')
- id('kotlin-kapt')
- """
+ | id('kotlin-android')
+ | id('kotlin-kapt')
+ """.trimMargin()
ProcessingBackend.KSP ->
"""
- id('kotlin-android')
- id('com.google.devtools.ksp')
- """
+ | id('kotlin-android')
+ | id('com.google.devtools.ksp')
+ """.trimMargin()
}
val repositoriesBlock = buildString {
@@ -101,48 +105,44 @@
// set up build file
File(projectRoot, "build.gradle").writeText(
"""
- plugins {
- id('com.android.application')
- id('androidx.room')
- $additionalPluginsBlock
- }
-
- $repositoriesBlock
-
- %s
-
- dependencies {
- // Uses latest Room built from tip of tree
- implementation "androidx.room:room-runtime:$roomVersion"
- $processorConfig "androidx.room:room-compiler:$roomVersion"
- }
-
- android {
- namespace "room.testapp"
- compileOptions {
- sourceCompatibility = JavaVersion.VERSION_1_8
- targetCompatibility = JavaVersion.VERSION_1_8
- }
- }
-
- $kotlinJvmTargetBlock
-
- room {
- schemaDirectory("${'$'}projectDir/schemas")
- }
-
- """
- .trimMargin()
- // doing format instead of "$projectSetup.androidProject" on purpose,
- // because otherwise trimIndent will mess with formatting
- .format(projectSetup.androidProject)
+ |plugins {
+ | id('com.android.application')
+ | id('androidx.room')
+ | $additionalPluginsBlock
+ |}
+ |
+ |$repositoriesBlock
+ |
+ |${projectSetup.androidProject}
+ |
+ |dependencies {
+ | // Uses latest Room built from tip of tree
+ | implementation "androidx.room:room-runtime:$roomVersion"
+ | $processorConfig "androidx.room:room-compiler:$roomVersion"
+ |}
+ |
+ |android {
+ | namespace "room.testapp"
+ | compileOptions {
+ | sourceCompatibility = JavaVersion.VERSION_1_8
+ | targetCompatibility = JavaVersion.VERSION_1_8
+ | }
+ |}
+ |
+ |$kotlinJvmTargetBlock
+ |
+ |room {
+ |${schemaDslLines.joinToString(separator = "\n")}
+ |}
+ |
+ """.trimMargin()
)
}
@Test
- fun testWorkflow() {
- setup("simple-project")
+ fun testWorkflow(@TestParameter backend: ProcessingBackend) {
+ setup("simple-project", backend = backend)
// First clean build, all tasks need to run
runGradleTasks(CLEAN_TASK, COMPILE_TASK).let { result ->
@@ -152,7 +152,7 @@
// Schema file at version 1 is created
var schemaOneTimestamp: Long
- projectSetup.rootDir.resolve("schemas/debug/room.testapp.MyDatabase/1.json").let {
+ projectSetup.rootDir.resolve("schemas/room.testapp.MyDatabase/1.json").let {
assertThat(it.exists()).isTrue()
schemaOneTimestamp = it.lastModified()
}
@@ -184,7 +184,7 @@
}
// Check schema file at version 1 is updated
- projectSetup.rootDir.resolve("schemas/debug/room.testapp.MyDatabase/1.json").let {
+ projectSetup.rootDir.resolve("schemas/room.testapp.MyDatabase/1.json").let {
assertThat(it.exists()).isTrue()
assertThat(schemaOneTimestamp).isNotEqualTo(it.lastModified())
schemaOneTimestamp = it.lastModified()
@@ -239,20 +239,27 @@
}
// Check schema file at version 1 is still present and unchanged.
- projectSetup.rootDir.resolve("schemas/debug/room.testapp.MyDatabase/1.json").let {
+ projectSetup.rootDir.resolve("schemas/room.testapp.MyDatabase/1.json").let {
assertThat(it.exists()).isTrue()
assertThat(schemaOneTimestamp).isEqualTo(it.lastModified())
}
// Check schema file at version 2 is created and copied.
- projectSetup.rootDir.resolve("schemas/debug/room.testapp.MyDatabase/2.json").let {
+ projectSetup.rootDir.resolve("schemas/room.testapp.MyDatabase/2.json").let {
assertThat(it.exists()).isTrue()
}
}
@Test
- fun testFlavoredProject() {
- setup("flavored-project")
+ fun testFlavoredProject(@TestParameter backend: ProcessingBackend) {
+ setup(
+ projectName = "flavored-project",
+ backend = backend,
+ schemaDslLines = listOf(
+ "schemaDirectory(\"flavorOne\", \"\$projectDir/schemas/flavorOne\")",
+ "schemaDirectory(\"flavorTwo\", \"\$projectDir/schemas/flavorTwo\")"
+ )
+ )
File(projectSetup.rootDir, "build.gradle").appendText(
"""
@@ -277,15 +284,15 @@
).let { result ->
result.assertTaskOutcome(":compileFlavorOneDebugJavaWithJavac", TaskOutcome.SUCCESS)
result.assertTaskOutcome(":compileFlavorTwoDebugJavaWithJavac", TaskOutcome.SUCCESS)
- result.assertTaskOutcome(":copyRoomSchemasFlavorOneDebug", TaskOutcome.SUCCESS)
- result.assertTaskOutcome(":copyRoomSchemasFlavorTwoDebug", TaskOutcome.SUCCESS)
+ result.assertTaskOutcome(":copyRoomSchemasFlavorOne", TaskOutcome.SUCCESS)
+ result.assertTaskOutcome(":copyRoomSchemasFlavorTwo", TaskOutcome.SUCCESS)
}
// Check schema files are generated for both flavor, each in its own folder.
val flavorOneSchema = projectSetup.rootDir.resolve(
- "schemas/flavorOneDebug/room.testapp.MyDatabase/1.json"
+ "schemas/flavorOne/room.testapp.MyDatabase/1.json"
)
val flavorTwoSchema = projectSetup.rootDir.resolve(
- "schemas/flavorTwoDebug/room.testapp.MyDatabase/1.json"
+ "schemas/flavorTwo/room.testapp.MyDatabase/1.json"
)
assertThat(flavorOneSchema.exists()).isTrue()
assertThat(flavorTwoSchema.exists()).isTrue()
@@ -294,8 +301,15 @@
}
@Test
- fun testMoreBuildTypesProject() {
- setup("simple-project")
+ fun testMoreBuildTypesProject(@TestParameter backend: ProcessingBackend) {
+ setup(
+ projectName = "simple-project",
+ backend = backend,
+ schemaDslLines = listOf(
+ "schemaDirectory(\"\$projectDir/schemas\")",
+ "schemaDirectory(\"staging\", \"\$projectDir/schemas/staging\")"
+ )
+ )
File(projectSetup.rootDir, "build.gradle").appendText(
"""
@@ -310,7 +324,7 @@
""".trimIndent()
)
- runGradleTasks(CLEAN_TASK, "compileStagingJavaWithJavac",).let { result ->
+ runGradleTasks(CLEAN_TASK, "compileStagingJavaWithJavac").let { result ->
result.assertTaskOutcome(":compileStagingJavaWithJavac", TaskOutcome.SUCCESS)
result.assertTaskOutcome(":copyRoomSchemasStaging", TaskOutcome.SUCCESS)
}
@@ -320,16 +334,129 @@
assertThat(schemeFile.exists()).isTrue()
}
+ @Test
+ fun testMissingConfigProject() {
+ setup(
+ projectName = "simple-project",
+ schemaDslLines = listOf()
+ )
+
+ runGradleTasks(CLEAN_TASK, COMPILE_TASK, expectFailure = true).let { result ->
+ assertThat(result.output).contains(
+ "The Room Gradle plugin was applied but no schema location was specified."
+ )
+ }
+ }
+
+ @Test
+ fun testEmptyDirConfigProject() {
+ setup(
+ projectName = "simple-project",
+ schemaDslLines = listOf("schemaDirectory(\"\")")
+ )
+
+ runGradleTasks(CLEAN_TASK, COMPILE_TASK, expectFailure = true).let { result ->
+ assertThat(result.output).contains(
+ "The schema directory path for variant 'debug' must not be empty."
+ )
+ }
+ }
+
+ @Test
+ fun testMissingConfigFlavoredProject() {
+ setup(
+ projectName = "flavored-project",
+ schemaDslLines = listOf(
+ "schemaDirectory(\"flavorOne\", \"\$projectDir/schemas/flavorOne\")",
+ )
+ )
+
+ File(projectSetup.rootDir, "build.gradle").appendText(
+ """
+ android {
+ flavorDimensions "mode"
+ productFlavors {
+ flavorOne {
+ dimension "mode"
+ }
+ flavorTwo {
+ dimension "mode"
+ }
+ }
+ }
+ """.trimIndent()
+ )
+
+ runGradleTasks(
+ CLEAN_TASK,
+ "compileFlavorOneDebugJavaWithJavac",
+ "compileFlavorTwoDebugJavaWithJavac",
+ expectFailure = true
+ ).let { result ->
+ assertThat(result.output).contains(
+ "No matching schema directory for variant 'flavorTwoDebug'."
+ )
+ }
+ }
+
+ @Test
+ fun testCopyInconsistencyFlavoredProject(@TestParameter backend: ProcessingBackend) {
+ setup(
+ projectName = "flavored-project",
+ backend = backend,
+ schemaDslLines = listOf(
+ "schemaDirectory(\"\$projectDir/schemas\")",
+ )
+ )
+
+ File(projectSetup.rootDir, "build.gradle").appendText(
+ """
+ android {
+ flavorDimensions "mode"
+ productFlavors {
+ flavorOne {
+ dimension "mode"
+ }
+ flavorTwo {
+ dimension "mode"
+ }
+ }
+ }
+ """.trimIndent()
+ )
+
+ runGradleTasks(
+ CLEAN_TASK,
+ "compileFlavorOneDebugJavaWithJavac",
+ "compileFlavorTwoDebugJavaWithJavac",
+ expectFailure = true
+ ).let { result ->
+ result.assertTaskOutcome(":compileFlavorOneDebugJavaWithJavac", TaskOutcome.SUCCESS)
+ result.assertTaskOutcome(":compileFlavorTwoDebugJavaWithJavac", TaskOutcome.SUCCESS)
+ result.assertTaskOutcome(":copyRoomSchemas", TaskOutcome.FAILED)
+
+ assertThat(result.output).contains(
+ "Inconsistency detected exporting schema files"
+ )
+ }
+ }
+
private fun runGradleTasks(
vararg args: String,
- projectDir: File = projectSetup.rootDir
+ projectDir: File = projectSetup.rootDir,
+ expectFailure: Boolean = false
): BuildResult {
- return GradleRunner.create()
+ val runner = GradleRunner.create()
.withProjectDir(projectDir)
.withPluginClasspath()
+ .withDebug(true)
// workaround for b/231154556
.withArguments("-Dorg.gradle.jvmargs=-Xmx1g -XX:MaxMetaspaceSize=512m", *args)
- .build()
+ return if (expectFailure) {
+ runner.buildAndFail()
+ } else {
+ runner.build()
+ }
}
private fun BuildResult.assertTaskOutcome(taskPath: String, outcome: TaskOutcome) {
@@ -351,6 +478,6 @@
companion object {
private const val CLEAN_TASK = ":clean"
private const val COMPILE_TASK = ":compileDebugJavaWithJavac"
- private const val COPY_TASK = ":copyRoomSchemasDebug"
+ private const val COPY_TASK = ":copyRoomSchemas"
}
}
\ No newline at end of file