Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

dependencyUpdates task fails with ConcurrentModificationException since 0.52.0 in certain projects #930

Open
mitchellmebane opened this issue Jan 27, 2025 · 8 comments

Comments

@mitchellmebane
Copy link

mitchellmebane commented Jan 27, 2025

I am getting a ConcurrentModificationException when running the dependencyUpdates task in some of my Spring Boot projects after upgrading them to gradle-versions-plugin v0.52.0. After some experimentation, I've narrowed it down to projects which use all 3 of these plugins:

  • io.spring.dependency-management, any version since at least v3.1.0
  • info.solidsoft.pitest v1.15.0
  • com.github.ben-manes.versions v0.52.0

The relevant bit of the stack trace (I can copy the whole thing here if you'd like):

org.gradle.api.tasks.TaskExecutionException: Execution failed for task ':dependencyUpdates'.
        at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.lambda$executeIfValid$1(ExecuteActionsTaskExecuter.java:130)
        at org.gradle.internal.Try$Failure.ifSuccessfulOrElse(Try.java:293)
        <snip>
Caused by: java.util.ConcurrentModificationException
        at org.gradle.api.internal.collections.FilteredElementSource$FilteringIterator.findNext(FilteredElementSource.java:115)
        at org.gradle.api.internal.collections.FilteredElementSource$FilteringIterator.next(FilteredElementSource.java:134)
        at org.gradle.api.internal.DefaultDomainObjectCollection$IteratorImpl.next(DefaultDomainObjectCollection.java:494)
        at com.github.benmanes.gradle.versions.updates.DependencyUpdates.resolveProjects(DependencyUpdates.kt:78)
        at com.github.benmanes.gradle.versions.updates.DependencyUpdates.run(DependencyUpdates.kt:46)
        at com.github.benmanes.gradle.versions.updates.DependencyUpdatesTask.dependencyUpdates(DependencyUpdatesTask.kt:140)
        at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
        at org.gradle.internal.reflect.JavaMethod.invoke(JavaMethod.java:125)
        at org.gradle.api.internal.project.taskfactory.StandardTaskAction.doExecute(StandardTaskAction.java:58)
        <snip>

Crashing line: DependencyUpdates.kt:78

I have a minimal reproducer here: https://github.com/mitchellmebane/gradle-versions-plugin-crash/

This sounds like it might have been caused by the fix to #907, or perhaps that fix just moved the weak point into the loop in resolveProjects and changed the timing enough that my projects only hit it now.

I don't know much about Gradle internals, but I'll try and dig into this more if I have time.

@ben-manes
Copy link
Owner

ugh, sorry to hear that @mitchellmebane. I think you're right that it is the same as #907 and simply shifted the problem to now impact you. I believe this is a Gradle bug and that you may need to open an issue with them. The logic of their custom collection is confusing with these lazy side-effects that invalidate the iterator. From what I could tell, that iterator was in their code so any fix would be internal rather than by a plugin, so I'm skeptical that we can give you a proper workaround.

@mitchellmebane
Copy link
Author

DependencyUpdates.kt:42-44

      val projectConfigs =
        project.allprojects
          .associateBy({ it }, { it.configurations.matching(filterConfigurations) })

That's the source of the data. Looks like the actual type of projectConfigs is Map<Project, NamedDomainObjectSet<Configuration>>. NamedDomainObjectSet is a subtype of DomainObjectCollection, about which the docs say:

DomainObjectCollection instances are not thread-safe and undefined behavior may result from the invocation of any method on a collection that is being mutated by another thread; this includes direct invocations, passing the collection to a method that might perform invocations, and using an existing iterator to examine the collection.

which probably explains the issue.

It might be possible to change resolveProjects to take projectConfigs as a Map<Project, DomainObjectCollection<Configuration>>, and then use DomainObjectCollection.all instead of the loop. However, it's not clear from the documentation if that method is fully asynchronous, synchronous-for-things-currently-in-the-collection-and-then-async, or what. I'll do some more digging.

@ben-manes
Copy link
Owner

I was thinking that the realizePending was adding to the collection, which invalidated any existing iterators by incrementing the mod count. This wouldn’t be a threading issue, but that modifications must go through the iterator in typical collections. Java’s concurrent collections use weakly consistent iterators, but non-concurrent throw CME for misuse rather than concurrency. That was my assumption last time I looked, but your rationale is interesting too. At execution time I didn’t expect this to be modified, only at init/configuration build phases.

@mitchellmebane
Copy link
Author

mitchellmebane commented Jan 27, 2025

I was thinking that the realizePending was adding to the collection

Hm, that might be possible. I don't have a good feel for how all the lazy initialization works in Gradle yet.

This wouldn’t be a threading issue

Right, that would be a reentrancy issue. I guess I tend to conflate them in my mind. The good news is that if it's not caused by multi-threading, I can probably write a deterministic test case, if I can find what behavior is triggering the crash.

At execution time I didn’t expect this to be modified, only at init/configuration build phases.

Yes, that's definitely what I would expect after reading the Build Lifecycle documentation. I'm going to dig through the other two plugins in my repro project and see if they're modifying the configurations anywhere weird.

@mitchellmebane
Copy link
Author

Based on some simple print debugging, it looks like the runtime type of currentConfigurations is org.gradle.api.internal.DefaultNamedDomainObjectSet_Decorated.

DefaultNamedDomainObjectSet's iterator() implementation is DefaultDomainObjectCollection#iterator, which mostly just delegates to the backing store, which at runtime is FilteredElementSource#iterator. That implementation calls realizePending before starting iteration, which makes me think something else is modifying the configurations later, during the iteration - but I'm still learning most of this as I go, so 🤷.

I'll have to set up a real debugger tonight and look more closely.

@ben-manes
Copy link
Owner

It has been a while since I attached a debugger directly to Gradle, but here are my notes.

In some cases the tasks accept --debug-jvm. Unfortunately I think this is only for test tasks to do the bootstrap for you.

More generally, for any Java program, you can use a remote attachment. In my projects, before that flag above, I used to use

if ("debug" in systemProperties) {
  jvmArgs("-Xdebug", "-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5005")
}

IntelliJ may have this built in now and it can all be done directly and understands their kts support. When I first wrote this plugin in groovy on gradle 1.0, IntelliJ had no support and Eclipse's was vastly superior thanks to Spring's plugin. At that time, when print debugging was not enough, I was able to step through this way (I think this was to pinpoint a bug in their repository handler that dropped maven repos by incorrect deduplication). hope this helps.

@mitchellmebane
Copy link
Author

IntelliJ is much smarter about this today indeed. I created a separate composite build with my repro project and the plugin project, imported it into IntelliJ, and can set breakpoints in the plugin while starting a debug of the dependencyUpdates task in the repro, and it all just works.

And there is, in fact, a modification of the configurations set during processing. When processing the testCompileClasspath configuration, this line triggers the addition of a tmpTestImplementation configuration:

Resolver.kt:279:

val lenient = copy.resolvedConfiguration.lenientConfiguration

That tmpTestImplemenation configuration is being added in gradle-pitest-plugin: PitestPlugin.groovy:267

Here's the call stack when that line is hit:

Stack Trace
doCall$original:267, PitestPlugin$_addJUnitPlatformLauncherDependencyIfNeeded_closure12$_closure16 (info.solidsoft.gradle.pitest)
doCall:-1, PitestPlugin$_addJUnitPlatformLauncherDependencyIfNeeded_closure12$_closure16 (info.solidsoft.gradle.pitest)
invokeSpecial:-1, DirectMethodHandle$Holder (java.lang.invoke)
invoke:-1, LambdaForm$MH/0x000000fc01098000 (java.lang.invoke)
invokeExact_MT:-1, Invokers$Holder (java.lang.invoke)
invokeImpl:154, DirectMethodHandleAccessor (jdk.internal.reflect)
invoke:103, DirectMethodHandleAccessor (jdk.internal.reflect)
invoke:580, Method (java.lang.reflect)
invoke:107, CachedMethod (org.codehaus.groovy.reflection)
doMethodInvoke:323, MetaMethod (groovy.lang)
invokeMethod:274, ClosureMetaClass (org.codehaus.groovy.runtime.metaclass)
invokeMethod:1030, MetaClassImpl (groovy.lang)
call:427, Closure (groovy.lang)
invokeCustom:50, ConvertedClosure (org.codehaus.groovy.runtime)
invoke:112, ConversionHandler (org.codehaus.groovy.runtime)
execute:-1, $Proxy124 (jdk.proxy1)
execute:285, ImmutableActionSet$SetWithFewActions (org.gradle.internal)
lambda$runDependencyActions$2:495, DefaultConfiguration (org.gradle.api.internal.artifacts.configurations)
execute:-1, DefaultConfiguration$$Lambda/0x000000fc016a79e8 (org.gradle.api.internal.artifacts.configurations)
runActionInHierarchy:1197, DefaultConfiguration (org.gradle.api.internal.artifacts.configurations)
runDependencyActions:493, DefaultConfiguration (org.gradle.api.internal.artifacts.configurations)
getDependencies:1906, DefaultConfiguration$ConfigurationResolvableDependencies (org.gradle.api.internal.artifacts.configurations)
getDependencies:-1, DefaultConfiguration$ConfigurationResolvableDependencies_Decorated (org.gradle.api.internal.artifacts.configurations)
getVersionedModuleDependencies:83, ImplicitDependencyManagementCollector (io.spring.gradle.dependencymanagement.internal)
processConfiguration:67, ImplicitDependencyManagementCollector (io.spring.gradle.dependencymanagement.internal)
accept:-1, ImplicitDependencyManagementCollector$$Lambda/0x000000fc01d6d7a0 (io.spring.gradle.dependencymanagement.internal)
forEach:75, Iterable (java.lang)
lambda$execute$0:61, ImplicitDependencyManagementCollector (io.spring.gradle.dependencymanagement.internal)
execute:-1, ImplicitDependencyManagementCollector$$Lambda/0x000000fc01cf9b78 (io.spring.gradle.dependencymanagement.internal)
execute:124, DefaultUserCodeApplicationContext$CurrentApplication$1 (org.gradle.internal.code)
dispatch:99, BroadcastDispatch$ActionInvocationHandler (org.gradle.internal.event)
dispatch:87, BroadcastDispatch$ActionInvocationHandler (org.gradle.internal.event)
dispatch:44, AbstractBroadcastDispatch (org.gradle.internal.event)
dispatch:268, BroadcastDispatch$SingletonDispatch (org.gradle.internal.event)
dispatch:170, BroadcastDispatch$SingletonDispatch (org.gradle.internal.event)
dispatch:84, AbstractBroadcastDispatch (org.gradle.internal.event)
dispatch:70, AbstractBroadcastDispatch (org.gradle.internal.event)
dispatch:380, BroadcastDispatch$CompositeDispatch (org.gradle.internal.event)
dispatch:272, BroadcastDispatch$CompositeDispatch (org.gradle.internal.event)
dispatch:153, ListenerBroadcast (org.gradle.internal.event)
dispatch:38, ListenerBroadcast (org.gradle.internal.event)
invoke:92, ProxyDispatchAdapter$DispatchingInvocationHandler (org.gradle.internal.dispatch)
beforeResolve:-1, $Proxy65 (jdk.proxy1)
runBeforeResolve:912, DefaultConfiguration (org.gradle.api.internal.artifacts.configurations)
access$1300:159, DefaultConfiguration (org.gradle.api.internal.artifacts.configurations)
call:781, DefaultConfiguration$1 (org.gradle.api.internal.artifacts.configurations)
call:777, DefaultConfiguration$1 (org.gradle.api.internal.artifacts.configurations)
execute:209, DefaultBuildOperationRunner$CallableBuildOperationWorker (org.gradle.internal.operations)
execute:204, DefaultBuildOperationRunner$CallableBuildOperationWorker (org.gradle.internal.operations)
execute:66, DefaultBuildOperationRunner$2 (org.gradle.internal.operations)
execute:59, DefaultBuildOperationRunner$2 (org.gradle.internal.operations)
execute:166, DefaultBuildOperationRunner (org.gradle.internal.operations)
execute:59, DefaultBuildOperationRunner (org.gradle.internal.operations)
call:53, DefaultBuildOperationRunner (org.gradle.internal.operations)
resolveGraphInBuildOperation:777, DefaultConfiguration (org.gradle.api.internal.artifacts.configurations)
lambda$resolveExclusivelyIfRequired$5:769, DefaultConfiguration (org.gradle.api.internal.artifacts.configurations)
apply:-1, DefaultConfiguration$$Lambda/0x000000fc016c55f8 (org.gradle.api.internal.artifacts.configurations)
update:509, DefaultProjectStateRegistry$CalculatedModelValueImpl (org.gradle.api.internal.project)
resolveExclusivelyIfRequired:764, DefaultConfiguration (org.gradle.api.internal.artifacts.configurations)
resolveGraphIfRequired:757, DefaultConfiguration (org.gradle.api.internal.artifacts.configurations)
access$1200:159, DefaultConfiguration (org.gradle.api.internal.artifacts.configurations)
getValue:731, DefaultConfiguration$ResolverResultsResolutionResultProvider (org.gradle.api.internal.artifacts.configurations)
getValue:692, DefaultConfiguration$ResolverResultsResolutionResultProvider (org.gradle.api.internal.artifacts.configurations)
getResolvedConfiguration:646, DefaultConfiguration (org.gradle.api.internal.artifacts.configurations)
getResolvedConfiguration:-1, DefaultUnlockedConfiguration_Decorated (org.gradle.api.internal.artifacts.configurations)
getCurrentCoordinates:279, Resolver (com.github.benmanes.gradle.versions.updates)
resolve:56, Resolver (com.github.benmanes.gradle.versions.updates)
resolve:101, DependencyUpdates (com.github.benmanes.gradle.versions.updates)
lambda 'forEach' in 'resolveProjects':86, DependencyUpdates (com.github.benmanes.gradle.versions.updates)
forEach:215, DependencyUpdates (com.github.benmanes.gradle.versions.updates)
resolveProjects:79, DependencyUpdates (com.github.benmanes.gradle.versions.updates)
run:49, DependencyUpdates (com.github.benmanes.gradle.versions.updates)
dependencyUpdates:140, DependencyUpdatesTask (com.github.benmanes.gradle.versions.updates)
invokeSpecial:-1, DirectMethodHandle$Holder (java.lang.invoke)
invoke:-1, LambdaForm$MH/0x000000fc01220800 (java.lang.invoke)
invokeExact_MT:-1, Invokers$Holder (java.lang.invoke)
invokeImpl:153, DirectMethodHandleAccessor (jdk.internal.reflect)
invoke:103, DirectMethodHandleAccessor (jdk.internal.reflect)
invoke:580, Method (java.lang.reflect)
invoke:125, JavaMethod (org.gradle.internal.reflect)
doExecute:58, StandardTaskAction (org.gradle.api.internal.project.taskfactory)
execute:51, StandardTaskAction (org.gradle.api.internal.project.taskfactory)
execute:29, StandardTaskAction (org.gradle.api.internal.project.taskfactory)
run:244, TaskExecution$3 (org.gradle.api.internal.tasks.execution)
execute:29, DefaultBuildOperationRunner$1 (org.gradle.internal.operations)
execute:26, DefaultBuildOperationRunner$1 (org.gradle.internal.operations)
execute:66, DefaultBuildOperationRunner$2 (org.gradle.internal.operations)
execute:59, DefaultBuildOperationRunner$2 (org.gradle.internal.operations)
execute:166, DefaultBuildOperationRunner (org.gradle.internal.operations)
execute:59, DefaultBuildOperationRunner (org.gradle.internal.operations)
run:47, DefaultBuildOperationRunner (org.gradle.internal.operations)
executeAction:229, TaskExecution (org.gradle.api.internal.tasks.execution)
executeActions:212, TaskExecution (org.gradle.api.internal.tasks.execution)
executeWithPreviousOutputFiles:195, TaskExecution (org.gradle.api.internal.tasks.execution)
execute:162, TaskExecution (org.gradle.api.internal.tasks.execution)
executeInternal:105, ExecuteStep (org.gradle.internal.execution.steps)
access$000:44, ExecuteStep (org.gradle.internal.execution.steps)
call:59, ExecuteStep$1 (org.gradle.internal.execution.steps)
call:56, ExecuteStep$1 (org.gradle.internal.execution.steps)
execute:209, DefaultBuildOperationRunner$CallableBuildOperationWorker (org.gradle.internal.operations)
execute:204, DefaultBuildOperationRunner$CallableBuildOperationWorker (org.gradle.internal.operations)
execute:66, DefaultBuildOperationRunner$2 (org.gradle.internal.operations)
execute:59, DefaultBuildOperationRunner$2 (org.gradle.internal.operations)
execute:166, DefaultBuildOperationRunner (org.gradle.internal.operations)
execute:59, DefaultBuildOperationRunner (org.gradle.internal.operations)
call:53, DefaultBuildOperationRunner (org.gradle.internal.operations)
execute:56, ExecuteStep (org.gradle.internal.execution.steps)
execute:44, ExecuteStep (org.gradle.internal.execution.steps)
execute:42, CancelExecutionStep (org.gradle.internal.execution.steps)
executeWithoutTimeout:75, TimeoutStep (org.gradle.internal.execution.steps)
execute:55, TimeoutStep (org.gradle.internal.execution.steps)
execute:50, PreCreateOutputParentsStep (org.gradle.internal.execution.steps)
execute:28, PreCreateOutputParentsStep (org.gradle.internal.execution.steps)
execute:67, RemovePreviousOutputsStep (org.gradle.internal.execution.steps)
execute:37, RemovePreviousOutputsStep (org.gradle.internal.execution.steps)
execute:61, BroadcastChangingOutputsStep (org.gradle.internal.execution.steps)
execute:26, BroadcastChangingOutputsStep (org.gradle.internal.execution.steps)
execute:69, CaptureOutputsAfterExecutionStep (org.gradle.internal.execution.steps)
execute:46, CaptureOutputsAfterExecutionStep (org.gradle.internal.execution.steps)
execute:40, ResolveInputChangesStep (org.gradle.internal.execution.steps)
execute:29, ResolveInputChangesStep (org.gradle.internal.execution.steps)
executeWithoutCache:189, BuildCacheStep (org.gradle.internal.execution.steps)
lambda$execute$1:75, BuildCacheStep (org.gradle.internal.execution.steps)
apply:-1, BuildCacheStep$$Lambda/0x000000fc01d63a10 (org.gradle.internal.execution.steps)
fold:175, Either$Right (org.gradle.internal)
fold:62, CachingState (org.gradle.internal.execution.caching)
execute:73, BuildCacheStep (org.gradle.internal.execution.steps)
execute:48, BuildCacheStep (org.gradle.internal.execution.steps)
execute:46, StoreExecutionStateStep (org.gradle.internal.execution.steps)
execute:35, StoreExecutionStateStep (org.gradle.internal.execution.steps)
executeBecause:75, SkipUpToDateStep (org.gradle.internal.execution.steps)
lambda$execute$2:53, SkipUpToDateStep (org.gradle.internal.execution.steps)
get:-1, SkipUpToDateStep$$Lambda/0x000000fc01cdd598 (org.gradle.internal.execution.steps)
orElseGet:364, Optional (java.util)
execute:53, SkipUpToDateStep (org.gradle.internal.execution.steps)
execute:35, SkipUpToDateStep (org.gradle.internal.execution.steps)
execute:37, MarkSnapshottingInputsFinishedStep (org.gradle.internal.execution.steps.legacy)
execute:27, MarkSnapshottingInputsFinishedStep (org.gradle.internal.execution.steps.legacy)
executeDelegate:49, ResolveIncrementalCachingStateStep (org.gradle.internal.execution.steps)
executeDelegate:27, ResolveIncrementalCachingStateStep (org.gradle.internal.execution.steps)
execute:71, AbstractResolveCachingStateStep (org.gradle.internal.execution.steps)
execute:39, AbstractResolveCachingStateStep (org.gradle.internal.execution.steps)
execute:65, ResolveChangesStep (org.gradle.internal.execution.steps)
execute:36, ResolveChangesStep (org.gradle.internal.execution.steps)
execute:107, ValidateStep (org.gradle.internal.execution.steps)
execute:56, ValidateStep (org.gradle.internal.execution.steps)
execute:64, AbstractCaptureStateBeforeExecutionStep (org.gradle.internal.execution.steps)
execute:43, AbstractCaptureStateBeforeExecutionStep (org.gradle.internal.execution.steps)
executeWithNonEmptySources:125, AbstractSkipEmptyWorkStep (org.gradle.internal.execution.steps)
execute:56, AbstractSkipEmptyWorkStep (org.gradle.internal.execution.steps)
execute:36, AbstractSkipEmptyWorkStep (org.gradle.internal.execution.steps)
execute:38, MarkSnapshottingInputsStartedStep (org.gradle.internal.execution.steps.legacy)
execute:36, LoadPreviousExecutionStateStep (org.gradle.internal.execution.steps)
execute:23, LoadPreviousExecutionStateStep (org.gradle.internal.execution.steps)
execute:75, HandleStaleOutputsStep (org.gradle.internal.execution.steps)
execute:41, HandleStaleOutputsStep (org.gradle.internal.execution.steps)
lambda$execute$0:35, AssignMutableWorkspaceStep (org.gradle.internal.execution.steps)
executeInWorkspace:-1, AssignMutableWorkspaceStep$$Lambda/0x000000fc01cbb698 (org.gradle.internal.execution.steps)
withWorkspace:289, TaskExecution$4 (org.gradle.api.internal.tasks.execution)
execute:31, AssignMutableWorkspaceStep (org.gradle.internal.execution.steps)
execute:22, AssignMutableWorkspaceStep (org.gradle.internal.execution.steps)
execute:40, ChoosePipelineStep (org.gradle.internal.execution.steps)
execute:23, ChoosePipelineStep (org.gradle.internal.execution.steps)
lambda$execute$2:67, ExecuteWorkBuildOperationFiringStep (org.gradle.internal.execution.steps)
get:-1, ExecuteWorkBuildOperationFiringStep$$Lambda/0x000000fc014ddb30 (org.gradle.internal.execution.steps)
orElseGet:364, Optional (java.util)
execute:67, ExecuteWorkBuildOperationFiringStep (org.gradle.internal.execution.steps)
execute:39, ExecuteWorkBuildOperationFiringStep (org.gradle.internal.execution.steps)
execute:46, IdentityCacheStep (org.gradle.internal.execution.steps)
execute:34, IdentityCacheStep (org.gradle.internal.execution.steps)
execute:48, IdentifyStep (org.gradle.internal.execution.steps)
execute:35, IdentifyStep (org.gradle.internal.execution.steps)
execute:61, DefaultExecutionEngine$1 (org.gradle.internal.execution.impl)
executeIfValid:127, ExecuteActionsTaskExecuter (org.gradle.api.internal.tasks.execution)
execute:116, ExecuteActionsTaskExecuter (org.gradle.api.internal.tasks.execution)
execute:46, FinalizePropertiesTaskExecuter (org.gradle.api.internal.tasks.execution)
execute:51, ResolveTaskExecutionModeExecuter (org.gradle.api.internal.tasks.execution)
execute:57, SkipTaskWithNoActionsExecuter (org.gradle.api.internal.tasks.execution)
execute:74, SkipOnlyIfTaskExecuter (org.gradle.api.internal.tasks.execution)
execute:36, CatchExceptionTaskExecuter (org.gradle.api.internal.tasks.execution)
executeTask:77, EventFiringTaskExecuter$1 (org.gradle.api.internal.tasks.execution)
call:55, EventFiringTaskExecuter$1 (org.gradle.api.internal.tasks.execution)
call:52, EventFiringTaskExecuter$1 (org.gradle.api.internal.tasks.execution)
execute:209, DefaultBuildOperationRunner$CallableBuildOperationWorker (org.gradle.internal.operations)
execute:204, DefaultBuildOperationRunner$CallableBuildOperationWorker (org.gradle.internal.operations)
execute:66, DefaultBuildOperationRunner$2 (org.gradle.internal.operations)
execute:59, DefaultBuildOperationRunner$2 (org.gradle.internal.operations)
execute:166, DefaultBuildOperationRunner (org.gradle.internal.operations)
execute:59, DefaultBuildOperationRunner (org.gradle.internal.operations)
call:53, DefaultBuildOperationRunner (org.gradle.internal.operations)
execute:52, EventFiringTaskExecuter (org.gradle.api.internal.tasks.execution)
execute:42, LocalTaskNodeExecutor (org.gradle.execution.plan)
execute:331, DefaultTaskExecutionGraph$InvokeNodeExecutorsAction (org.gradle.execution.taskgraph)
execute:318, DefaultTaskExecutionGraph$InvokeNodeExecutorsAction (org.gradle.execution.taskgraph)
lambda$execute$0:314, DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction (org.gradle.execution.taskgraph)
run:-1, DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction$$Lambda/0x000000fc01c93000 (org.gradle.execution.taskgraph)
with:85, CurrentBuildOperationRef (org.gradle.internal.operations)
execute:314, DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction (org.gradle.execution.taskgraph)
execute:303, DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction (org.gradle.execution.taskgraph)
execute:459, DefaultPlanExecutor$ExecutorWorker (org.gradle.execution.plan)
run:376, DefaultPlanExecutor$ExecutorWorker (org.gradle.execution.plan)
onExecute:64, ExecutorPolicy$CatchAndRecordFailures (org.gradle.internal.concurrent)
run:48, AbstractManagedExecutor$1 (org.gradle.internal.concurrent)
runWorker:1144, ThreadPoolExecutor (java.util.concurrent)
run:642, ThreadPoolExecutor$Worker (java.util.concurrent)
runWith:1588, Thread (java.lang)
run:1575, Thread (java.lang)

You can see the execution flows through the io.spring.dependency-management plugin. And if I remove that plugin from my project, that configuration in the pitest plugin doesn't run and the tmpTestImplementation configuration is not added.

I guess I'll have to look at the io.spring.dependency-management plugin next.

@ben-manes
Copy link
Owner

ben-manes commented Jan 28, 2025

It seems odd that they would add a configuration during dependency resolution? The withDependencies would be invoked when that configuration is being resolved, which then adds another configuration with more dependencies to the project. I could understand if it was a detached configuration, but here it is added to the project's configuration container at execution time which is invalid. If they are trying to resolve the configuration, then how we do it would make more sense? They modify the test configuration by adding a dependency to it, which may by chance be done before it was resolved since that likely depends on the testImplementation being evaluated. It seems like a very weird hack to me to implicitly add a dependency when it would otherwise be explicit. It is a confusing approach which I think typically a default dependency would be more straightforward.

I know a little about the io.spring.dependency-management as it handles BOM management. There was previously an issue with their resolution strategy not allowing us to query for dynamic versions. They were very responsive, helpful, and quickly resolved their issues.

I would guess that the spring plugin is adding the junit dependency via its BOM, and that triggers pitest to do this crazy logic.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants