Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ class ConfigurationCacheBuildOperationsIntegrationTest extends AbstractConfigura
def cacheDir = file('lib/.gradle/configuration-cache')
def entryDir = single(subDirsOf(cacheDir).findAll { containsFileNamed("entry.bin", it) })
def entryFiles = entryDir.listFiles().toList()
.findAll { it.name !in ['entry.bin', 'buildfingerprint.bin', 'projectfingerprint.bin'] } // TODO: include fingerprints as well
.findAll { it.name !in ['entry.bin', 'buildfingerprint.bin', 'projectfingerprint.bin', 'classloaderscopes.bin'] } // TODO: include fingerprints as well

entryFiles.size() > 0
entryFiles.every { it.name.endsWith(".bin") } // sanity check
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*
* Copyright 2025 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.internal.cc.impl

import org.gradle.test.fixtures.file.TestFile
import spock.lang.Ignore
import spock.lang.Issue

class ConfigurationCacheClassLoaderValidationIntegrationTest extends AbstractConfigurationCacheIntegrationTest {

@Ignore //not yet implemented
@Issue('https://github.com/gradle/gradle/issues/28727')
def "invalidates entry when script classpath file dependency changes"() {
given:
def configurationCache = newConfigurationCacheFixture()
def jarFile = file('lib.jar')
jarWithPrintTask(jarFile, 'Version 1')

buildFile """
buildscript {
dependencies {
classpath(files('${jarFile.toURI()}'))
}
}

tasks.register('ok', PrintTask)
"""

when:
configurationCacheRun 'ok'

then:
output.contains('Version 1')
configurationCache.assertStateStored()

when:
jarWithPrintTask(jarFile, 'Version 42')

and:
configurationCacheRun 'ok'

then:
output.contains('Version 42')
configurationCache.assertStateStored()

when:
configurationCacheRun 'ok'

then:
output.contains('Version 42')
configurationCache.assertStateLoaded()
}

private void jarWithPrintTask(TestFile jarFile, String message) {
jarWithClasses(
jarFile,
PrintTask: """
import org.gradle.api.*;
import org.gradle.api.tasks.*;
public class PrintTask extends DefaultTask {
@TaskAction void printValue() {
System.out.println("$message");
}
}
"""
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ class ConfigurationCacheCompositeBuildsIntegrationTest extends AbstractConfigura
def confCacheDir = file("./app/.gradle/configuration-cache")
confCacheDir.isDirectory()
def confCacheFiles = confCacheDir.allDescendants().findAll { it != 'configuration-cache.lock' && it != 'gc.properties' }
confCacheFiles.size() == 13 // header, candidates file, 2 * fingerprint, build strings file, root build state file, root build shared objects file, included build state file, included build shared objects file, 2 * project state file, 2 * owner-less node state files
confCacheFiles.size() == 14 // header, candidates file, classloader scopes, 2 * fingerprint, build strings file, root build state file, root build shared objects file, included build state file, included build shared objects file, 2 * project state file, 2 * owner-less node state files
if (!OperatingSystem.current().isWindows()) {
confCacheFiles.forEach {
assert confCacheDir.file(it).mode == 384
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import org.gradle.internal.buildtree.BuildTreeWorkGraph
import org.gradle.internal.cc.impl.cacheentry.EntryDetails
import org.gradle.internal.cc.impl.cacheentry.ModelKey
import org.gradle.internal.cc.impl.serialize.Codecs
import org.gradle.internal.serialize.Decoder
import org.gradle.internal.serialize.PositionAwareEncoder
import org.gradle.internal.serialize.graph.ClassDecoder
import org.gradle.internal.serialize.graph.ClassEncoder
import org.gradle.internal.serialize.graph.CloseableReadContext
Expand Down Expand Up @@ -78,6 +80,16 @@ interface ConfigurationCacheBuildTreeIO : ConfigurationCacheOperationIO {
customClassEncoder: ClassEncoder? = null
): Pair<CloseableWriteContext, Codecs>

fun encoderFor(
stateType: StateType,
outputStream: () -> OutputStream
): PositionAwareEncoder

fun decoderFor(
stateType: StateType,
inputStream: () -> InputStream
): Decoder

fun <R> withReadContextFor(
stateFile: ConfigurationCacheStateFile,
specialDecoders: SpecialDecoders = SpecialDecoders(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,12 @@ enum class StateType(val encryptable: Boolean = false) {
BuildFingerprint(true),
ProjectFingerprint(true),

/**
* Contains the [ClassLoaderScope specifications][org.gradle.internal.cc.impl.serialize.ClassLoaderScopeSpec]
* required to restore the specific configuration cache entry it is associated with.
*/
ClassLoaderScopes(false),

/**
* The index file that points to all of these things
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,12 @@ import org.gradle.internal.cc.base.problems.IgnoringProblemsListener
import org.gradle.internal.cc.base.services.ConfigurationCacheEnvironmentChangeTracker
import org.gradle.internal.cc.impl.barrier.BarrierAwareBuildTreeLifecycleControllerFactory
import org.gradle.internal.cc.impl.barrier.VintageConfigurationTimeActionRunner
import org.gradle.internal.cc.impl.fingerprint.ClassLoaderScopesFingerprintController
import org.gradle.internal.cc.impl.fingerprint.ConfigurationCacheClassLoaderScopesFingerprintController
import org.gradle.internal.cc.impl.fingerprint.ConfigurationCacheFingerprintController
import org.gradle.internal.cc.impl.fingerprint.ConfigurationCacheInputFileChecker
import org.gradle.internal.cc.impl.fingerprint.DefaultConfigurationCacheInputFileCheckerHost
import org.gradle.internal.cc.impl.fingerprint.IsolatedProjectsClassLoaderScopesFingerprintController
import org.gradle.internal.cc.impl.initialization.ConfigurationCacheInjectedClasspathInstrumentationStrategy
import org.gradle.internal.cc.impl.initialization.ConfigurationCacheProblemsListener
import org.gradle.internal.cc.impl.initialization.ConfigurationCacheStartParameter
Expand Down Expand Up @@ -265,6 +270,21 @@ class DefaultBuildTreeModelControllerServices : BuildTreeModelControllerServices
registration.add(BuildTreeConfigurationCache::class.java, DefaultConfigurationCache::class.java)
registration.add(InstrumentedExecutionAccessListenerRegistry::class.java)
registration.add(ConfigurationCacheFingerprintController::class.java)
registration.add(
ConfigurationCacheInputFileChecker.Host::class.java,
DefaultConfigurationCacheInputFileCheckerHost::class.java
)
if (modelParameters.isIsolatedProjects) {
registration.add(
ClassLoaderScopesFingerprintController::class.java,
IsolatedProjectsClassLoaderScopesFingerprintController::class.java
)
} else {
registration.add(
ClassLoaderScopesFingerprintController::class.java,
ConfigurationCacheClassLoaderScopesFingerprintController::class.java
)
}
registration.addProvider(ConfigurationCacheBuildTreeProvider())
registration.add(ConfigurationCacheBuildTreeModelSideEffectExecutor::class.java)
registration.add(ConfigurationCacheInputsListener::class.java, InstrumentedInputAccessListener::class.java)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,10 @@ import org.gradle.internal.cc.base.serialize.HostServiceProvider
import org.gradle.internal.cc.base.serialize.IsolateOwners
import org.gradle.internal.cc.base.serialize.service
import org.gradle.internal.cc.impl.extensions.withMostRecentEntry
import org.gradle.internal.cc.impl.fingerprint.ClassLoaderScopesFingerprintController
import org.gradle.internal.cc.impl.fingerprint.ConfigurationCacheFingerprintController
import org.gradle.internal.cc.impl.fingerprint.ConfigurationCacheFingerprintStartParameters
import org.gradle.internal.cc.impl.fingerprint.InvalidationReason
import org.gradle.internal.cc.impl.initialization.ConfigurationCacheStartParameter
import org.gradle.internal.cc.impl.metadata.ProjectMetadataController
import org.gradle.internal.cc.impl.models.BuildTreeModelSideEffectStore
Expand Down Expand Up @@ -90,6 +92,7 @@ class DefaultConfigurationCache internal constructor(
private val virtualFileSystem: BuildLifecycleAwareVirtualFileSystem,
private val buildOperationRunner: BuildOperationRunner,
private val cacheFingerprintController: ConfigurationCacheFingerprintController,
private val classLoaderScopes: ClassLoaderScopesFingerprintController,
private val resolveStateFactory: LocalComponentGraphResolveStateFactory,
/**
* Force the [FileSystemAccess] service to be initialized as it initializes important static state.
Expand Down Expand Up @@ -358,6 +361,7 @@ class DefaultConfigurationCache internal constructor(
sideEffects,
fileFor(StateType.Entry)
)
classLoaderScopes.commit(fileFor(StateType.ClassLoaderScopes))
}
updateMostRecentEntry(entryId)
}
Expand Down Expand Up @@ -578,7 +582,9 @@ class DefaultConfigurationCache internal constructor(
private
fun prepareForWork() {
prepareConfigurationTimeBarrier()
startCollectingCacheFingerprint()
val parameters = fingerprintStartParameters()
classLoaderScopes.prepareForWriting(parameters)
cacheFingerprintController.maybeStartCollectingFingerprint(parameters)
InstrumentedInputs.setListener(inputsAccessListener)
}

Expand All @@ -588,6 +594,20 @@ class DefaultConfigurationCache internal constructor(
cacheFingerprintController.stopCollectingFingerprint()
}

private
fun fingerprintStartParameters(): ConfigurationCacheFingerprintStartParameters = object : ConfigurationCacheFingerprintStartParameters {
override fun assignBuildScopedSpoolFile() = entryStore.assignSpoolFile(StateType.BuildFingerprint)
override fun assignProjectScopedSpoolFile() = entryStore.assignSpoolFile(StateType.ProjectFingerprint)
override fun assignClassLoaderScopesFile() = entryStore.assignSpoolFile(StateType.ClassLoaderScopes)
override fun writerContextFor(stateFile: ConfigurationCacheStateStore.StateFile) =
cacheFingerprintWriteContextFor(stateFile.stateType, stateFile.file::outputStream) {
profileNameFor(stateFile)
}

override fun encoderFor(stateFile: ConfigurationCacheStateStore.StateFile) =
cacheIO.encoderFor(stateFile.stateType, stateFile.file::outputStream)
}

private
fun saveModel(model: Any) {
cacheEntryRequiresCommit = true
Expand Down Expand Up @@ -720,20 +740,6 @@ class DefaultConfigurationCache internal constructor(
)
}

private
fun startCollectingCacheFingerprint() {
cacheFingerprintController.maybeStartCollectingFingerprint(
object : ConfigurationCacheFingerprintStartParameters {
override fun assignBuildScopedSpoolFile() = entryStore.assignSpoolFile(StateType.BuildFingerprint)
override fun assignProjectScopedSpoolFile() = entryStore.assignSpoolFile(StateType.ProjectFingerprint)
override fun writeContextForOutputStream(stateFile: ConfigurationCacheStateStore.StateFile) =
cacheFingerprintWriteContextFor(stateFile.stateType, stateFile.file::outputStream) {
profileNameFor(stateFile)
}
}
)
}

private
fun profileNameFor(stateFile: ConfigurationCacheStateStore.StateFile) =
stateFile.stateType.name.replace(Regex("\\p{Upper}")) { match ->
Expand All @@ -759,6 +765,11 @@ class DefaultConfigurationCache internal constructor(
// without violating file system invariants.
registerWatchableBuildDirectories(rootDirs)

val classLoaderScopesInvalidationReason = checkClassLoaderScopes()
if (classLoaderScopesInvalidationReason != null) {
return CheckedFingerprint.Invalid(buildPath(), classLoaderScopesInvalidationReason)
}

loadGradleProperties()

return checkFingerprintAgainstLoadedProperties(candidateEntry).also { result ->
Expand All @@ -771,6 +782,14 @@ class DefaultConfigurationCache internal constructor(
}
}

private
fun ConfigurationCacheRepository.Layout.checkClassLoaderScopes(): InvalidationReason? =
fileFor(StateType.ClassLoaderScopes).let { stateFile ->
classLoaderScopes.checkClassLoaderScopes {
cacheIO.decoderFor(stateFile.stateType, stateFile::inputStream)
}
}

private
fun ConfigurationCacheRepository.Layout.checkFingerprintAgainstLoadedProperties(
candidateEntry: CandidateEntry
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import org.gradle.internal.cc.base.serialize.service
import org.gradle.internal.cc.base.serialize.withGradleIsolate
import org.gradle.internal.cc.impl.cacheentry.EntryDetails
import org.gradle.internal.cc.impl.cacheentry.ModelKey
import org.gradle.internal.cc.impl.fingerprint.ClassLoaderScopesFingerprintController
import org.gradle.internal.cc.impl.initialization.ConfigurationCacheStartParameter
import org.gradle.internal.cc.impl.io.safeWrap
import org.gradle.internal.cc.impl.problems.ConfigurationCacheProblems
Expand Down Expand Up @@ -105,7 +106,8 @@ class DefaultConfigurationCacheIO internal constructor(
private val eventEmitter: BuildOperationProgressEventEmitter,
private val classLoaderScopeRegistryListener: ConfigurationCacheClassLoaderScopeRegistryListener,
private val classLoaderScopeRegistry: ClassLoaderScopeRegistry,
private val instantiatorFactory: InstantiatorFactory
private val instantiatorFactory: InstantiatorFactory,
private val classLoaderScopes: ClassLoaderScopesFingerprintController
) : ConfigurationCacheBuildTreeIO, ConfigurationCacheIncludedBuildIO {

private
Expand Down Expand Up @@ -415,15 +417,13 @@ class DefaultConfigurationCacheIO internal constructor(
) to codecs
}

private
fun encoderFor(stateType: StateType, outputStream: () -> OutputStream): PositionAwareEncoder =
override fun encoderFor(stateType: StateType, outputStream: () -> OutputStream): PositionAwareEncoder =
outputStreamFor(stateType, outputStream).let { stream ->
if (isUsingSequentialStringDeduplicationStrategy(stateType)) StringDeduplicatingKryoBackedEncoder(stream)
else KryoBackedEncoder(stream)
}

private
fun decoderFor(stateType: StateType, inputStream: () -> InputStream): Decoder =
override fun decoderFor(stateType: StateType, inputStream: () -> InputStream): Decoder =
inputStreamFor(stateType, inputStream).let { stream ->
if (isUsingSequentialStringDeduplicationStrategy(stateType)) StringDeduplicatingKryoBackedDecoder(stream)
else KryoBackedDecoder(stream)
Expand Down Expand Up @@ -497,7 +497,7 @@ class DefaultConfigurationCacheIO internal constructor(
customClassDecoder: ClassDecoder?,
readOperation: suspend MutableReadContext.(Codecs) -> R
): R =
readContextFor(name, stateType, inputStream, specialDecoders)
readContextFor(name, stateType, inputStream, specialDecoders, customClassDecoder)
.let { (context, codecs) ->
withReadContextFor(context, codecs, readOperation)
}
Expand Down Expand Up @@ -609,13 +609,17 @@ class DefaultConfigurationCacheIO internal constructor(

private
fun classEncoder() =
DefaultClassEncoder(classLoaderScopeRegistryListener)
DefaultClassEncoder(
classLoaderScopeRegistryListener,
classLoaderScopes.encoder()
)

private
fun classDecoder() =
DefaultClassDecoder(
classLoaderScopeRegistry.coreAndPluginsScope,
instantiatorFactory.decorateScheme().deserializationInstantiator()
instantiatorFactory.decorateScheme().deserializationInstantiator(),
scopeSpecDecoder = classLoaderScopes.decoder()
)

/**
Expand Down
Loading