/* * Copyright 2020 the original author or authors. * * 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 org.gradle.configurationcache import org.gradle.api.logging.LogLevel import org.gradle.cache.internal.streams.BlockAddress import org.gradle.cache.internal.streams.BlockAddressSerializer import org.gradle.configurationcache.cacheentry.EntryDetails import org.gradle.configurationcache.cacheentry.ModelKey import org.gradle.configurationcache.extensions.useToRun import org.gradle.configurationcache.initialization.ConfigurationCacheStartParameter import org.gradle.configurationcache.problems.ConfigurationCacheProblems import org.gradle.configurationcache.serialization.DefaultReadContext import org.gradle.configurationcache.serialization.DefaultWriteContext import org.gradle.configurationcache.serialization.LoggingTracer import org.gradle.configurationcache.serialization.Tracer import org.gradle.configurationcache.serialization.beans.BeanStateReaderLookup import org.gradle.configurationcache.serialization.beans.BeanStateWriterLookup import org.gradle.configurationcache.serialization.codecs.Codecs import org.gradle.configurationcache.serialization.readCollection import org.gradle.configurationcache.serialization.readFile import org.gradle.configurationcache.serialization.readList import org.gradle.configurationcache.serialization.readNonNull import org.gradle.configurationcache.serialization.runReadOperation import org.gradle.configurationcache.serialization.runWriteOperation import org.gradle.configurationcache.serialization.withGradleIsolate import org.gradle.configurationcache.serialization.writeCollection import org.gradle.configurationcache.serialization.writeFile import org.gradle.internal.build.BuildStateRegistry import org.gradle.internal.buildtree.BuildTreeWorkGraph import org.gradle.internal.operations.BuildOperationProgressEventEmitter import org.gradle.internal.serialize.Decoder import org.gradle.internal.serialize.Encoder import org.gradle.internal.serialize.kryo.KryoBackedDecoder import org.gradle.internal.serialize.kryo.KryoBackedEncoder import org.gradle.internal.service.scopes.Scopes import org.gradle.internal.service.scopes.ServiceScope import org.gradle.util.Path import java.io.File import java.io.InputStream import java.io.OutputStream @ServiceScope(Scopes.Gradle::class) class ConfigurationCacheIO internal constructor( private val startParameter: ConfigurationCacheStartParameter, private val host: DefaultConfigurationCache.Host, private val problems: ConfigurationCacheProblems, private val scopeRegistryListener: ConfigurationCacheClassLoaderScopeRegistryListener, private val beanStateReaderLookup: BeanStateReaderLookup, private val beanStateWriterLookup: BeanStateWriterLookup, private val eventEmitter: BuildOperationProgressEventEmitter ) { private val codecs = codecs() private val encryptionService by lazy { service() } internal fun writeCacheEntryDetailsTo( buildStateRegistry: BuildStateRegistry, intermediateModels: Map, projectMetadata: Map, stateFile: ConfigurationCacheStateFile ) { val rootDirs = collectRootDirs(buildStateRegistry) writeConfigurationCacheState(stateFile) { writeCollection(rootDirs) { writeFile(it) } val addressSerializer = BlockAddressSerializer() writeCollection(intermediateModels.entries) { entry -> writeNullableString(entry.key.identityPath?.path) writeString(entry.key.modelName) addressSerializer.write(this, entry.value) } writeCollection(projectMetadata.entries) { entry -> writeString(entry.key.path) addressSerializer.write(this, entry.value) } } } internal fun readCacheEntryDetailsFrom(stateFile: ConfigurationCacheStateFile): EntryDetails? { if (!stateFile.exists) { return null } return readConfigurationCacheState(stateFile) { val rootDirs = readList { readFile() } val addressSerializer = BlockAddressSerializer() val intermediateModels = mutableMapOf() readCollection { val path = readNullableString()?.let { Path.path(it) } val modelName = readString() val address = addressSerializer.read(this) intermediateModels[ModelKey(path, modelName)] = address } val metadata = mutableMapOf() readCollection { val path = Path.path(readString()) val address = addressSerializer.read(this) metadata[path] = address } EntryDetails(rootDirs, intermediateModels, metadata) } } private fun collectRootDirs(buildStateRegistry: BuildStateRegistry): MutableSet { val rootDirs = mutableSetOf() buildStateRegistry.visitBuilds { build -> rootDirs.add(build.buildRootDir) } return rootDirs } /** * See [ConfigurationCacheState.writeRootBuildState]. */ internal fun writeRootBuildStateTo(stateFile: ConfigurationCacheStateFile) = writeConfigurationCacheState(stateFile) { cacheState -> cacheState.run { writeRootBuildState(host.currentBuild) } } internal fun readRootBuildStateFrom(stateFile: ConfigurationCacheStateFile, loadAfterStore: Boolean, graph: BuildTreeWorkGraph, graphBuilder: BuildTreeWorkGraphBuilder?): BuildTreeWorkGraph.FinalizedGraph { return readConfigurationCacheState(stateFile) { state -> state.run { readRootBuildState(graph, graphBuilder, loadAfterStore) } } } internal fun writeIncludedBuildStateTo(stateFile: ConfigurationCacheStateFile, buildTreeState: StoredBuildTreeState) { writeConfigurationCacheState(stateFile) { cacheState -> cacheState.run { writeBuildContent(host.currentBuild, buildTreeState) } } } internal fun readIncludedBuildStateFrom(stateFile: ConfigurationCacheStateFile, includedBuild: ConfigurationCacheBuild) = readConfigurationCacheState(stateFile) { state -> state.run { readBuildContent(includedBuild) } } private fun readConfigurationCacheState( stateFile: ConfigurationCacheStateFile, action: suspend DefaultReadContext.(ConfigurationCacheState) -> T ): T { return withReadContextFor(encryptionService.inputStream(stateFile.stateType, stateFile::inputStream)) { codecs -> ConfigurationCacheState(codecs, stateFile, eventEmitter, host).run { action(this) } } } private fun writeConfigurationCacheState( stateFile: ConfigurationCacheStateFile, action: suspend DefaultWriteContext.(ConfigurationCacheState) -> T ): T { val (context, codecs) = writerContextFor(encryptionService.outputStream(stateFile.stateType, stateFile::outputStream)) { host.currentBuild.gradle.owner.displayName.displayName + " state" } return context.useToRun { runWriteOperation { action(ConfigurationCacheState(codecs, stateFile, eventEmitter, host)) } } } internal fun writeModelTo(model: Any, stateFile: ConfigurationCacheStateFile) { writeConfigurationCacheState(stateFile) { withGradleIsolate(host.currentBuild.gradle, codecs.userTypesCodec()) { write(model) } } } internal fun readModelFrom(stateFile: ConfigurationCacheStateFile): Any { return readConfigurationCacheState(stateFile) { withGradleIsolate(host.currentBuild.gradle, codecs.userTypesCodec()) { readNonNull() } } } /** * @param profile the unique name associated with the output stream for debugging space usage issues */ internal fun writerContextFor(outputStream: OutputStream, profile: () -> String): Pair = KryoBackedEncoder(outputStream).let { encoder -> writeContextFor( encoder, loggingTracerFor(profile, encoder), codecs ) to codecs } private fun loggingTracerFor(profile: () -> String, encoder: KryoBackedEncoder) = loggingTracerLogLevel()?.let { level -> LoggingTracer(profile(), encoder::getWritePosition, logger, level) } private fun loggingTracerLogLevel(): LogLevel? = when { startParameter.isDebug -> LogLevel.LIFECYCLE logger.isDebugEnabled -> LogLevel.DEBUG else -> null } internal fun writerContextFor(encoder: Encoder): Pair = writeContextFor( encoder, null, codecs ) to codecs internal fun withReadContextFor( inputStream: InputStream, readOperation: suspend DefaultReadContext.(Codecs) -> R ): R = readerContextFor(inputStream).let { (context, codecs) -> context.use { context.run { initClassLoader(javaClass.classLoader) runReadOperation { readOperation(codecs) }.also { finish() } } } } private fun readerContextFor( inputStream: InputStream, ) = readerContextFor(KryoBackedDecoder(inputStream)) internal fun readerContextFor( decoder: Decoder, ) = readContextFor(decoder, codecs).apply { initClassLoader(javaClass.classLoader) } to codecs private fun writeContextFor( encoder: Encoder, tracer: Tracer?, codecs: Codecs ) = DefaultWriteContext( codecs.userTypesCodec(), encoder, scopeRegistryListener, beanStateWriterLookup, logger, tracer, problems ) private fun readContextFor( decoder: Decoder, codecs: Codecs ) = DefaultReadContext( codecs.userTypesCodec(), decoder, beanStateReaderLookup, logger, problems ) private fun codecs(): Codecs = Codecs( directoryFileTreeFactory = service(), fileCollectionFactory = service(), artifactSetConverter = service(), fileLookup = service(), propertyFactory = service(), filePropertyFactory = service(), fileResolver = service(), objectFactory = service(), instantiator = service(), fileSystemOperations = service(), listenerManager = service(), taskNodeFactory = service(), ordinalGroupFactory = service(), inputFingerprinter = service(), buildOperationExecutor = service(), classLoaderHierarchyHasher = service(), isolatableFactory = service(), managedFactoryRegistry = service(), parameterScheme = service(), actionScheme = service(), attributesFactory = service(), valueSourceProviderFactory = service(), calculatedValueContainerFactory = service(), patternSetFactory = factory(), fileOperations = service(), fileFactory = service(), includedTaskGraph = service(), buildStateRegistry = service(), documentationRegistry = service(), javaSerializationEncodingLookup = service(), flowProviders = service(), transformStepNodeFactory = service(), ) private inline fun service() = host.service() private inline fun factory() = host.factory(T::class.java) }