diff --git a/README.md b/README.md index f1c0e60..588e1b2 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ plugins { } ``` -add your firebase credentials to the rootproject as `ftl-credentials.json` +add your firebase credentials to the rootproject as `ftl-credentials.json` or authenticate with your Google Account with `./gradlew flankAuth` That's it, run `./gradlew flankRun` and get the results. diff --git a/src/main/kotlin/io/github/flank/gradle/simple-flank.gradle.kts b/src/main/kotlin/io/github/flank/gradle/simple-flank.gradle.kts index 5affe71..f55dfb8 100644 --- a/src/main/kotlin/io/github/flank/gradle/simple-flank.gradle.kts +++ b/src/main/kotlin/io/github/flank/gradle/simple-flank.gradle.kts @@ -49,6 +49,7 @@ plugins.withType { } tasks.register("flankVersion") { flankJarClasspath.from(flankExecutable) } registerRunFlankTask() + registerAuthTask() } plugins.withType { @@ -73,6 +74,7 @@ plugins.withType { } tasks.register("flankVersion") { flankJarClasspath.from(flankExecutable) } registerRunFlankTask() + registerAuthTask() } fun registerFlankYamlWriter( @@ -127,6 +129,7 @@ fun registerFlankRun( flankJarClasspath.from(flankExecutable) serviceAccountCredentials.convention(simpleFlankExtension.credentialsFile) + flankAuthDirectory.set(File(System.getProperty("user.home")).resolve(".flank")) this@register.variant.convention(variant.name) hermeticTests.convention(simpleFlankExtension.hermeticTests) this.appApk.convention(appApk) @@ -173,3 +176,7 @@ fun registerRunFlankTask() { dependsOn(tasks.withType()) } } + +fun registerAuthTask() { + tasks.register("flankAuth") { flankJarClasspath.from(flankExecutable) } +} diff --git a/src/main/kotlin/io/github/flank/gradle/tasks/FlankAuthTask.kt b/src/main/kotlin/io/github/flank/gradle/tasks/FlankAuthTask.kt new file mode 100644 index 0000000..5150fe2 --- /dev/null +++ b/src/main/kotlin/io/github/flank/gradle/tasks/FlankAuthTask.kt @@ -0,0 +1,31 @@ +package io.github.flank.gradle.tasks + +import javax.inject.Inject +import org.gradle.api.DefaultTask +import org.gradle.api.file.ConfigurableFileCollection +import org.gradle.api.tasks.Classpath +import org.gradle.api.tasks.InputFiles +import org.gradle.api.tasks.TaskAction +import org.gradle.process.ExecOperations + +abstract class FlankAuthTask @Inject constructor(private val execOperations: ExecOperations) : + DefaultTask() { + @get:InputFiles @get:Classpath abstract val flankJarClasspath: ConfigurableFileCollection + + init { + group = "flank" + description = + "Performs the authentication with a Google Account. https://flank.github.io/flank/#authenticate-with-a-google-account" + } + + @TaskAction + fun run() { + execOperations + .javaexec { + classpath = flankJarClasspath + mainClass.set("ftl.Main") + args = listOf("auth", "login") + } + .assertNormalExitValue() + } +} diff --git a/src/main/kotlin/io/github/flank/gradle/tasks/FlankRunTask.kt b/src/main/kotlin/io/github/flank/gradle/tasks/FlankRunTask.kt index 47401c4..bb1df78 100644 --- a/src/main/kotlin/io/github/flank/gradle/tasks/FlankRunTask.kt +++ b/src/main/kotlin/io/github/flank/gradle/tasks/FlankRunTask.kt @@ -26,10 +26,14 @@ constructor( @get:Input val dry: Property = objectFactory.property(Boolean::class.java).convention(false) - @get:InputFile + @get:InputFiles @get:PathSensitive(PathSensitivity.NONE) abstract val serviceAccountCredentials: RegularFileProperty + @get:InputFiles + @get:PathSensitive(PathSensitivity.ABSOLUTE) + abstract val flankAuthDirectory: DirectoryProperty + @get:Input abstract val variant: Property @get:InputFiles @get:Classpath abstract val flankJarClasspath: ConfigurableFileCollection @@ -62,9 +66,7 @@ constructor( @TaskAction fun run() { - check(serviceAccountCredentials.get().asFile.exists()) { - "serviceAccountCredential file doesn't exist ${serviceAccountCredentials.get()}" - } + checkAuthentication() getOutputDir().get().asFile.deleteRecursively() execOperations @@ -72,7 +74,9 @@ constructor( isIgnoreExitValue = true classpath = flankJarClasspath mainClass.set("ftl.Main") - environment(mapOf("GOOGLE_APPLICATION_CREDENTIALS" to serviceAccountCredentials.get())) + serviceAccountCredentials.get().takeIf { it.asFile.exists() }?.let { credentialsFile -> + environment(mapOf("GOOGLE_APPLICATION_CREDENTIALS" to credentialsFile)) + } args = listOf("firebase", "test", "android", "run", "-c=${flankYaml.get()}") if (dumpShards.get()) args("--dump-shards") if (dry.get()) args("--dry") @@ -98,6 +102,20 @@ constructor( } } + private fun checkAuthentication() { + val isCredentialsFileProvided = serviceAccountCredentials.get().asFile.exists() + val isFlankAuthenticationProvided = flankAuthDirectory.get().asFile.exists() + check(isCredentialsFileProvided || isFlankAuthenticationProvided) { + """ + Either a service account credential file should be provided or the flank authentication performed. + You can: + - Declare the service account credentials in the ftl-credentials.json file on your rootProject + - Declare the service account credentials in a custom path via the simple flank's extension simpleFlank { credentialsFile = "path/to/file.json" } + - Perform the authentication with a Google Account via "./gradlew flankAuth" + """.trimIndent() + } + } + companion object { const val NO_TESTS_EXIT_CODE = 3 } diff --git a/src/test/kotlin/CredentialsTest.kt b/src/test/kotlin/CredentialsTest.kt new file mode 100644 index 0000000..1d866c9 --- /dev/null +++ b/src/test/kotlin/CredentialsTest.kt @@ -0,0 +1,86 @@ +import java.io.File +import org.gradle.testkit.runner.TaskOutcome +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TemporaryFolder +import strikt.api.expectThat +import strikt.assertions.contains +import strikt.gradle.testkit.output +import strikt.gradle.testkit.taskPaths + +class CredentialsTest : GradleTest() { + @get:Rule val userHomeDirectory = TemporaryFolder() + + @Test + fun `When no service account or flank credentials are provided, build fails`() { + projectFromResources("app") + File(testProjectDir.root, "ftl-credentials.json").deleteRecursively() + File(testProjectDir.root, "build.gradle.kts") + .appendText("simpleFlank { projectId.set(\"my-project\") }") + + val build = gradleRunner("flankRun", "--stacktrace").forwardOutput().buildAndFail() + + expectThat(build) { + output.contains( + "Either a service account credential file should be provided or the flank authentication performed.") + } + expectThat(build) { taskPaths(TaskOutcome.FAILED).contains(":flankRunDebug") } + } + + @Test + fun `When service account is provided using the default file, build is successful`() { + projectFromResources("app") + + val build = gradleRunner("flankRun", "--stacktrace").forwardOutput().build() + + expectThat(build) { + output + .not() + .contains( + "Either a service account credential file should be provided or the flank authentication performed.") + taskPaths(TaskOutcome.SUCCESS).contains(":flankRunDebug") + } + } + + @Test + fun `When service account is provided using the a custom file, build is successful`() { + projectFromResources("app") + + val build = gradleRunner("flankRun", "--stacktrace").forwardOutput().build() + File(testProjectDir.root, "ftl-credentials.json").deleteRecursively() + File(testProjectDir.root, "custom-credentials.json") + .appendText("{ \"project_id\": \"custom-project-id\" }") + File(testProjectDir.root, "build.gradle.kts") + .appendText("simpleFlank { credentialsFile.set(file(\"custom-credentials.json\")) }") + + expectThat(build) { + output + .not() + .contains( + "Either a service account credential file should be provided or the flank authentication performed.") + taskPaths(TaskOutcome.SUCCESS).contains(":flankRunDebug") + } + } + + @Test + fun `When flank Google Account authentication is provided, build is successful`() { + projectFromResources("app") + File(testProjectDir.root, "ftl-credentials.json").deleteRecursively() + File(testProjectDir.root, "build.gradle.kts") + .appendText("simpleFlank { projectId.set(\"my-project\") }") + userHomeDirectory.root.resolve(".flank").mkdir() + + val build = + gradleRunner("flankRun", "-Duser.home=${userHomeDirectory.root.path}", "--stacktrace") + .forwardOutput() + .build() + + expectThat(build) { + output + .not() + .contains( + "Either a service account credential file should be provided or the flank authentication performed.") + taskPaths(TaskOutcome.SUCCESS).contains(":flankRunDebug") + } + } +}