diff --git a/build.gradle b/build.gradle index 5b4a27741e9..5f53f805494 100644 --- a/build.gradle +++ b/build.gradle @@ -33,7 +33,7 @@ buildscript { } dependencies { - classpath 'com.google.protobuf:protobuf-gradle-plugin:0.9.2' + classpath 'com.google.protobuf:protobuf-gradle-plugin:0.9.4' classpath 'net.ltgt.gradle:gradle-errorprone-plugin:1.3.0' classpath 'gradle.plugin.com.github.sherter.google-java-format:google-java-format-gradle-plugin:0.9' classpath 'com.google.gms:google-services:4.3.15' diff --git a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/FirebaseLibraryPlugin.kt b/buildSrc/src/main/java/com/google/firebase/gradle/plugins/FirebaseLibraryPlugin.kt index 4660b859a21..2f05a18c25a 100644 --- a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/FirebaseLibraryPlugin.kt +++ b/buildSrc/src/main/java/com/google/firebase/gradle/plugins/FirebaseLibraryPlugin.kt @@ -86,9 +86,18 @@ class FirebaseLibraryPlugin : BaseFirebaseLibraryPlugin() { setupStaticAnalysis(project, firebaseLibrary) getIsPomValidTask(project, firebaseLibrary) getSemverTaskAar(project, firebaseLibrary) + getPackageTransform(project, firebaseLibrary) configurePublishing(project, firebaseLibrary, android) } + private fun getPackageTransform(project: Project, firebaseLibrary: FirebaseLibraryExtension) { + project.tasks.register("packageTransform") { + groupId.set(firebaseLibrary.groupId.get()) + artifactId.set(firebaseLibrary.artifactId.get()) + projectPath.set(project.projectDir.absolutePath) + } + } + private fun getSemverTaskAar(project: Project, firebaseLibrary: FirebaseLibraryExtension) { project.mkdir("semver") project.tasks.register("copyPreviousArtifacts") { diff --git a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/PackageTransform.kt b/buildSrc/src/main/java/com/google/firebase/gradle/plugins/PackageTransform.kt new file mode 100644 index 00000000000..adb82cfd20c --- /dev/null +++ b/buildSrc/src/main/java/com/google/firebase/gradle/plugins/PackageTransform.kt @@ -0,0 +1,455 @@ +// Copyright 2023 Google 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 +// +// 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.google.firebase.gradle.plugins + +import java.io.File +import java.nio.file.Files +import java.nio.file.Path +import java.nio.file.StandardCopyOption +import javax.xml.parsers.DocumentBuilderFactory +import org.gradle.api.DefaultTask +import org.gradle.api.provider.Property +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.TaskAction +import org.gradle.configurationcache.extensions.capitalized +import org.gradle.kotlin.dsl.project +import org.w3c.dom.Element + +val PROJECT_LEVEL_REQUIRED = + mutableListOf( + "firebase-appdistribution-api", + "firebase-common", + "firebase-config", + "firebase-crashlytics", + "firebase-components", + "firebase-database", + "firebase-dynamic-links", + "firebase-firestore", + "firebase-functions", + "firebase-messaging", + "firebase-inappmessaging", + "firebase-inappmessaging-display", + "firebase-installations", + "firebase-ml-modeldownloader", + "firebase-perf", + "firebase-storage", + ) +val KTX_CONTENT = + """ + // Copyright 2023 Google 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 + // + // 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 #{PACKAGE_NAME}.ktx + + import androidx.annotation.Keep + import com.google.firebase.components.Component + import com.google.firebase.components.ComponentRegistrar + import #{PACKAGE_NAME}.BuildConfig + import com.google.firebase.platforminfo.LibraryVersionComponent + + internal const val LIBRARY_NAME: String = #{LIBRARY_NAME} + + /** @suppress */ + @Keep + class #{PROJECT_NAME}LoggingRegistrar : ComponentRegistrar { + override fun getComponents(): List> { + return listOf(LibraryVersionComponent.create(LIBRARY_NAME, BuildConfig.VERSION_NAME)) + } + } +""" + .trimIndent() + +abstract class PackageTransform : DefaultTask() { + @get:Input abstract val artifactId: Property + @get:Input abstract val groupId: Property + @get:Input abstract val projectPath: Property + + fun getSymbol(line: String): String { + val parts = line.split(" ") + if (parts.get(0) == "val" || parts.get(0) == "class") { + // Field or class + return parts.get(1).replace(":", "") + } else if (parts.get(0) == "object") { + return parts.get(1) + } else if (parts.get(0) == "fun") { + // Method + var output: String = "" + var ignore = false + for (c in line) { + if (c == ':') { + ignore = true + } + if (c == ')' || c == ',') { + ignore = false + } + if (c == '=') { + break + } + if (!ignore) { + output += c + } + } + return output.replace("fun", "") + } + return "" + } + + fun copyDir(src: Path, dest: Path, cont: Boolean) { + for (file in Files.walk(src)) { + if (!Files.isDirectory(file)) { + val destination = dest.resolve(src.relativize(file)) + Files.createDirectories(destination.parent) + Files.copy(file, destination, StandardCopyOption.REPLACE_EXISTING) + } + } + if (!cont) return + val dir: File = File(src.parent.toString()) + if (dir.exists() && dir.isDirectory) { + for (file in Files.walk(dir.toPath())) { + if (!Files.isDirectory(file) && !file.toAbsolutePath().contains(src.toAbsolutePath())) { + val destination = dest.resolve(dir.toPath().relativize(file)) + Files.createDirectories(destination.parent) + Files.copy(file, destination, StandardCopyOption.REPLACE_EXISTING) + } + } + } + } + + fun deprecateKTX(src: String, pkgName: String) { + for (file in File(src).walk()) { + if (file.absolutePath.endsWith(".kt")) { + val lines = File(file.absolutePath).readLines() + val output = mutableListOf() + for (i in 0 until lines.size) { + output.add(lines[i]) + if (lines[i].contains("*/")) { + var symbol = "" + var ctr = i + 1 + while (symbol.isEmpty() && ctr < lines.size) { + symbol = getSymbol(lines[ctr++]).trim() + } + output.add( + """@Deprecated("Use `${pkgName}${symbol}`", ReplaceWith("${pkgName}${symbol}"))""" + ) + } + } + File(file.absolutePath).writeText(output.joinToString("\n")) + } + } + } + + fun updateKTXReferences(src: String) { + // Remove all .ktx suffixes essentially. + File(src).walk().forEach { + if (it.absolutePath.endsWith(".kt") && !it.absolutePath.contains("/ktx/")) { + val lines = File(it.absolutePath).readLines().map { x -> x.replace(".ktx", "") } + File(it.absolutePath).delete() + val newFile = File(it.absolutePath) + newFile.writeText(lines.joinToString("\n")) + } + } + } + + fun updatePlatformLogging(src: String) { + File(src).walk().forEach { + if (it.absolutePath.endsWith(".kt")) { + val lines = + File(it.absolutePath) + .readLines() + .map { x -> + val p = + x.replace( + "LibraryVersionComponent.create(LIBRARY_NAME, BuildConfig.VERSION_NAME)", + "" + ) + if (p.trim().isEmpty()) { + return@map "#REMOVE#REMOVE#REMOVE" + } else { + return@map p + } + } + .filter { + !it.contains("#REMOVE#REMOVE#REMOVE") && !it.contains("contains(LIBRARY_NAME)") + } + File(it.absolutePath).delete() + val newFile = File(it.absolutePath) + newFile.writeText(lines.joinToString("\n")) + } + } + } + + fun readDependencies(path: String): List { + val lines = File(path).readLines() + var ctr = 0 + var output = mutableListOf() + var tmpString = "" + for (line in lines) { + if (ctr == 0) { + if (line.contains("dependencies {")) { + ctr++ + continue + } + } else if (ctr == 1) { + if (line.contains("{") && !line.contains("}")) { + tmpString = line + ctr += 1 + } else if (line.contains("}") && !line.contains("{")) { + break + } else { + output.add(line.trim()) + } + } else if (ctr == 2) { + val add = " " + if (line.contains("}")) { + ctr = 1 + tmpString += "\n" + add + line + output.add(tmpString.trim()) + } else { + tmpString += "\n" + add + line + } + } + } + return output.filter { x -> (x.trim().isNotEmpty() && !x.startsWith("//")) } + } + + fun copyManifestComponent(source: String, dest: String) { + val documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder() + val ktxManifest = documentBuilder.parse(File(source)) + ktxManifest.documentElement.normalize() + val nodeList = ktxManifest.getElementsByTagName("*") + val metadata = + (0..nodeList.length - 1) + .toList() + .map { x -> nodeList.item(x) } + .filter { it.nodeType == Element.ELEMENT_NODE } + .map { x -> (x as Element) } + .filter { it.nodeName == "meta-data" } + .map { x -> + "" + } + .joinToString("\n") + val lines = File(dest).readLines() + var output = mutableListOf() + var inside = false + for (line in lines) { + if ( + line.contains("android:name=\"com.google.firebase.components.ComponentDiscoveryService\"") + ) { + inside = true + } + if (inside == true) { + if (line.contains("/>")) { + output.add(line.replace("/>", "> ${metadata} ")) + inside = false + continue + } else if (line.contains(">")) { + output.add(line.replace(">", "> ${metadata}")) + inside = false + continue + } + } + output.add(line) + } + File(dest).writeText(output.joinToString("\n")) + } + + @TaskAction + fun run() { + var packageNamePath = "${groupId.get()}.${artifactId.get().split("-")[1]}".replace(".", "/") + packageNamePath = packageNamePath.replace("common", "") + if (artifactId.get().equals("firebase-config")) { + packageNamePath = "com/google/firebase/remoteconfig" + } else if (artifactId.get().equals("firebase-dynamic-links")) { + packageNamePath = "com/google/firebase/dynamiclinks" + } else if (artifactId.get().equals("firebase-ml-modeldownloader")) { + packageNamePath = "com/google/firebase/ml/modeldownloader" + } else if (artifactId.get().equals("firebase-inappmessaging-display")) { + packageNamePath = "com/google/firebase/inappmessaging/display" + } else if (artifactId.get().contains("appcheck")) { + packageNamePath = "com/google/firebase/appcheck" + } + val ktxArtifactPath = "${projectPath.get()}/ktx/src/main/kotlin/${packageNamePath}/ktx" + + val ktxPackagePath = "${projectPath.get()}/src/main/java/${packageNamePath}/ktx" + val mainPackagePath = "${projectPath.get()}/src/main/java/${packageNamePath}" + val ktxArtifactTestPath = "${projectPath.get()}/ktx/src/test/kotlin/${packageNamePath}/ktx" + val ktxPackageTestPath = "${projectPath.get()}/src/test/java/${packageNamePath}/ktx" + val mainPackageTestPath = "${projectPath.get()}/src/test/java/${packageNamePath}" + val ktxArtifactAndroidTestPath = + "${projectPath.get()}/ktx/src/androidTest/kotlin/${packageNamePath}/ktx" + val ktxPackageAndroidTestPath = + "${projectPath.get()}/src/androidTest/java/${packageNamePath}/ktx" + val mainPackageAndroidTestPath = "${projectPath.get()}/src/androidTest/java/${packageNamePath}" + copyDir(File(ktxArtifactPath).toPath(), File(ktxPackagePath).toPath(), false) + copyDir(File(ktxArtifactPath).toPath(), File(mainPackagePath).toPath(), true) + if (File(ktxArtifactTestPath).exists()) { + copyDir(File(ktxArtifactTestPath).toPath(), File(mainPackageTestPath).toPath(), true) + copyDir(File(ktxArtifactTestPath).toPath(), File(ktxPackageTestPath).toPath(), false) + updateKTXReferences(mainPackageTestPath) + } + if (File(ktxArtifactAndroidTestPath).exists()) { + copyDir( + File(ktxArtifactAndroidTestPath).toPath(), + File(mainPackageAndroidTestPath).toPath(), + true + ) + copyDir( + File(ktxArtifactAndroidTestPath).toPath(), + File(ktxPackageAndroidTestPath).toPath(), + false + ) + updateKTXReferences(mainPackageAndroidTestPath) + } + updateKTXReferences(mainPackagePath) + updatePlatformLogging("${projectPath.get()}/src") + copyManifestComponent( + "${projectPath.get()}/ktx/src/main/AndroidManifest.xml", + "${projectPath.get()}/src/main/AndroidManifest.xml" + ) + deprecateKTX(ktxPackagePath, packageNamePath.replace("/", ".")) + var gradlePath = "${projectPath.get()}/${artifactId.get()}.gradle.kts" + var ktxGradlePath = "${projectPath.get()}/ktx/ktx.gradle.kts" + if (!File(gradlePath).exists()) { + gradlePath = "${projectPath.get()}/${artifactId.get()}.gradle" + ktxGradlePath = "${projectPath.get()}/ktx/ktx.gradle" + } + val dependencies = readDependencies(gradlePath) + var ktxDependencies = + readDependencies(ktxGradlePath) + .filter { + !it.contains("project(\"${project.path}\")") && !it.contains("project('${project.path}')") + } + .toMutableList() + val filtered_project_deps = + PROJECT_LEVEL_REQUIRED.map { x -> ":${x}" }.filter { it != project.path } + val deps = + (dependencies.toSet() + ktxDependencies.toSet()) + .toList() + .map { x -> + val matches = + filtered_project_deps.filter { y -> + x.contains(y) && !x.contains("interop") && !x.contains("collection") + } + if (matches.isEmpty()) return@map x + return@map "implementation(project(\"${matches.get(0)}\"))" + } + .toSet() + .toList() + + updateGradleFile(gradlePath, deps) + // KTX changes + updateCode( + ktxArtifactPath, + packageNamePath.replace("/", "."), + "${projectPath.get()}/ktx/src/main/AndroidManifest.xml" + ) + ktxDependencies = + ktxDependencies + .filter { + !((it.contains("implementation") || it.contains("api")) && + (!it.contains("firebase") || it.contains(project.path) || it.contains("-ktx"))) + } + .map { x -> + val filtered = PROJECT_LEVEL_REQUIRED.filter { x.contains("${it}:") } + if (filtered.isEmpty()) return@map x + else return@map "api(project(\":${filtered.get(0)}\"))" + } + .toMutableList() + // KTX gradle changes + ktxDependencies.add("api(project(\"${project.path}\"))") + updateGradleFile(ktxGradlePath, ktxDependencies) + } + private fun updateGradleFile(gradlePath: String, depsArg: List) { + val deps = depsArg.sorted().map { x -> " " + x } + val lines = File(gradlePath).readLines() + val output = mutableListOf() + for (line in lines) { + if (line.contains("id(\"kotlin-android\")") || line.contains("id 'kotlin-android'")) { + continue + } + if ( + line.contains("plugins { id(\"firebase-library\") }") || + line.contains("plugins { id 'firebase-library' }") + ) { + output.add("plugins {") + output.add(" id(\"firebase-library\")") + output.add(" id(\"kotlin-android\")") + output.add("}") + continue + } + output.add(line) + if (line.contains("id(\"firebase-library\")") || line.contains("id 'firebase-library'")) { + output.add(" id(\"kotlin-android\")") + } + if (line.contains("dependencies {")) { + output += deps + output.add("}") + break + } + } + File(gradlePath).writeText(output.joinToString("\n")) + } + private fun extractLibraryName(path: String): String? = + File(path) + .readLines() + .filter { x -> x.contains("LIBRARY_NAME:") } + .map { x -> x.split(" ").last() } + .getOrNull(0) + private fun updateCode(path: String, pkgName: String, manifestPath: String) { + File(path).walk().forEach { + if ( + it.absolutePath.endsWith(".kt") && + !it.absolutePath.endsWith("ChildEvent.kt") && + !it.absolutePath.endsWith("Logging.kt") + ) { + val filePath = it.absolutePath + val projectName = artifactId.get().split("-").map { x -> x.capitalized() }.joinToString("") + val replaceClass: String? = + File(filePath) + .readLines() + .filter { it.contains("class") && it.contains("KtxRegistrar") } + .map { x -> x.split(" ").get(1).replace(":", "") } + .getOrNull(0) + val loggingPath: String = "${File(filePath).parent}/Logging.kt" + val libraryName = extractLibraryName(filePath) + if (!libraryName.isNullOrEmpty()) { + File(loggingPath) + .writeText( + KTX_CONTENT.replace("#{LIBRARY_NAME}", libraryName) + .replace("#{PROJECT_NAME}", projectName) + .replace("#{PACKAGE_NAME}", pkgName.trim('.')) + ) + } + if (!replaceClass.isNullOrEmpty()) { + val lines = + File(manifestPath).readLines().map { x -> + x.replace(replaceClass, "${projectName}LoggingRegistrar") + } + File(manifestPath).writeText(lines.joinToString("\n")) + } + File(filePath).delete() + } + } + } +} diff --git a/firebase-common/firebase-common.gradle.kts b/firebase-common/firebase-common.gradle.kts index ae4fd4292ec..f9f4c2502a1 100644 --- a/firebase-common/firebase-common.gradle.kts +++ b/firebase-common/firebase-common.gradle.kts @@ -14,6 +14,7 @@ plugins { id("firebase-library") + id("kotlin-android") } firebaseLibrary { @@ -51,34 +52,33 @@ android { } dependencies { + androidTestImplementation(libs.androidx.test.junit) + androidTestImplementation(libs.androidx.test.runner) + androidTestImplementation(libs.junit) + androidTestImplementation(libs.mockito.core) + androidTestImplementation(libs.mockito.dexmaker) + androidTestImplementation(libs.truth) + androidTestImplementation(project(":integ-testing")) + annotationProcessor(libs.autovalue) + api(libs.kotlin.coroutines.tasks) + compileOnly(libs.autovalue.annotations) + compileOnly(libs.findbugs.jsr305) + compileOnly(libs.kotlin.stdlib) implementation("com.google.firebase:firebase-annotations:16.2.0") implementation("com.google.firebase:firebase-components:17.1.0") + implementation(libs.androidx.annotation) implementation(libs.androidx.futures) + implementation(libs.kotlin.stdlib) implementation(libs.playservices.basement) implementation(libs.playservices.tasks) - - annotationProcessor(libs.autovalue) - - compileOnly(libs.autovalue.annotations) - compileOnly(libs.findbugs.jsr305) - // needed for Kotlin detection to compile, but not necessarily present at runtime. - compileOnly(libs.kotlin.stdlib) - testImplementation("com.google.guava:guava-testlib:12.0-rc2") - testImplementation(libs.androidx.test.junit) + testImplementation(libs.androidx.test.core) testImplementation(libs.androidx.test.junit) testImplementation(libs.androidx.test.runner) testImplementation(libs.junit) + testImplementation(libs.kotlin.coroutines.test) testImplementation(libs.mockito.core) testImplementation(libs.org.json) testImplementation(libs.robolectric) testImplementation(libs.truth) - - androidTestImplementation(libs.androidx.test.junit) - androidTestImplementation(libs.androidx.test.runner) - androidTestImplementation(libs.junit) - androidTestImplementation(libs.mockito.core) - androidTestImplementation(libs.mockito.dexmaker) - androidTestImplementation(libs.truth) - androidTestImplementation(project(":integ-testing")) -} +} \ No newline at end of file diff --git a/firebase-common/ktx/ktx.gradle.kts b/firebase-common/ktx/ktx.gradle.kts index eb0218a0948..2c5ee2aa6f8 100644 --- a/firebase-common/ktx/ktx.gradle.kts +++ b/firebase-common/ktx/ktx.gradle.kts @@ -42,16 +42,8 @@ android { } dependencies { - implementation(libs.kotlin.stdlib) - - implementation("com.google.firebase:firebase-annotations:16.2.0") - implementation(project(":firebase-common")) implementation("com.google.firebase:firebase-components:17.1.0") - implementation(libs.androidx.annotation) - - // We"re exposing this library as a transitive dependency so developers can - // get Kotlin Coroutines support out-of-the-box for methods that return a Task - api(libs.kotlin.coroutines.tasks) + api(project(":firebase-common")) testImplementation(libs.robolectric) testImplementation(libs.junit) diff --git a/firebase-common/ktx/src/main/AndroidManifest.xml b/firebase-common/ktx/src/main/AndroidManifest.xml index 05858c6eb7b..d7872f2e0dc 100644 --- a/firebase-common/ktx/src/main/AndroidManifest.xml +++ b/firebase-common/ktx/src/main/AndroidManifest.xml @@ -4,7 +4,7 @@ - diff --git a/firebase-common/ktx/src/main/kotlin/com/google/firebase/ktx/Logging.kt b/firebase-common/ktx/src/main/kotlin/com/google/firebase/ktx/Logging.kt new file mode 100644 index 00000000000..6690747e68b --- /dev/null +++ b/firebase-common/ktx/src/main/kotlin/com/google/firebase/ktx/Logging.kt @@ -0,0 +1,29 @@ +// Copyright 2023 Google 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 +// +// 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.google.firebase.ktx + +import androidx.annotation.Keep +import com.google.firebase.components.Component +import com.google.firebase.components.ComponentRegistrar +import com.google.firebase.platforminfo.LibraryVersionComponent + +internal const val LIBRARY_NAME: String = "fire-core-ktx" + +/** @suppress */ +@Keep +class FirebaseCommonLoggingRegistrar : ComponentRegistrar { + override fun getComponents(): List> { + return listOf(LibraryVersionComponent.create(LIBRARY_NAME, BuildConfig.VERSION_NAME)) + } +} diff --git a/firebase-common/ktx/src/test/kotlin/com/google/firebase/ktx/Tests.kt b/firebase-common/ktx/src/test/kotlin/com/google/firebase/ktx/Tests.kt index 43ac45c33b3..4002bbfe334 100644 --- a/firebase-common/ktx/src/test/kotlin/com/google/firebase/ktx/Tests.kt +++ b/firebase-common/ktx/src/test/kotlin/com/google/firebase/ktx/Tests.kt @@ -17,6 +17,7 @@ package com.google.firebase.ktx import androidx.test.core.app.ApplicationProvider import com.google.android.gms.tasks.Tasks import com.google.common.truth.Truth.assertThat +import com.google.firebase.Firebase import com.google.firebase.FirebaseApp import com.google.firebase.FirebaseOptions import com.google.firebase.platforminfo.UserAgentPublisher @@ -50,7 +51,6 @@ class VersionTests { withApp("ktxTestApp") { val uaPublisher = get(UserAgentPublisher::class.java) assertThat(uaPublisher.userAgent).contains("kotlin") - assertThat(uaPublisher.userAgent).contains(LIBRARY_NAME) } } } diff --git a/firebase-common/src/main/AndroidManifest.xml b/firebase-common/src/main/AndroidManifest.xml index a2913a3a915..ed589155c92 100644 --- a/firebase-common/src/main/AndroidManifest.xml +++ b/firebase-common/src/main/AndroidManifest.xml @@ -29,6 +29,9 @@ android:name="com.google.firebase.components.ComponentDiscoveryService" android:directBootAware="true" android:exported="false" - tools:targetApi="n" /> + tools:targetApi="n" > + + diff --git a/firebase-common/src/main/java/com/google/firebase/Firebase.kt b/firebase-common/src/main/java/com/google/firebase/Firebase.kt new file mode 100644 index 00000000000..c18befc0723 --- /dev/null +++ b/firebase-common/src/main/java/com/google/firebase/Firebase.kt @@ -0,0 +1,80 @@ +// Copyright 2019 Google 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 +// +// 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.google.firebase + +import android.content.Context +import androidx.annotation.Keep +import com.google.firebase.annotations.concurrent.Background +import com.google.firebase.annotations.concurrent.Blocking +import com.google.firebase.annotations.concurrent.Lightweight +import com.google.firebase.annotations.concurrent.UiThread +import com.google.firebase.components.Component +import com.google.firebase.components.ComponentRegistrar +import com.google.firebase.components.Dependency +import com.google.firebase.components.Qualified +import java.util.concurrent.Executor +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.asCoroutineDispatcher + +/** + * Single access point to all firebase SDKs from Kotlin. + * + *

Acts as a target for extension methods provided by sdks. + */ +object Firebase + +/** Returns the default firebase app instance. */ +val Firebase.app: FirebaseApp + get() = FirebaseApp.getInstance() + +/** Returns a named firebase app instance. */ +fun Firebase.app(name: String): FirebaseApp = FirebaseApp.getInstance(name) + +/** Initializes and returns a FirebaseApp. */ +fun Firebase.initialize(context: Context): FirebaseApp? = FirebaseApp.initializeApp(context) + +/** Initializes and returns a FirebaseApp. */ +fun Firebase.initialize(context: Context, options: FirebaseOptions): FirebaseApp = + FirebaseApp.initializeApp(context, options) + +/** Initializes and returns a FirebaseApp. */ +fun Firebase.initialize(context: Context, options: FirebaseOptions, name: String): FirebaseApp = + FirebaseApp.initializeApp(context, options, name) + +/** Returns options of default FirebaseApp */ +val Firebase.options: FirebaseOptions + get() = Firebase.app.options + +internal const val LIBRARY_NAME: String = "fire-core-ktx" + +/** @suppress */ +@Keep +class FirebaseCommonKtxRegistrar : ComponentRegistrar { + override fun getComponents(): List> { + return listOf( + coroutineDispatcher(), + coroutineDispatcher(), + coroutineDispatcher(), + coroutineDispatcher() + ) + } +} + +private inline fun coroutineDispatcher(): Component = + Component.builder(Qualified.qualified(T::class.java, CoroutineDispatcher::class.java)) + .add(Dependency.required(Qualified.qualified(T::class.java, Executor::class.java))) + .factory { c -> + c.get(Qualified.qualified(T::class.java, Executor::class.java)).asCoroutineDispatcher() + } + .build() diff --git a/firebase-common/src/main/java/com/google/firebase/ktx/Firebase.kt b/firebase-common/src/main/java/com/google/firebase/ktx/Firebase.kt new file mode 100644 index 00000000000..8b3d7943433 --- /dev/null +++ b/firebase-common/src/main/java/com/google/firebase/ktx/Firebase.kt @@ -0,0 +1,87 @@ +// Copyright 2019 Google 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 +// +// 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.google.firebase.ktx + +import android.content.Context +import androidx.annotation.Keep +import com.google.firebase.FirebaseApp +import com.google.firebase.FirebaseOptions +import com.google.firebase.annotations.concurrent.Background +import com.google.firebase.annotations.concurrent.Blocking +import com.google.firebase.annotations.concurrent.Lightweight +import com.google.firebase.annotations.concurrent.UiThread +import com.google.firebase.components.Component +import com.google.firebase.components.ComponentRegistrar +import com.google.firebase.components.Dependency +import com.google.firebase.components.Qualified +import java.util.concurrent.Executor +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.asCoroutineDispatcher + +/** + * Single access point to all firebase SDKs from Kotlin. + * + *

Acts as a target for extension methods provided by sdks. + */ +@Deprecated("Use `com.google.firebase.Firebase`", ReplaceWith("com.google.firebase.Firebase")) +object Firebase + +/** Returns the default firebase app instance. */ +@Deprecated( + "Use `com.google.firebase.Firebase.app`", + ReplaceWith("com.google.firebase.Firebase.app") +) +val Firebase.app: FirebaseApp + get() = FirebaseApp.getInstance() + +/** Returns a named firebase app instance. */ +@Deprecated( + "Use `com.google.firebase.Firebase.app(name)`", + ReplaceWith("com.google.firebase.Firebase.app(name)") +) +fun Firebase.app(name: String): FirebaseApp = FirebaseApp.getInstance(name) + +/** Initializes and returns a FirebaseApp. */ +@Deprecated( + "Use `com.google.firebase.Firebase.initialize(context)`", + ReplaceWith("com.google.firebase.Firebase.initialize(context)") +) +fun Firebase.initialize(context: Context): FirebaseApp? = FirebaseApp.initializeApp(context) + +/** Initializes and returns a FirebaseApp. */ +@Deprecated( + "Use `com.google.firebase.Firebase.initialize(context, options)`", + ReplaceWith("com.google.firebase.Firebase.initialize(context, options)") +) +fun Firebase.initialize(context: Context, options: FirebaseOptions): FirebaseApp = + FirebaseApp.initializeApp(context, options) + +/** Initializes and returns a FirebaseApp. */ +@Deprecated( + "Use `com.google.firebase.Firebase.initialize(context, options, name)`", + ReplaceWith("com.google.firebase.Firebase.initialize(context, options, name)") +) +fun Firebase.initialize(context: Context, options: FirebaseOptions, name: String): FirebaseApp = + FirebaseApp.initializeApp(context, options, name) + +/** Returns options of default FirebaseApp */ +@Deprecated( + "Use `com.google.firebase.Firebase.options`", + ReplaceWith("com.google.firebase.Firebase.options") +) +val Firebase.options: FirebaseOptions + get() = Firebase.app.options + +internal const val LIBRARY_NAME: String = "fire-core-ktx" + diff --git a/firebase-common/src/test/java/com/google/firebase/Tests.kt b/firebase-common/src/test/java/com/google/firebase/Tests.kt new file mode 100644 index 00000000000..4a1357bb1d9 --- /dev/null +++ b/firebase-common/src/test/java/com/google/firebase/Tests.kt @@ -0,0 +1,139 @@ +// Copyright 2019 Google 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 +// +// 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.google.firebase + +import androidx.test.core.app.ApplicationProvider +import com.google.android.gms.tasks.Tasks +import com.google.common.truth.Truth.assertThat +import com.google.firebase.platforminfo.UserAgentPublisher +import kotlinx.coroutines.tasks.await +import kotlinx.coroutines.test.runTest +import org.junit.Assert.fail +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner + +fun withApp(name: String, block: FirebaseApp.() -> Unit) { + val app = + Firebase.initialize( + ApplicationProvider.getApplicationContext(), + FirebaseOptions.Builder().setApplicationId("appId").build(), + name + ) + try { + block(app) + } finally { + app.delete() + } +} + +class TestException(message: String) : Exception(message) + +@RunWith(RobolectricTestRunner::class) +class VersionTests { + @Test + fun libraryVersions_shouldBeRegisteredWithRuntime() { + withApp("ktxTestApp") { + val uaPublisher = get(UserAgentPublisher::class.java) + assertThat(uaPublisher.userAgent).contains("kotlin") + } + } +} + +@RunWith(RobolectricTestRunner::class) +class KtxTests { + @Test + fun `Firebase#app should delegate to FirebaseApp#getInstance()`() { + withApp(FirebaseApp.DEFAULT_APP_NAME) { + assertThat(Firebase.app).isSameInstanceAs(FirebaseApp.getInstance()) + } + } + + @Test + fun `Firebase#app(String) should delegate to FirebaseApp#getInstance(String)`() { + val appName = "testApp" + withApp(appName) { + assertThat(Firebase.app(appName)).isSameInstanceAs(FirebaseApp.getInstance(appName)) + } + } + + @Test + fun `Firebase#options should delegate to FirebaseApp#getInstance()#options`() { + withApp(FirebaseApp.DEFAULT_APP_NAME) { + assertThat(Firebase.options).isSameInstanceAs(FirebaseApp.getInstance().options) + } + } + + @Test + fun `Firebase#initialize(Context, FirebaseOptions) should initialize the app correctly`() { + val options = FirebaseOptions.Builder().setApplicationId("appId").build() + val app = Firebase.initialize(ApplicationProvider.getApplicationContext(), options) + try { + assertThat(app).isNotNull() + assertThat(app.name).isEqualTo(FirebaseApp.DEFAULT_APP_NAME) + assertThat(app.options).isSameInstanceAs(options) + assertThat(app.applicationContext) + .isSameInstanceAs(ApplicationProvider.getApplicationContext()) + } finally { + app.delete() + } + } + + @Test + fun `Firebase#initialize(Context, FirebaseOptions, String) should initialize the app correctly`() { + val options = FirebaseOptions.Builder().setApplicationId("appId").build() + val name = "appName" + val app = Firebase.initialize(ApplicationProvider.getApplicationContext(), options, name) + try { + assertThat(app).isNotNull() + assertThat(app.name).isEqualTo(name) + assertThat(app.options).isSameInstanceAs(options) + assertThat(app.applicationContext) + .isSameInstanceAs(ApplicationProvider.getApplicationContext()) + } finally { + app.delete() + } + } +} + +class CoroutinesPlayServicesTests { + // We are only interested in the await() function offered by kotlinx-coroutines-play-services + // So we're not testing the other functions provided by that library. + + @Test + fun `Task#await() resolves to the same result as Task#getResult()`() = runTest { + val task = Tasks.forResult(21) + + val expected = task.result + val actual = task.await() + + assertThat(actual).isEqualTo(expected) + assertThat(task.isSuccessful).isTrue() + assertThat(task.exception).isNull() + } + + @Test + fun `Task#await() throws an Exception for failing Tasks`() = runTest { + val task = Tasks.forException(TestException("some error happened")) + + try { + task.await() + fail("Task#await should throw an Exception") + } catch (e: Exception) { + assertThat(e).isInstanceOf(TestException::class.java) + assertThat(task.isSuccessful).isFalse() + } + } +} diff --git a/firebase-common/src/test/java/com/google/firebase/ktx/Tests.kt b/firebase-common/src/test/java/com/google/firebase/ktx/Tests.kt new file mode 100644 index 00000000000..eb49aa0ad09 --- /dev/null +++ b/firebase-common/src/test/java/com/google/firebase/ktx/Tests.kt @@ -0,0 +1,141 @@ +// Copyright 2019 Google 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 +// +// 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.google.firebase.ktx + +import androidx.test.core.app.ApplicationProvider +import com.google.android.gms.tasks.Tasks +import com.google.common.truth.Truth.assertThat +import com.google.firebase.FirebaseApp +import com.google.firebase.FirebaseOptions +import com.google.firebase.platforminfo.UserAgentPublisher +import kotlinx.coroutines.tasks.await +import kotlinx.coroutines.test.runTest +import org.junit.Assert.fail +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner + +fun withApp(name: String, block: FirebaseApp.() -> Unit) { + val app = + Firebase.initialize( + ApplicationProvider.getApplicationContext(), + FirebaseOptions.Builder().setApplicationId("appId").build(), + name + ) + try { + block(app) + } finally { + app.delete() + } +} + +class TestException(message: String) : Exception(message) + +@RunWith(RobolectricTestRunner::class) +class VersionTests { + @Test + fun libraryVersions_shouldBeRegisteredWithRuntime() { + withApp("ktxTestApp") { + val uaPublisher = get(UserAgentPublisher::class.java) + assertThat(uaPublisher.userAgent).contains("kotlin") + } + } +} + +@RunWith(RobolectricTestRunner::class) +class KtxTests { + @Test + fun `Firebase#app should delegate to FirebaseApp#getInstance()`() { + withApp(FirebaseApp.DEFAULT_APP_NAME) { + assertThat(Firebase.app).isSameInstanceAs(FirebaseApp.getInstance()) + } + } + + @Test + fun `Firebase#app(String) should delegate to FirebaseApp#getInstance(String)`() { + val appName = "testApp" + withApp(appName) { + assertThat(Firebase.app(appName)).isSameInstanceAs(FirebaseApp.getInstance(appName)) + } + } + + @Test + fun `Firebase#options should delegate to FirebaseApp#getInstance()#options`() { + withApp(FirebaseApp.DEFAULT_APP_NAME) { + assertThat(Firebase.options).isSameInstanceAs(FirebaseApp.getInstance().options) + } + } + + @Test + fun `Firebase#initialize(Context, FirebaseOptions) should initialize the app correctly`() { + val options = FirebaseOptions.Builder().setApplicationId("appId").build() + val app = Firebase.initialize(ApplicationProvider.getApplicationContext(), options) + try { + assertThat(app).isNotNull() + assertThat(app.name).isEqualTo(FirebaseApp.DEFAULT_APP_NAME) + assertThat(app.options).isSameInstanceAs(options) + assertThat(app.applicationContext) + .isSameInstanceAs(ApplicationProvider.getApplicationContext()) + } finally { + app.delete() + } + } + + @Test + fun `Firebase#initialize(Context, FirebaseOptions, String) should initialize the app correctly`() { + val options = FirebaseOptions.Builder().setApplicationId("appId").build() + val name = "appName" + val app = Firebase.initialize(ApplicationProvider.getApplicationContext(), options, name) + try { + assertThat(app).isNotNull() + assertThat(app.name).isEqualTo(name) + assertThat(app.options).isSameInstanceAs(options) + assertThat(app.applicationContext) + .isSameInstanceAs(ApplicationProvider.getApplicationContext()) + } finally { + app.delete() + } + } +} + +class CoroutinesPlayServicesTests { + // We are only interested in the await() function offered by kotlinx-coroutines-play-services + // So we're not testing the other functions provided by that library. + + @Test + fun `Task#await() resolves to the same result as Task#getResult()`() = runTest { + val task = Tasks.forResult(21) + + val expected = task.result + val actual = task.await() + + assertThat(actual).isEqualTo(expected) + assertThat(task.isSuccessful).isTrue() + assertThat(task.exception).isNull() + } + + @Test + fun `Task#await() throws an Exception for failing Tasks`() = runTest { + val task = Tasks.forException(TestException("some error happened")) + + try { + task.await() + fail("Task#await should throw an Exception") + } catch (e: Exception) { + assertThat(e).isInstanceOf(TestException::class.java) + assertThat(task.isSuccessful).isFalse() + } + } +} diff --git a/firebase-components/src/main/java/com/google/firebase/components/ComponentRuntime.java b/firebase-components/src/main/java/com/google/firebase/components/ComponentRuntime.java index a560cf5b502..15eead69d53 100644 --- a/firebase-components/src/main/java/com/google/firebase/components/ComponentRuntime.java +++ b/firebase-components/src/main/java/com/google/firebase/components/ComponentRuntime.java @@ -49,6 +49,7 @@ public class ComponentRuntime implements ComponentContainer, ComponentLoader { private final Map, Provider> lazyInstanceMap = new HashMap<>(); private final Map, LazySet> lazySetMap = new HashMap<>(); private final List> unprocessedRegistrarProviders; + private final List> processedComponents = new ArrayList<>(); private final EventBus eventBus; private final AtomicReference eagerComponentsInitializedWith = new AtomicReference<>(); private final ComponentRegistrarProcessor componentRegistrarProcessor; @@ -107,6 +108,8 @@ private void discoverComponents(List> componentsToAdd) { // instead of executing such code in the synchronized block below, we store it in a list and // execute right after the synchronized section. List runAfterDiscovery = new ArrayList<>(); + List> componentsAdding = new ArrayList<>(); + Set existingInterfaces = new HashSet<>(); synchronized (this) { Iterator> iterator = unprocessedRegistrarProviders.iterator(); while (iterator.hasNext()) { @@ -122,16 +125,41 @@ private void discoverComponents(List> componentsToAdd) { Log.w(ComponentDiscovery.TAG, "Invalid component registrar.", ex); } } + for (int i = 0; i < processedComponents.size(); i++) { + Object[] interfaces = processedComponents.get(i).getProvidedInterfaces().toArray(); + for (Object anInterface : interfaces) { + if (anInterface.toString().contains("kotlinx.coroutines.CoroutineDispatcher")) { + existingInterfaces.add(anInterface.toString()); + } + } + } + for (int i = 0; i < componentsToAdd.size(); i++) { + Object[] interfaces = componentsToAdd.get(i).getProvidedInterfaces().toArray(); + boolean addComponent = true; + for (Object anInterface : interfaces) { + String interfaceString = anInterface.toString(); + if (interfaceString.contains("kotlinx.coroutines.CoroutineDispatcher")) { + if (existingInterfaces.contains(interfaceString)) { + addComponent = false; + } else { + existingInterfaces.add(interfaceString); + } + } + } + if (addComponent) { + componentsAdding.add(componentsToAdd.get(i)); + } + } if (components.isEmpty()) { - CycleDetector.detect(componentsToAdd); + CycleDetector.detect(componentsAdding); } else { ArrayList> allComponents = new ArrayList<>(this.components.keySet()); - allComponents.addAll(componentsToAdd); + allComponents.addAll(componentsAdding); CycleDetector.detect(allComponents); } - for (Component component : componentsToAdd) { + for (Component component : componentsAdding) { Lazy lazy = new Lazy<>( () -> @@ -142,7 +170,8 @@ private void discoverComponents(List> componentsToAdd) { components.put(component, lazy); } - runAfterDiscovery.addAll(processInstanceComponents(componentsToAdd)); + runAfterDiscovery.addAll(processInstanceComponents(componentsAdding)); + processedComponents.addAll(componentsAdding); runAfterDiscovery.addAll(processSetComponents()); processDependencies(); } diff --git a/firebase-crashlytics/src/main/AndroidManifest.xml b/firebase-crashlytics/src/main/AndroidManifest.xml index ef10ac95cec..b14a7ff1107 100644 --- a/firebase-crashlytics/src/main/AndroidManifest.xml +++ b/firebase-crashlytics/src/main/AndroidManifest.xml @@ -6,10 +6,10 @@ - + - + \ No newline at end of file diff --git a/firebase-messaging/firebase-messaging.gradle b/firebase-messaging/firebase-messaging.gradle index eaaa4445f8a..a6f7753c09b 100644 --- a/firebase-messaging/firebase-messaging.gradle +++ b/firebase-messaging/firebase-messaging.gradle @@ -13,25 +13,26 @@ // limitations under the License. plugins { - id 'firebase-library' id 'com.google.protobuf' -} - -configurations.create("protobuild") -dependencies { - protobuild project(path: ":encoders:protoc-gen-firebase-encoders", configuration: "shadow") + id 'firebase-library' + id 'kotlin-android' } protobuf { + dependencies { + // Include the project dependency directly + implementation project(':encoders:protoc-gen-firebase-encoders') + } protoc { artifact = "com.google.protobuf:protoc:$protocVersion" } plugins { firebaseEncoders { - path = configurations.protobuild.asPath + path = project(':encoders:protoc-gen-firebase-encoders').buildDir.path + '/libs' +'/protoc-gen-firebase-encoders-all.jar' } } generateProtoTasks { + all().each { task -> task.dependsOn configurations.protobuild task.inputs.file 'code_gen_cfg.textproto' @@ -42,7 +43,9 @@ protobuf { } task.builtins { remove java + remove kotlin } + } } } @@ -75,67 +78,69 @@ android { } dependencies { - implementation 'com.google.firebase:firebase-common:20.3.1' - implementation 'com.google.firebase:firebase-components:17.1.0' - implementation 'com.google.firebase:firebase-installations-interop:17.1.0' - implementation 'com.google.firebase:firebase-datatransport:18.1.7' - implementation 'com.google.android.datatransport:transport-api:3.0.0' - implementation 'com.google.android.datatransport:transport-runtime:3.1.8' - implementation 'com.google.android.datatransport:transport-backend-cct:3.1.8' - implementation 'com.google.firebase:firebase-encoders:17.0.0' - implementation 'com.google.firebase:firebase-encoders-json:18.0.0' - implementation "com.google.firebase:firebase-encoders-proto:16.0.0" - implementation "com.google.firebase:firebase-installations:17.1.3" + androidTestImplementation "androidx.annotation:annotation:1.0.0" + androidTestImplementation "androidx.test.ext:junit:$androidxTestJUnitVersion" + androidTestImplementation "com.google.truth:truth:$googleTruthVersion" + androidTestImplementation 'androidx.test:runner:1.2.0' + androidTestImplementation 'junit:junit:4.12' + androidTestImplementation 'org.mockito:mockito-core:2.25.0' + androidTestImplementation 'org.mockito:mockito-inline:2.25.0' + androidTestImplementation project(':integ-testing') annotationProcessor project(":encoders:firebase-encoders-processor") - - implementation 'androidx.annotation:annotation:1.2.0' - implementation "com.google.android.gms:play-services-tasks:18.0.1" implementation "com.google.android.gms:play-services-basement:18.1.0" - implementation 'com.google.android.gms:play-services-base:18.0.1' - implementation 'com.google.android.gms:play-services-stats:17.0.2' - implementation 'com.google.firebase:firebase-measurement-connector:19.0.0' + implementation "com.google.android.gms:play-services-tasks:18.0.1" + implementation "com.google.errorprone:error_prone_annotations:2.9.0" + implementation "com.google.firebase:firebase-encoders-proto:16.0.0" implementation "com.google.firebase:firebase-iid-interop:17.1.0" + implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion" + implementation 'androidx.annotation:annotation:1.2.0' + implementation 'com.google.android.datatransport:transport-api:3.0.0' + implementation 'com.google.android.datatransport:transport-backend-cct:3.1.8' + implementation 'com.google.android.datatransport:transport-runtime:3.1.8' + implementation 'com.google.android.gms:play-services-base:18.0.1' implementation 'com.google.android.gms:play-services-cloud-messaging:17.0.1' - implementation "com.google.errorprone:error_prone_annotations:2.9.0" - testImplementation ("com.google.api-client:google-api-client:1.30.9") { - exclude group: "org.apache.httpcomponents", module: "httpclient" + implementation 'com.google.android.gms:play-services-stats:17.0.2' + implementation('com.google.firebase:firebase-datatransport:18.1.7') { + exclude group: 'com.google.firebase', module: 'firebase-common' + exclude group: 'com.google.firebase', module: 'firebase-components' + } + implementation 'com.google.firebase:firebase-encoders-json:18.0.0' + implementation 'com.google.firebase:firebase-encoders:17.0.0' + implementation('com.google.firebase:firebase-installations-interop:17.1.0') + implementation('com.google.firebase:firebase-measurement-connector:19.0.0') + implementation(project(":firebase-common")) + implementation(project(":firebase-components")) + implementation(project(":firebase-installations")) { + exclude group: 'com.google.firebase', module: 'firebase-common' + exclude group: 'com.google.firebase', module: 'firebase-components' } - testCompileOnly 'com.google.auto.value:auto-value-annotations:1.6.3' - testAnnotationProcessor "com.google.auto.value:auto-value:1.6.3" - javadocClasspath 'com.google.auto.value:auto-value-annotations:1.6.6' - + testAnnotationProcessor "com.google.auto.value:auto-value:1.6.3" + testCompileOnly 'com.google.auto.value:auto-value-annotations:1.6.3' testImplementation "androidx.test:core:$androidxTestCoreVersion" - testImplementation 'com.google.android.gms:play-services-cloud-messaging:17.0.1' + testImplementation "com.google.truth:truth:$googleTruthVersion" + testImplementation "org.robolectric:robolectric:$robolectricVersion" + testImplementation 'androidx.core:core:1.6.0' + testImplementation 'androidx.test.espresso:espresso-intents:3.2.0' + testImplementation 'androidx.test.ext:truth:1.4.0' + testImplementation 'androidx.test.services:test-services:1.2.0' testImplementation 'androidx.test:rules:1.2.0' testImplementation 'androidx.test:runner:1.2.0' - testImplementation "org.robolectric:robolectric:$robolectricVersion" + testImplementation 'com.android.support.test:runner:1.0.2' + testImplementation 'com.fasterxml.jackson.core:jackson-databind:2.9.8' + testImplementation 'com.google.android.gms:play-services-cloud-messaging:17.0.1' + testImplementation 'com.google.android.gms:play-services-vision:20.1.3' + testImplementation 'com.google.guava:guava-testlib:12.0-rc2' + testImplementation 'junit:junit:4.12' testImplementation 'junit:junit:4.13-beta-2' testImplementation 'org.mockito:mockito-core:2.25.0' - testImplementation "com.google.truth:truth:$googleTruthVersion" - testImplementation 'com.google.android.gms:play-services-vision:20.1.3' + testImplementation ("com.google.api-client:google-api-client:1.30.9") { + exclude group: "org.apache.httpcomponents", module: "httpclient" + } testImplementation ("com.google.firebase:firebase-iid:21.1.0") { exclude group: "com.google.firebase", module: "firebase-common" exclude group: "com.google.firebase", module: "firebase-components" exclude group: "com.google.firebase", module: "firebase-installations-interop" exclude group: "com.google.firebase", module: "firebase-installations" } - - testImplementation 'com.android.support.test:runner:1.0.2' - testImplementation 'com.fasterxml.jackson.core:jackson-databind:2.9.8' - testImplementation 'com.google.guava:guava-testlib:12.0-rc2' - testImplementation 'androidx.test.espresso:espresso-intents:3.2.0' - testImplementation 'androidx.test:rules:1.2.0' - testImplementation 'androidx.test.ext:truth:1.4.0' - testImplementation 'androidx.test.services:test-services:1.2.0' - testImplementation 'androidx.core:core:1.6.0' - - androidTestImplementation project(':integ-testing') - androidTestImplementation "androidx.test.ext:junit:$androidxTestJUnitVersion" - androidTestImplementation 'androidx.test:runner:1.2.0' - androidTestImplementation "com.google.truth:truth:$googleTruthVersion" - androidTestImplementation 'junit:junit:4.12' - androidTestImplementation "androidx.annotation:annotation:1.0.0" - androidTestImplementation 'org.mockito:mockito-core:2.25.0' - androidTestImplementation 'org.mockito:mockito-inline:2.25.0' -} +} \ No newline at end of file