From 1697a2aa8428f3911b7e4fdc54873b61aac41447 Mon Sep 17 00:00:00 2001 From: 5peak2me <461260911@qq.com> Date: Fri, 24 Jan 2025 18:02:00 +0800 Subject: [PATCH] Support configuration-cache feature --- gradle.properties | 8 +- .../common/dependency/DependencyEntry.kt | 8 +- .../com/spotify/ruler/plugin/RulerPlugin.kt | 8 + .../com/spotify/ruler/plugin/RulerTask.kt | 162 ++++++++++++------ .../ruler/plugin/dependency/EntryParser.kt | 85 +++++---- 5 files changed, 181 insertions(+), 90 deletions(-) diff --git a/gradle.properties b/gradle.properties index 887b65f9..ad70ed00 100644 --- a/gradle.properties +++ b/gradle.properties @@ -18,5 +18,11 @@ org.gradle.jvmargs=-Xmx4608m -Dfile.encoding=UTF-8 kotlin.code.style=official kotlin.mpp.stability.nowarn=true + android.useAndroidX=true -android.experimental.legacyTransform.forceNonIncremental=true \ No newline at end of file +android.experimental.legacyTransform.forceNonIncremental=true + +org.gradle.configuration-cache=true +org.gradle.configuration-cache.max-problems=5 +org.gradle.configuration-cache.parallel=true +org.gradle.configuration-cache.problems=warn diff --git a/ruler-common/src/main/java/com/spotify/ruler/common/dependency/DependencyEntry.kt b/ruler-common/src/main/java/com/spotify/ruler/common/dependency/DependencyEntry.kt index d98b1e5a..f7bda4ec 100644 --- a/ruler-common/src/main/java/com/spotify/ruler/common/dependency/DependencyEntry.kt +++ b/ruler-common/src/main/java/com/spotify/ruler/common/dependency/DependencyEntry.kt @@ -16,18 +16,24 @@ package com.spotify.ruler.common.dependency +import java.io.Serializable +import kotlinx.serialization.Serializable as KSerializable + /** Single entry of a dependency. */ -sealed class DependencyEntry { +@KSerializable +sealed class DependencyEntry: Serializable { abstract val name: String abstract val component: String /** Default dependency entry. If an entry has no special type, it is considered to be a default entry. */ + @KSerializable data class Default( override val name: String, override val component: String, ) : DependencyEntry() /** Class file dependency entry. */ + @KSerializable data class Class( override val name: String, override val component: String, diff --git a/ruler-gradle-plugin/src/main/kotlin/com/spotify/ruler/plugin/RulerPlugin.kt b/ruler-gradle-plugin/src/main/kotlin/com/spotify/ruler/plugin/RulerPlugin.kt index 891af588..855a82e7 100644 --- a/ruler-gradle-plugin/src/main/kotlin/com/spotify/ruler/plugin/RulerPlugin.kt +++ b/ruler-gradle-plugin/src/main/kotlin/com/spotify/ruler/plugin/RulerPlugin.kt @@ -21,6 +21,7 @@ import com.android.build.api.variant.ApplicationVariant import com.spotify.ruler.common.models.AppInfo import com.spotify.ruler.common.models.DeviceSpec import com.spotify.ruler.common.veritication.VerificationConfig +import com.spotify.ruler.plugin.dependency.EntryParser import org.codehaus.groovy.runtime.StringGroovyMethods import org.gradle.api.Plugin import org.gradle.api.Project @@ -47,6 +48,13 @@ class RulerPlugin : Plugin { RulerTask::class.java ) { task -> task.group = name + + val resolve = EntryParser().parse(variant.runtimeConfiguration) + task.dependencies.set(resolve) + + task.projectPath.set(project.path) + task.sdkDirectory.set(androidComponents.sdkComponents.sdkDirectory) + task.appInfo.set(getAppInfo(project, variant)) task.deviceSpec.set(getDeviceSpec(rulerExtension)) diff --git a/ruler-gradle-plugin/src/main/kotlin/com/spotify/ruler/plugin/RulerTask.kt b/ruler-gradle-plugin/src/main/kotlin/com/spotify/ruler/plugin/RulerTask.kt index b80ae6f9..f73d1326 100644 --- a/ruler-gradle-plugin/src/main/kotlin/com/spotify/ruler/plugin/RulerTask.kt +++ b/ruler-gradle-plugin/src/main/kotlin/com/spotify/ruler/plugin/RulerTask.kt @@ -16,32 +16,42 @@ package com.spotify.ruler.plugin -import com.android.build.gradle.BaseExtension import com.spotify.ruler.common.BaseRulerTask import com.spotify.ruler.common.apk.ApkCreator import com.spotify.ruler.common.dependency.DependencyComponent +import com.spotify.ruler.common.dependency.DependencyEntry import com.spotify.ruler.common.dependency.DependencySanitizer import com.spotify.ruler.common.models.AppInfo import com.spotify.ruler.common.models.DeviceSpec import com.spotify.ruler.common.models.RulerConfig import com.spotify.ruler.common.sanitizer.ClassNameSanitizer import com.spotify.ruler.common.veritication.VerificationConfig -import com.spotify.ruler.plugin.dependency.EntryParser import org.gradle.api.DefaultTask import org.gradle.api.file.DirectoryProperty import org.gradle.api.file.RegularFile import org.gradle.api.file.RegularFileProperty import org.gradle.api.provider.ListProperty import org.gradle.api.provider.Property -import org.gradle.api.tasks.Input -import org.gradle.api.tasks.InputFile -import org.gradle.api.tasks.InputFiles -import org.gradle.api.tasks.Optional -import org.gradle.api.tasks.OutputDirectory -import org.gradle.api.tasks.TaskAction +import org.gradle.api.provider.Provider +import org.gradle.api.tasks.* +import org.gradle.workers.WorkAction +import org.gradle.workers.WorkParameters +import org.gradle.workers.WorkerExecutor import java.io.File +import javax.inject.Inject -abstract class RulerTask : DefaultTask(), BaseRulerTask { +@CacheableTask +abstract class RulerTask : DefaultTask() { + + @get:Input + abstract val dependencies: ListProperty>> + + @get:Input + abstract val projectPath: Property + + @get:InputDirectory + @get:PathSensitive(PathSensitivity.RELATIVE) + abstract val sdkDirectory: DirectoryProperty @get:Input abstract val appInfo: Property @@ -50,18 +60,22 @@ abstract class RulerTask : DefaultTask(), BaseRulerTask { abstract val deviceSpec: Property @get:InputFile + @get:PathSensitive(PathSensitivity.NONE) abstract val bundleFile: RegularFileProperty @get:Optional @get:InputFile + @get:PathSensitive(PathSensitivity.RELATIVE) abstract val mappingFile: RegularFileProperty @get:Optional @get:InputFile + @get:PathSensitive(PathSensitivity.RELATIVE) abstract val resourceMappingFile: RegularFileProperty @get:Optional @get:InputFile + @get:PathSensitive(PathSensitivity.RELATIVE) abstract val ownershipFile: RegularFileProperty @get:Input @@ -72,10 +86,12 @@ abstract class RulerTask : DefaultTask(), BaseRulerTask { @get:Optional @get:InputFiles + @get:PathSensitive(PathSensitivity.RELATIVE) abstract val unstrippedNativeFiles: ListProperty @get:Optional @get:InputFile + @get:PathSensitive(PathSensitivity.RELATIVE) abstract val staticDependenciesFile: RegularFileProperty @get:Input @@ -87,61 +103,103 @@ abstract class RulerTask : DefaultTask(), BaseRulerTask { @get:OutputDirectory abstract val reportDir: DirectoryProperty + @get:Inject + abstract val workerExecutor: WorkerExecutor + @TaskAction fun analyze() { - run() + workerExecutor.noIsolation().submit(RulerTaskAction::class.java) { + it.dependencies.set(dependencies) + it.projectPath.set(projectPath) + it.sdkDirectory.set(sdkDirectory) + it.appInfo.set(appInfo) + it.deviceSpec.set(deviceSpec) + it.bundleFile.set(bundleFile) + it.mappingFile.set(mappingFile) + it.resourceMappingFile.set(resourceMappingFile) + it.ownershipFile.set(ownershipFile) + it.defaultOwner.set(defaultOwner) + it.omitFileBreakdown.set(omitFileBreakdown) + it.unstrippedNativeFiles.set(unstrippedNativeFiles) + it.staticDependenciesFile.set(staticDependenciesFile) + it.verificationConfig.set(verificationConfig) + it.workingDir.set(workingDir) + it.reportDir.set(reportDir) + } } - private val config by lazy { - RulerConfig( - projectPath = project.path, - apkFilesMap = createApkFile(), - reportDir = reportDir.asFile.get(), - ownershipFile = ownershipFile.asFile.orNull, - staticDependenciesFile = staticDependenciesFile.asFile.orNull, - appInfo = appInfo.get(), - deviceSpec = deviceSpec.get(), - defaultOwner = defaultOwner.get(), - omitFileBreakdown = omitFileBreakdown.get(), - additionalEntries = emptyList(), - ignoredFiles = emptyList(), - verificationConfig = verificationConfig.get() - ) + abstract class Params : WorkParameters { + abstract val dependencies: ListProperty>> + abstract val projectPath: Property + abstract val sdkDirectory: DirectoryProperty + abstract val appInfo: Property + abstract val deviceSpec: Property + abstract val bundleFile: RegularFileProperty + abstract val mappingFile: RegularFileProperty + abstract val resourceMappingFile: RegularFileProperty + abstract val ownershipFile: RegularFileProperty + abstract val defaultOwner: Property + abstract val omitFileBreakdown: Property + abstract val unstrippedNativeFiles: ListProperty + abstract val staticDependenciesFile: RegularFileProperty + abstract val verificationConfig: Property + abstract val workingDir: DirectoryProperty + abstract val reportDir: DirectoryProperty } - override fun rulerConfig(): RulerConfig = config + abstract class RulerTaskAction : WorkAction, BaseRulerTask { - override fun provideDependencies(): Map> { - val dependencyParser = EntryParser() - val entries = dependencyParser.parse(project, rulerConfig().appInfo) + override fun execute() { + run() + } - val classNameSanitizer = ClassNameSanitizer(provideMappingFile()) - val dependencySanitizer = DependencySanitizer(classNameSanitizer) - return dependencySanitizer.sanitize(entries) - } + private val config by lazy { + RulerConfig( + projectPath = parameters.projectPath.get(), + apkFilesMap = createApkFile(), + reportDir = parameters.reportDir.asFile.get(), + ownershipFile = parameters.ownershipFile.asFile.orNull, + staticDependenciesFile = parameters.staticDependenciesFile.asFile.orNull, + appInfo = parameters.appInfo.get(), + deviceSpec = parameters.deviceSpec.get(), + defaultOwner = parameters.defaultOwner.get(), + omitFileBreakdown = parameters.omitFileBreakdown.get(), + additionalEntries = emptyList(), + ignoredFiles = emptyList(), + verificationConfig = parameters.verificationConfig.get(), + ) + } - override fun print(content: String) = project.logger.lifecycle(content) - override fun provideMappingFile(): File? = mappingFile.asFile.orNull - override fun provideResourceMappingFile(): File? = resourceMappingFile.asFile.orNull - override fun provideUnstrippedLibraryFiles(): List = unstrippedNativeFiles.get().map { - it.asFile - } + override fun rulerConfig(): RulerConfig = config - override fun provideBloatyPath() = null + override fun provideDependencies(): Map> { + val classNameSanitizer = ClassNameSanitizer(provideMappingFile()) + val dependencySanitizer = DependencySanitizer(classNameSanitizer) + return dependencySanitizer.sanitize(parameters.dependencies.get().flatMap { it.get() }) + } - private fun createApkFile(): Map> { - val android = project.extensions.findByName("android") as BaseExtension? - val apkCreator = ApkCreator(android?.sdkDirectory) + override fun print(content: String) = println(content) // Use println directly? + override fun provideMappingFile(): File? = parameters.mappingFile.asFile.orNull + override fun provideResourceMappingFile(): File? = parameters.resourceMappingFile.asFile.orNull + override fun provideUnstrippedLibraryFiles(): List = parameters.unstrippedNativeFiles.get().map { + it.asFile + } - val apkFile = bundleFile.asFile.get() - return if (apkFile.extension == "apk") { - mapOf(ApkCreator.BASE_FEATURE_NAME to listOf(apkFile)) - } else { - apkCreator.createSplitApks( - apkFile, - deviceSpec.get(), - workingDir.asFile.get() - ) + override fun provideBloatyPath() = null + + private fun createApkFile(): Map> { + val apkCreator = ApkCreator(parameters.sdkDirectory.asFile.get()) + + val apkFile = parameters.bundleFile.asFile.get() + return if (apkFile.extension == "apk") { + mapOf(ApkCreator.BASE_FEATURE_NAME to listOf(apkFile)) + } else { + apkCreator.createSplitApks( + apkFile, + parameters.deviceSpec.get(), + parameters.workingDir.asFile.get(), + ) + } } } } diff --git a/ruler-gradle-plugin/src/main/kotlin/com/spotify/ruler/plugin/dependency/EntryParser.kt b/ruler-gradle-plugin/src/main/kotlin/com/spotify/ruler/plugin/dependency/EntryParser.kt index a47e0b28..62ff5bcc 100644 --- a/ruler-gradle-plugin/src/main/kotlin/com/spotify/ruler/plugin/dependency/EntryParser.kt +++ b/ruler-gradle-plugin/src/main/kotlin/com/spotify/ruler/plugin/dependency/EntryParser.kt @@ -19,31 +19,23 @@ package com.spotify.ruler.plugin.dependency import com.spotify.ruler.common.dependency.ArtifactResult import com.spotify.ruler.common.dependency.DependencyEntry import com.spotify.ruler.common.dependency.DependencyParser -import com.spotify.ruler.common.models.AppInfo -import org.gradle.api.Project +import org.gradle.api.Transformer import org.gradle.api.artifacts.Configuration import org.gradle.api.artifacts.result.ResolvedArtifactResult import org.gradle.api.attributes.Attribute +import org.gradle.api.provider.Provider import java.io.File /** Responsible for parsing and extracting entries from dependencies. */ class EntryParser { - /** Parses and returns the list of entries contained in all dependencies of the given [project]. */ - fun parse(project: Project, appInfo: AppInfo): List { - val configuration = - project.configurations.getByName("${appInfo.variantName}RuntimeClasspath") - val entries = mutableListOf() - val parser = DependencyParser() - listOf("android-classes").forEach { artifactType -> - entries += parseFile(configuration, artifactType, true) + /** Parses and returns the list of entries contained in all dependencies of the given [configuration]. */ + fun parse(configuration: Configuration): List>> { + return listOf("android-classes").map { artifactType -> + parseFile(configuration, artifactType, true) + } + listOf("android-res", "android-assets", "android-jni").map { artifactType -> + parseFile(configuration, artifactType, false) } - - listOf("android-res", "android-assets", "android-jni").forEach { artifactType -> - entries += parseFile(configuration, artifactType, false) - } - - return parser.parse(entries) } private fun getArtifactView( @@ -56,32 +48,53 @@ class EntryParser { artifactType ) } - }.artifacts.artifacts + }.artifacts.resolvedArtifacts private fun parseFile( configuration: Configuration, artifactType: String, - isJarOrClass: Boolean - ) = getArtifactView(configuration, artifactType).flatMap { artifactResult -> - val artifactFiles = artifactResult.file.walkTopDown().filter(File::isFile) - val component = getComponentIdentifier(artifactResult) - if (isJarOrClass) { - artifactFiles.map { artifactFile -> - when (artifactFile.extension.lowercase()) { - "jar" -> ArtifactResult.JarArtifact(artifactFile, component) - "class" -> ArtifactResult.ClassArtifact(artifactFile, artifactResult.file, component) - // in case there are files we don't recognize on the classpath, fallback to a default artifact - else -> ArtifactResult.DefaultArtifact(artifactFile, artifactResult.file, component) + isJarOrClass: Boolean, + ) = getArtifactView(configuration, artifactType).map { artifactResult -> + DependencyEntryExtractor(isJarOrClass).transform(artifactResult) + } + + internal class DependencyEntryExtractor(private val isJarOrClass: Boolean) : + Transformer, Collection> { + + private val parser = DependencyParser() + + override fun transform(artifacts: Collection): List { + return artifacts.flatMap { artifactResult -> + val artifactFiles = artifactResult.file.walkTopDown().filter(File::isFile) + val component = getComponentIdentifier(artifactResult) + if (isJarOrClass) { + artifactFiles.map { artifactFile -> + when (artifactFile.extension.lowercase()) { + "jar" -> ArtifactResult.JarArtifact(artifactFile, component) + "class" -> ArtifactResult.ClassArtifact( + artifactFile, + artifactResult.file, + component, + ) + // in case there are files we don't recognize on the classpath, fallback to a default artifact + else -> ArtifactResult.DefaultArtifact( + artifactFile, + artifactResult.file, + component, + ) + } + } + } else { + artifactFiles.map { artifactFile -> + ArtifactResult.DefaultArtifact(artifactFile, artifactResult.file, component) + } } - } - } else { - artifactFiles.map { artifactFile -> - ArtifactResult.DefaultArtifact(artifactFile, artifactResult.file, component) - } + }.run(parser::parse) } - } - private fun getComponentIdentifier(artifactResult: ResolvedArtifactResult): String { - return artifactResult.id.componentIdentifier.displayName + private fun getComponentIdentifier(artifactResult: ResolvedArtifactResult): String { + return artifactResult.id.componentIdentifier.displayName + } } + }