app.cash.paparazzi.gradle.PaparazziPlugin.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of paparazzi-gradle-plugin Show documentation
Show all versions of paparazzi-gradle-plugin Show documentation
A Gradle plugin to set up the Paparazzi test library
/*
* Copyright (C) 2019 Square, Inc.
*
* 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 app.cash.paparazzi.gradle
import app.cash.paparazzi.NATIVE_LIB_VERSION
import app.cash.paparazzi.VERSION
import com.android.build.gradle.BaseExtension
import com.android.build.gradle.LibraryExtension
import com.android.build.gradle.tasks.MergeSourceSetFolders
import com.android.ide.common.symbols.getPackageNameFromManifest
import org.gradle.api.Action
import org.gradle.api.DefaultTask
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.type.ArtifactTypeDefinition
import org.gradle.api.attributes.Attribute
import org.gradle.api.internal.artifacts.ArtifactAttributes
import org.gradle.api.internal.artifacts.transform.UnzipTransform
import org.gradle.api.logging.LogLevel.LIFECYCLE
import org.gradle.api.plugins.JavaBasePlugin
import org.gradle.api.tasks.TaskProvider
import org.gradle.api.tasks.options.Option
import org.gradle.api.tasks.testing.Test
import org.gradle.internal.os.OperatingSystem
import org.gradle.language.base.plugins.LifecycleBasePlugin.VERIFICATION_GROUP
import org.jetbrains.kotlin.gradle.plugin.KotlinBasePluginWrapper
import java.util.Locale
@Suppress("unused")
class PaparazziPlugin : Plugin {
@OptIn(ExperimentalStdlibApi::class)
override fun apply(project: Project) {
project.afterEvaluate {
require(project.plugins.hasPlugin("com.android.library")) {
"The Android Gradle library plugin must be applied for Paparazzi to be configured."
}
}
project.plugins.withId("com.android.library") { setupPaparazzi(project) }
}
private fun setupPaparazzi(project: Project) {
val unzipConfiguration = project.setupPlatformDataTransform()
// Create anchor tasks for all variants.
val verifyVariants = project.tasks.register("verifyPaparazzi")
val recordVariants = project.tasks.register("recordPaparazzi")
val variants = project.extensions.getByType(LibraryExtension::class.java)
.libraryVariants
variants.all { variant ->
val variantSlug = variant.name.capitalize(Locale.US)
val mergeResourcesOutputDir = variant.mergeResourcesProvider.flatMap { it.outputDir }
val mergeAssetsProvider =
project.tasks.named("merge${variantSlug}Assets") as TaskProvider
val mergeAssetsOutputDir = mergeAssetsProvider.flatMap { it.outputDir }
val reportOutputDir = project.layout.buildDirectory.dir("reports/paparazzi")
val snapshotOutputDir = project.layout.projectDirectory.dir("src/test/snapshots")
val packageAwareArtifacts = project.configurations.getByName("${variant.name}RuntimeClasspath")
.incoming
.artifactView {
it.attributes.attribute(
Attribute.of("artifactType", String::class.java), "android-symbol-with-package-name"
)
}
.artifacts
val writeResourcesTask = project.tasks.register(
"preparePaparazzi${variantSlug}Resources", PrepareResourcesTask::class.java
) { task ->
val android = project.extensions.getByType(BaseExtension::class.java)
val nonTransitiveRClassEnabled =
(project.findProperty("android.nonTransitiveRClass") as? String).toBoolean()
task.packageName.set(android.packageName())
task.artifactFiles.from(packageAwareArtifacts.artifactFiles)
task.nonTransitiveRClassEnabled.set(nonTransitiveRClassEnabled)
task.mergeResourcesOutput.set(mergeResourcesOutputDir)
task.targetSdkVersion.set(android.targetSdkVersion())
task.compileSdkVersion.set(android.compileSdkVersion())
task.mergeAssetsOutput.set(mergeAssetsOutputDir)
task.platformDataRoot.set(unzipConfiguration.singleFile)
task.paparazziResources.set(project.layout.buildDirectory.file("intermediates/paparazzi/${variant.name}/resources.txt"))
}
val testVariantSlug = variant.unitTestVariant.name.capitalize(Locale.US)
project.plugins.withType(JavaBasePlugin::class.java) {
project.tasks.named("compile${testVariantSlug}JavaWithJavac")
.configure { it.dependsOn(writeResourcesTask) }
}
project.plugins.withType(KotlinBasePluginWrapper::class.java) {
project.tasks.named("compile${testVariantSlug}Kotlin")
.configure { it.dependsOn(writeResourcesTask) }
}
val recordTaskProvider = project.tasks.register("recordPaparazzi${variantSlug}", PaparazziTask::class.java) {
it.group = VERIFICATION_GROUP
}
recordVariants.configure { it.dependsOn(recordTaskProvider) }
val verifyTaskProvider = project.tasks.register("verifyPaparazzi${variantSlug}", PaparazziTask::class.java) {
it.group = VERIFICATION_GROUP
}
verifyVariants.configure { it.dependsOn(verifyTaskProvider) }
val isRecordRun = project.objects.property(Boolean::class.java)
val isVerifyRun = project.objects.property(Boolean::class.java)
project.gradle.taskGraph.whenReady { graph ->
isRecordRun.set(graph.hasTask(recordTaskProvider.get()))
isVerifyRun.set(graph.hasTask(verifyTaskProvider.get()))
}
val testTaskProvider = project.tasks.named("test${testVariantSlug}", Test::class.java) { test ->
test.systemProperties["paparazzi.test.resources"] =
writeResourcesTask.flatMap { it.paparazziResources.asFile }.get().path
test.inputs.dir(mergeResourcesOutputDir)
test.inputs.dir(mergeAssetsOutputDir)
test.outputs.dir(reportOutputDir)
test.outputs.dir(snapshotOutputDir)
val paparazziProperties = project.properties.filterKeys { it.startsWith("app.cash.paparazzi") }
@Suppress("ObjectLiteralToLambda")
// why not a lambda? See: https://docs.gradle.org/7.2/userguide/validation_problems.html#implementation_unknown
test.doFirst(object : Action {
override fun execute(t: Task) {
test.systemProperties["paparazzi.test.record"] = isRecordRun.get()
test.systemProperties["paparazzi.test.verify"] = isVerifyRun.get()
test.systemProperties.putAll(paparazziProperties)
}
})
}
recordTaskProvider.configure { it.dependsOn(testTaskProvider) }
verifyTaskProvider.configure { it.dependsOn(testTaskProvider) }
testTaskProvider.configure { test ->
@Suppress("ObjectLiteralToLambda")
// why not a lambda? See: https://docs.gradle.org/7.2/userguide/validation_problems.html#implementation_unknown
test.doLast(object : Action {
override fun execute(t: Task) {
val uri = reportOutputDir.get().asFile.toPath().resolve("index.html").toUri()
test.logger.log(LIFECYCLE, "See the Paparazzi report at: $uri")
}
})
}
}
}
open class PaparazziTask : DefaultTask() {
@Option(option = "tests", description = "Sets test class or method name to be included, '*' is supported.")
open fun setTestNameIncludePatterns(testNamePattern: List): PaparazziTask {
project.tasks.withType(Test::class.java).configureEach {
it.setTestNameIncludePatterns(testNamePattern)
}
return this
}
}
private fun Project.setupPlatformDataTransform(): Configuration {
configurations.getByName("testImplementation").dependencies.add(
dependencies.create("app.cash.paparazzi:paparazzi:$VERSION")
)
val unzipConfiguration = configurations.create("unzip")
unzipConfiguration.attributes.attribute(
ArtifactAttributes.ARTIFACT_FORMAT, ArtifactTypeDefinition.DIRECTORY_TYPE
)
configurations.add(unzipConfiguration)
val operatingSystem = OperatingSystem.current()
val nativeLibraryArtifactId = when {
operatingSystem.isMacOsX -> {
val osArch = System.getProperty("os.arch").lowercase(Locale.US)
if (osArch.startsWith("x86")) "macosx" else "macarm"
}
operatingSystem.isWindows -> "win"
else -> "linux"
}
unzipConfiguration.dependencies.add(
dependencies.create("app.cash.paparazzi:layoutlib-native-$nativeLibraryArtifactId:$NATIVE_LIB_VERSION")
)
dependencies.registerTransform(UnzipTransform::class.java) { transform ->
transform.from.attribute(ArtifactAttributes.ARTIFACT_FORMAT, ArtifactTypeDefinition.JAR_TYPE)
transform.to.attribute(
ArtifactAttributes.ARTIFACT_FORMAT, ArtifactTypeDefinition.DIRECTORY_TYPE
)
}
return unzipConfiguration
}
private fun BaseExtension.packageName(): String {
sourceSets
.map { it.manifest.srcFile }
.filter { it.exists() }
.forEach {
return getPackageNameFromManifest(it)
}
throw IllegalStateException("No source sets available")
}
private fun BaseExtension.compileSdkVersion(): String {
return compileSdkVersion!!.substringAfter("android-", DEFAULT_COMPILE_SDK_VERSION.toString())
}
private fun BaseExtension.targetSdkVersion(): String {
return defaultConfig.targetSdkVersion?.apiLevel?.toString()
?: DEFAULT_COMPILE_SDK_VERSION.toString()
}
}
private const val DEFAULT_COMPILE_SDK_VERSION = 30
© 2015 - 2024 Weber Informatics LLC | Privacy Policy