diff --git a/src/main/kotlin/com/autonomousapps/internal/OutputPaths.kt b/src/main/kotlin/com/autonomousapps/internal/OutputPaths.kt index abc3c7e78..2029ee718 100644 --- a/src/main/kotlin/com/autonomousapps/internal/OutputPaths.kt +++ b/src/main/kotlin/com/autonomousapps/internal/OutputPaths.kt @@ -4,17 +4,19 @@ package com.autonomousapps.internal +import com.autonomousapps.internal.OutputPaths.Companion.ROOT_DIR import org.gradle.api.Project -internal const val ROOT_DIR = "reports/dependency-analysis" - internal class OutputPaths( private val project: Project, - variantName: String + variantName: String, ) { + internal companion object { + const val ROOT_DIR = "reports/dependency-analysis" + } + private fun file(path: String) = project.layout.buildDirectory.file(path) - private fun dir(path: String) = project.layout.buildDirectory.dir(path) private val variantDirectory = "$ROOT_DIR/$variantName" private val intermediatesDir = "${variantDirectory}/intermediates" @@ -37,7 +39,7 @@ internal class OutputPaths( val declaredProcPath = file("${intermediatesDir}/procs-declared.json") val abiAnalysisPath = file("${intermediatesDir}/abi.json") val abiDumpPath = file("${variantDirectory}/abi-dump.txt") - val dependenciesDir = dir("${variantDirectory}/dependencies") + val dependencies = file("${variantDirectory}/dependencies.txt") val explodedSourcePath = file("${intermediatesDir}/exploded-source.json") val explodingBytecodePath = file("${intermediatesDir}/exploding-bytecode.json") val syntheticProjectPath = file("${intermediatesDir}/synthetic-project.json") diff --git a/src/main/kotlin/com/autonomousapps/internal/analyzer/AndroidProjectAnalyzer.kt b/src/main/kotlin/com/autonomousapps/internal/analyzer/AndroidProjectAnalyzer.kt index 8cc56ce2a..02ab9b7d0 100644 --- a/src/main/kotlin/com/autonomousapps/internal/analyzer/AndroidProjectAnalyzer.kt +++ b/src/main/kotlin/com/autonomousapps/internal/analyzer/AndroidProjectAnalyzer.kt @@ -220,6 +220,7 @@ internal class AndroidLibAnalyzer( ): TaskProvider { return project.tasks.register("computeAndroidScore$taskNameSuffix") { dependencies.set(synthesizeDependenciesTask.flatMap { it.outputDir }) + dependenciesList.set(synthesizeDependenciesTask.flatMap { it.output }) syntheticProject.set(synthesizeProjectViewTask.flatMap { it.output }) output.set(outputPaths.androidScorePath) } diff --git a/src/main/kotlin/com/autonomousapps/model/ProjectVariant.kt b/src/main/kotlin/com/autonomousapps/model/ProjectVariant.kt index 2eda39bea..b7f6b8c75 100644 --- a/src/main/kotlin/com/autonomousapps/model/ProjectVariant.kt +++ b/src/main/kotlin/com/autonomousapps/model/ProjectVariant.kt @@ -9,6 +9,7 @@ import com.autonomousapps.internal.utils.fromJson import com.autonomousapps.model.CodeSource.Kind import com.autonomousapps.model.declaration.Variant import com.squareup.moshi.JsonClass +import com.squareup.moshi.JsonEncodingException import org.gradle.api.file.Directory /** Represents a variant-specific view of the project under analysis. */ @@ -22,7 +23,7 @@ data class ProjectVariant( val sources: Set, val classpath: Set, val annotationProcessors: Set, - val testInstrumentationRunner: String? + val testInstrumentationRunner: String?, ) { val usedClassesBySrc: Set by unsafeLazy { @@ -93,9 +94,13 @@ data class ProjectVariant( .map { val file = dependenciesDir.file(it.toFileName()) if (file.asFile.exists()) { - file.fromJson() + try { + file.fromJson() + } catch (e: JsonEncodingException) { + throw IllegalStateException("Couldn't deserialize '${file.asFile}'", e) + } } else { - error("No file ${it.toFileName()}") + error("No file '${it.toFileName()}'") } } .toSet() diff --git a/src/main/kotlin/com/autonomousapps/services/InMemoryCache.kt b/src/main/kotlin/com/autonomousapps/services/InMemoryCache.kt index 45d8a45ac..16fc52533 100644 --- a/src/main/kotlin/com/autonomousapps/services/InMemoryCache.kt +++ b/src/main/kotlin/com/autonomousapps/services/InMemoryCache.kt @@ -5,6 +5,7 @@ package com.autonomousapps.services import com.autonomousapps.Flags.cacheSize +import com.autonomousapps.internal.OutputPaths.Companion.ROOT_DIR import com.autonomousapps.model.InlineMemberCapability import com.autonomousapps.model.intermediates.AnnotationProcessorDependency import com.autonomousapps.model.intermediates.ExplodingJar @@ -13,6 +14,7 @@ import com.autonomousapps.tasks.KotlinCapabilities import com.github.benmanes.caffeine.cache.Cache import com.github.benmanes.caffeine.cache.Caffeine import org.gradle.api.Project +import org.gradle.api.file.DirectoryProperty import org.gradle.api.invocation.Gradle import org.gradle.api.provider.Property import org.gradle.api.provider.Provider @@ -23,6 +25,7 @@ abstract class InMemoryCache : BuildService { interface Params : BuildServiceParameters { val cacheSize: Property + val dependenciesDir: DirectoryProperty } private val cacheSize = parameters.cacheSize.get() @@ -53,7 +56,7 @@ abstract class InMemoryCache : BuildService { procs.asMap().putIfAbsent(procName, proc) } - companion object { + internal companion object { private const val SHARED_SERVICES_IN_MEMORY_CACHE = "inMemoryCache" private const val DEFAULT_CACHE_VALUE = -1L @@ -92,6 +95,13 @@ abstract class InMemoryCache : BuildService { .sharedServices .registerIfAbsent(SHARED_SERVICES_IN_MEMORY_CACHE, InMemoryCache::class.java) { parameters.cacheSize.set(project.cacheSize(DEFAULT_CACHE_VALUE)) + parameters.dependenciesDir.set(project.layout.buildDirectory.dir("${ROOT_DIR}/dependencies")) + + // TODO I wonder how this works in the context of a composite build. Maybe I should create a new service just + // for this, and _not_ share that service across builds? + if (project != project.rootProject) { + project.logger.warn("Creating global dependencies output directory in '${project.path}'") + } } } } diff --git a/src/main/kotlin/com/autonomousapps/subplugin/ProjectPlugin.kt b/src/main/kotlin/com/autonomousapps/subplugin/ProjectPlugin.kt index 253c7dafd..84e1c0f69 100644 --- a/src/main/kotlin/com/autonomousapps/subplugin/ProjectPlugin.kt +++ b/src/main/kotlin/com/autonomousapps/subplugin/ProjectPlugin.kt @@ -671,7 +671,9 @@ internal class ProjectPlugin(private val project: Project) { val synthesizeDependenciesTask = tasks.register("synthesizeDependencies$taskNameSuffix") { - inMemoryCache.set(InMemoryCache.register(project)) + val cache = InMemoryCache.register(project) + + inMemoryCache.set(cache) projectPath.set(thisProjectPath) compileDependencies.set(graphViewTask.flatMap { it.outputNodes }) physicalArtifacts.set(artifactsReport.flatMap { it.output }) @@ -686,7 +688,8 @@ internal class ProjectPlugin(private val project: Project) { findNativeLibsTask?.let { task -> nativeLibs.set(task.flatMap { it.output }) } findAndroidAssetsTask?.let { task -> androidAssets.set(task.flatMap { it.output }) } - outputDir.set(outputPaths.dependenciesDir) + outputDir.set(cache.flatMap { it.parameters.dependenciesDir }) + output.set(outputPaths.dependencies) } /* ****************************** @@ -770,6 +773,7 @@ internal class ProjectPlugin(private val project: Project) { graph.set(graphViewTask.flatMap { it.output }) declarations.set(findDeclarationsTask.flatMap { it.output }) dependencies.set(synthesizeDependenciesTask.flatMap { it.outputDir }) + dependenciesList.set(synthesizeDependenciesTask.flatMap { it.output }) syntheticProject.set(synthesizeProjectViewTask.flatMap { it.output }) kapt.set(isKaptApplied()) output.set(outputPaths.dependencyTraceReportPath) diff --git a/src/main/kotlin/com/autonomousapps/subplugin/RootPlugin.kt b/src/main/kotlin/com/autonomousapps/subplugin/RootPlugin.kt index b9bc519dd..9c4a206b5 100644 --- a/src/main/kotlin/com/autonomousapps/subplugin/RootPlugin.kt +++ b/src/main/kotlin/com/autonomousapps/subplugin/RootPlugin.kt @@ -13,6 +13,7 @@ import com.autonomousapps.internal.advice.DslKind import com.autonomousapps.internal.artifacts.DagpArtifacts import com.autonomousapps.internal.artifacts.Resolver.Companion.interProjectResolver import com.autonomousapps.internal.utils.log +import com.autonomousapps.services.InMemoryCache import com.autonomousapps.tasks.BuildHealthTask import com.autonomousapps.tasks.ComputeDuplicateDependenciesTask import com.autonomousapps.tasks.GenerateBuildHealthTask @@ -23,9 +24,7 @@ import org.gradle.kotlin.dsl.register internal const val DEPENDENCY_ANALYSIS_PLUGIN = "com.autonomousapps.dependency-analysis" -/** - * This "plugin" is applied to the root project only. - */ +/** This "plugin" is applied to the root project only. */ internal class RootPlugin(private val project: Project) { init { @@ -87,6 +86,9 @@ internal class RootPlugin(private val project: Project) { private fun Project.configureRootProject() { val paths = RootOutputPaths(this) + // Register this in the root project to centralize dependency synthesis files + InMemoryCache.register(this) + val computeDuplicatesTask = tasks.register("computeDuplicateDependencies") { resolvedDependenciesReports.setFrom(resolvedDepsResolver.internal) output.set(paths.duplicateDependenciesPath) diff --git a/src/main/kotlin/com/autonomousapps/tasks/AndroidScoreTask.kt b/src/main/kotlin/com/autonomousapps/tasks/AndroidScoreTask.kt index a94bf85df..606452c67 100644 --- a/src/main/kotlin/com/autonomousapps/tasks/AndroidScoreTask.kt +++ b/src/main/kotlin/com/autonomousapps/tasks/AndroidScoreTask.kt @@ -31,10 +31,16 @@ abstract class AndroidScoreTask @Inject constructor( @get:InputFile abstract val syntheticProject: RegularFileProperty - @get:PathSensitive(PathSensitivity.NONE) - @get:InputDirectory + /** + * TODO: docs + */ + @get:Internal abstract val dependencies: DirectoryProperty + @get:PathSensitive(PathSensitivity.NONE) + @get:InputFile + abstract val dependenciesList: RegularFileProperty + @get:OutputFile abstract val output: RegularFileProperty diff --git a/src/main/kotlin/com/autonomousapps/tasks/ComputeUsagesTask.kt b/src/main/kotlin/com/autonomousapps/tasks/ComputeUsagesTask.kt index d65835bb0..334899f7f 100644 --- a/src/main/kotlin/com/autonomousapps/tasks/ComputeUsagesTask.kt +++ b/src/main/kotlin/com/autonomousapps/tasks/ComputeUsagesTask.kt @@ -40,10 +40,20 @@ abstract class ComputeUsagesTask @Inject constructor( @get:InputFile abstract val declarations: RegularFileProperty - @get:PathSensitive(PathSensitivity.NONE) - @get:InputDirectory + /** + * This contains all of the [Dependencies][Dependency] used by this project. It cannot be a task input because it is + * a globally shared directory that many tasks write to and read from. See also [dependenciesList]. + */ + @get:Internal abstract val dependencies: DirectoryProperty + /** + * Only for task snapshotting. A simplified representation of [dependencies]. + */ + @get:PathSensitive(PathSensitivity.NONE) + @get:InputFile + abstract val dependenciesList: RegularFileProperty + @get:PathSensitive(PathSensitivity.NONE) @get:InputFile abstract val syntheticProject: RegularFileProperty diff --git a/src/main/kotlin/com/autonomousapps/tasks/SynthesizeDependenciesTask.kt b/src/main/kotlin/com/autonomousapps/tasks/SynthesizeDependenciesTask.kt index d06ccd509..c05410eb3 100644 --- a/src/main/kotlin/com/autonomousapps/tasks/SynthesizeDependenciesTask.kt +++ b/src/main/kotlin/com/autonomousapps/tasks/SynthesizeDependenciesTask.kt @@ -87,9 +87,14 @@ abstract class SynthesizeDependenciesTask @Inject constructor( @get:InputFile abstract val androidAssets: RegularFileProperty + // TODO: Maybe this should not be an OutputDirectory anymore, but just Internal? Since multiple tasks will write to it @get:OutputDirectory abstract val outputDir: DirectoryProperty + /** A simplified representation of [outputDir] for task snapshotting purposes only. */ + @get:OutputFile + abstract val output: RegularFileProperty + @TaskAction fun action() { workerExecutor.noIsolation().submit(SynthesizeDependenciesWorkAction::class.java) { inMemoryCache.set(this@SynthesizeDependenciesTask.inMemoryCache) @@ -105,6 +110,7 @@ abstract class SynthesizeDependenciesTask @Inject constructor( nativeLibs.set(this@SynthesizeDependenciesTask.nativeLibs) androidAssets.set(this@SynthesizeDependenciesTask.androidAssets) outputDir.set(this@SynthesizeDependenciesTask.outputDir) + output.set(this@SynthesizeDependenciesTask.output) } } @@ -125,6 +131,7 @@ abstract class SynthesizeDependenciesTask @Inject constructor( val androidAssets: RegularFileProperty val outputDir: DirectoryProperty + val output: RegularFileProperty } abstract class SynthesizeDependenciesWorkAction : WorkAction { @@ -133,6 +140,7 @@ abstract class SynthesizeDependenciesTask @Inject constructor( override fun execute() { val outputDir = parameters.outputDir + val output = parameters.output.getAndDelete() val dependencies = parameters.compileDependencies.fromJson().coordinates val physicalArtifacts = parameters.physicalArtifacts.fromJsonSet() @@ -192,7 +200,13 @@ abstract class SynthesizeDependenciesTask @Inject constructor( .map { it.build() } .forEach { dependency -> val coordinates = dependency.coordinates - outputDir.file(coordinates.toFileName()).get().asFile.bufferWriteJson(dependency) + val file = outputDir.file(coordinates.toFileName()).get().asFile + if (!file.exists()) { + file.bufferWriteJson(dependency) + } + + // This is the task output for snapshotting purposes + output.appendText("${coordinates.gav()}\n") } }