Skip to content

Commit 2cbfd04

Browse files
committed
IDEA-383440 Identify data services entities and mark them
Change-Id: I32f0b99e90c492cb48629749e92ed3131eade926
1 parent f386554 commit 2cbfd04

File tree

7 files changed

+2210
-1147
lines changed

7 files changed

+2210
-1147
lines changed

plugins/gradle/generated/org/jetbrains/plugins/gradle/model/projectModel/impl/MetadataStorageImpl.kt

Lines changed: 1984 additions & 1145 deletions
Large diffs are not rendered by default.

plugins/gradle/plugin-resources/META-INF/plugin.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@
108108

109109
<externalProjectDataService implementation="org.jetbrains.plugins.gradle.service.syncAction.impl.bridge.GradleBridgeProjectDataService"/>
110110
<externalWorkspaceDataService implementation="org.jetbrains.plugins.gradle.service.syncAction.impl.bridge.GradleBridgeModuleDataService"/>
111+
<externalProjectDataService implementation="org.jetbrains.plugins.gradle.service.syncAction.impl.bridge.GradleBridgeFinalizerDataService"/>
111112

112113
<postStartupActivity implementation="org.jetbrains.plugins.gradle.service.project.GradleStartupActivity"/>
113114
<backgroundPostStartupActivity implementation="org.jetbrains.plugins.gradle.service.project.GradleVersionUpdateStartupActivity"/>

plugins/gradle/src/org/jetbrains/plugins/gradle/service/syncAction/GradleSyncPhase.kt

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ sealed interface GradleSyncPhase : Comparable<GradleSyncPhase> {
4848
}
4949
}
5050

51+
sealed interface DataServices: GradleSyncPhase
52+
5153
companion object {
5254

5355
/**
@@ -106,6 +108,9 @@ sealed interface GradleSyncPhase : Comparable<GradleSyncPhase> {
106108
*/
107109
@JvmField
108110
val ADDITIONAL_MODEL_PHASE: GradleSyncPhase = GradleModelFetchPhase.ADDITIONAL_MODEL_PHASE.asSyncPhase()
111+
112+
@JvmField
113+
val DATA_SERVICES_PHASE: GradleSyncPhase = GradleDataServicesSyncPhase()
109114
}
110115
}
111116

@@ -122,7 +127,8 @@ private class GradleStaticSyncPhase(
122127
return when (other) {
123128
is GradleStaticSyncPhase -> order.compareTo(other.order)
124129
is GradleBaseScriptSyncPhase -> -1
125-
is GradleDynamicSyncPhase -> -1
130+
is GradleDynamicSyncPhase,
131+
is GradleDataServicesSyncPhase -> -1
126132
}
127133
}
128134

@@ -153,6 +159,7 @@ private class GradleDynamicSyncPhase(
153159
is GradleStaticSyncPhase -> 1
154160
is GradleBaseScriptSyncPhase -> 1
155161
is GradleDynamicSyncPhase -> modelFetchPhase.compareTo(other.modelFetchPhase)
162+
is GradleDataServicesSyncPhase -> -1
156163
}
157164
}
158165

@@ -178,7 +185,16 @@ private data object GradleBaseScriptSyncPhase: GradleSyncPhase.BaseScript {
178185
return when (other) {
179186
is GradleStaticSyncPhase -> 1
180187
is GradleBaseScriptSyncPhase -> 0
181-
is GradleDynamicSyncPhase -> -1
188+
is GradleDynamicSyncPhase,
189+
is GradleDataServicesSyncPhase -> -1
182190
}
183191
}
192+
}
193+
194+
private class GradleDataServicesSyncPhase: GradleSyncPhase.DataServices {
195+
196+
override val name: String = "DATA_SERVICES"
197+
198+
override fun compareTo(other: GradleSyncPhase): Int =
199+
if (other is GradleDataServicesSyncPhase) 0 else 1
184200
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
2+
package org.jetbrains.plugins.gradle.service.syncAction.impl.bridge
3+
4+
import com.intellij.openapi.externalSystem.model.Key
5+
import org.jetbrains.annotations.ApiStatus
6+
7+
@ApiStatus.Internal
8+
object GradleBridgeFinalizerData {
9+
10+
val KEY: Key<GradleBridgeFinalizerData> = Key.create(GradleBridgeFinalizerData::class.java, Int.MAX_VALUE)
11+
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
/*
2+
* Copyright (C) 2025 The Android Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.jetbrains.plugins.gradle.service.syncAction.impl.bridge
17+
18+
import com.intellij.openapi.externalSystem.model.DataNode
19+
import com.intellij.openapi.externalSystem.model.project.ProjectData
20+
import com.intellij.openapi.externalSystem.service.project.IdeModifiableModelsProvider
21+
import com.intellij.openapi.externalSystem.service.project.manage.AbstractProjectDataService
22+
import com.intellij.openapi.project.Project
23+
import com.intellij.openapi.util.registry.Registry
24+
import com.intellij.platform.workspace.storage.EntitySource
25+
import com.intellij.platform.workspace.storage.WorkspaceEntity
26+
import com.intellij.platform.workspace.storage.WorkspaceEntityBuilder
27+
import com.intellij.platform.workspace.storage.WorkspaceEntityWithSymbolicId
28+
import com.intellij.platform.workspace.storage.impl.WorkspaceEntityBase
29+
import org.jetbrains.plugins.gradle.service.syncAction.GradleEntitySource
30+
import org.jetbrains.plugins.gradle.service.syncAction.GradleSyncPhase
31+
32+
33+
class GradleBridgeFinalizerDataService : AbstractProjectDataService<GradleBridgeFinalizerData, Unit>() {
34+
override fun getTargetDataKey() = GradleBridgeFinalizerData.KEY
35+
36+
override fun postProcess(toImport: Collection<DataNode<GradleBridgeFinalizerData?>?>,
37+
projectData: ProjectData?,
38+
project: Project,
39+
modelsProvider: IdeModifiableModelsProvider) {
40+
if (!Registry.`is`("gradle.phased.sync.bridge.disabled") || projectData == null) return
41+
42+
val currentStorage = modelsProvider.actualStorageBuilder
43+
44+
val storageBeforeDataServices = modelsProvider.getUserData(SYNC_STORAGE_SNAPSHOT_BEFORE_DATA_SERVICES)!!
45+
val index = storageBeforeDataServices.entitiesBySource {
46+
sourceFilter(it, projectData.linkedExternalProjectPath)
47+
}.associateBy {
48+
WorkspaceEntityForLookup(it)
49+
}
50+
51+
// Go over all the relevant entities and mark the ones that are not originally in the storage before data services execution
52+
// with an explicit data service source. This is required because some entities otherwise inherit from their parents which are
53+
// marked entity sources with explicit phases.
54+
currentStorage.entitiesBySource {
55+
sourceFilter(it, projectData.linkedExternalProjectPath)
56+
}.filter {
57+
if (it is WorkspaceEntityWithSymbolicId) {
58+
storageBeforeDataServices.resolve(it.symbolicId)
59+
} else {
60+
index[WorkspaceEntityForLookup(it)]
61+
} == null
62+
}.forEach {
63+
currentStorage.modifyEntity(WorkspaceEntityBuilder::class.java, it) {
64+
entitySource = DataServiceEntitySource(projectData.linkedExternalProjectPath)
65+
}
66+
}
67+
}
68+
69+
private fun sourceFilter(source: EntitySource, linkedExternalProjectPath: String) =
70+
source is GradleEntitySource && source.projectPath == linkedExternalProjectPath
71+
72+
/** This is used for looking up entities without a symbolic ID. */
73+
private class WorkspaceEntityForLookup(entity: WorkspaceEntity) {
74+
val data = (entity as WorkspaceEntityBase).getData()
75+
76+
override fun equals(other: Any?): Boolean {
77+
if (this === other) return true
78+
if (javaClass != other?.javaClass) return false
79+
return data.equalsByKey((other as WorkspaceEntityForLookup).data)
80+
}
81+
82+
override fun hashCode() = data.hashCodeByKey()
83+
}
84+
85+
private data class DataServiceEntitySource(override val projectPath: String): GradleEntitySource {
86+
override val phase = GradleSyncPhase.DATA_SERVICES_PHASE
87+
}
88+
}
89+
90+

plugins/gradle/src/org/jetbrains/plugins/gradle/service/syncAction/impl/bridge/GradleBridgeProjectDataService.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,13 @@ import com.intellij.openapi.externalSystem.service.project.manage.AbstractProjec
99
import com.intellij.openapi.project.Project
1010
import com.intellij.openapi.util.registry.Registry
1111
import com.intellij.platform.workspace.jps.entities.ModuleEntity
12+
import com.intellij.platform.workspace.storage.ImmutableEntityStorage
1213
import com.intellij.platform.workspace.storage.entities
1314
import org.jetbrains.annotations.ApiStatus
15+
import com.intellij.openapi.util.Key as UserDataKey
16+
17+
internal val SYNC_STORAGE_SNAPSHOT_BEFORE_DATA_SERVICES =
18+
UserDataKey.create<ImmutableEntityStorage>("SYNC_STORAGE_SNAPSHOT_BEFORE_DATA_SERVICES")
1419

1520
@ApiStatus.Internal
1621
class GradleBridgeProjectDataService : AbstractProjectDataService<GradleBridgeData, Unit>() {
@@ -26,6 +31,10 @@ class GradleBridgeProjectDataService : AbstractProjectDataService<GradleBridgeDa
2631
if (!Registry.`is`("gradle.phased.sync.bridge.disabled")) {
2732
removeModulesFromModelProvider(modelsProvider)
2833
removeEntitiesFromWorkspaceModel(modelsProvider)
34+
} else {
35+
// If the entities are not cleaned up, store a snapshot of the storage to be used for later post processing in.
36+
// See [Int]
37+
modelsProvider.putUserData(SYNC_STORAGE_SNAPSHOT_BEFORE_DATA_SERVICES, modelsProvider.actualStorageBuilder.toSnapshot())
2938
}
3039
}
3140

plugins/gradle/testSources/org/jetbrains/plugins/gradle/importing/syncAction/GradlePhasedSyncTest.kt

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ import com.intellij.gradle.toolingExtension.modelAction.GradleModelFetchPhase
55
import com.intellij.openapi.observable.operation.OperationExecutionStatus
66
import com.intellij.openapi.progress.ProgressManager
77
import com.intellij.openapi.util.Disposer
8+
import com.intellij.openapi.util.registry.Registry
89
import com.intellij.openapi.util.use
10+
import com.intellij.platform.backend.workspace.workspaceModel
911
import com.intellij.platform.testFramework.assertion.WorkspaceAssertions
1012
import com.intellij.platform.testFramework.assertion.listenerAssertion.ListenerAssertion
1113
import com.intellij.platform.workspace.storage.toBuilder
@@ -14,6 +16,7 @@ import org.jetbrains.plugins.gradle.importing.TestModelProvider
1416
import org.jetbrains.plugins.gradle.importing.TestPhasedModel
1517
import org.jetbrains.plugins.gradle.service.project.DefaultProjectResolverContext
1618
import org.jetbrains.plugins.gradle.service.project.ProjectResolverContext
19+
import org.jetbrains.plugins.gradle.service.syncAction.GradleEntitySource
1720
import org.jetbrains.plugins.gradle.service.syncAction.GradleSyncPhase
1821
import org.jetbrains.plugins.gradle.service.syncAction.GradleSyncPhase.Dynamic.Companion.asSyncPhase
1922
import org.jetbrains.plugins.gradle.util.entity.GradleTestBridgeEntitySource
@@ -208,6 +211,7 @@ class GradlePhasedSyncTest : GradlePhasedSyncTestCase() {
208211
true -> allDynamicPhases
209212
else -> completedDynamicPhases
210213
}
214+
is GradleSyncPhase.DataServices -> error("Should not execute")
211215
}
212216
WorkspaceAssertions.assertEntities(myProject, expectedEntities.map { GradleTestEntityId(it) }) {
213217
"Entities should be created for completed phases.\n" +
@@ -271,6 +275,7 @@ class GradlePhasedSyncTest : GradlePhasedSyncTestCase() {
271275
is GradleSyncPhase.Static -> completedStaticPhases
272276
is GradleSyncPhase.BaseScript -> completedBaseScriptPhases
273277
is GradleSyncPhase.Dynamic -> completedDynamicPhases
278+
is GradleSyncPhase.DataServices -> error("Should not execute")
274279
}
275280
WorkspaceAssertions.assertEntities(myProject, expectedEntities.map { GradleTestEntityId(it) }) {
276281
"Bridge entities should be created for completed phases.\n" +
@@ -298,9 +303,101 @@ class GradlePhasedSyncTest : GradlePhasedSyncTestCase() {
298303
"Requested phases = $allPhases"
299304
"Completed phases = $completedPhases"
300305
}
306+
val dataServicesEntities = myProject.workspaceModel.currentSnapshot.entitiesBySource {
307+
it is GradleEntitySource && it.phase == GradleSyncPhase.DATA_SERVICES_PHASE
308+
}
309+
Assertions.assertTrue(dataServicesEntities.toList().isEmpty()) {
310+
"There should be no entities with phase ${GradleSyncPhase.DATA_SERVICES_PHASE}"
311+
}
301312
}
302313
}
303314

315+
@Test
316+
fun `test bridge entity contribution on Gradle sync phase with bridge disabled`() {
317+
repeat(2) { index ->
318+
Disposer.newDisposable().use { disposable ->
319+
Registry.get("gradle.phased.sync.bridge.disabled").setValue(true, disposable)
320+
val isSecondarySync = index == 1
321+
322+
val syncContributorAssertions = ListenerAssertion()
323+
val syncPhaseCompletionAssertions = ListenerAssertion()
324+
325+
val allPhases = DEFAULT_SYNC_PHASES
326+
val allStaticPhases = allPhases.filterIsInstance<GradleSyncPhase.Static>()
327+
val allDynamicPhases = allPhases.filterIsInstance<GradleSyncPhase.Dynamic>()
328+
val completedPhases = CopyOnWriteArrayList<GradleSyncPhase>()
329+
330+
for (phase in allPhases) {
331+
addSyncContributor(phase, disposable) { context, storage ->
332+
val builder = storage.toBuilder()
333+
syncContributorAssertions.trace {
334+
val entitySource = GradleTestEntitySource(context.projectPath, phase)
335+
builder addEntity GradleTestEntity(phase, entitySource)
336+
Assertions.assertTrue(completedPhases.add(phase)) {
337+
"The $phase should be completed only once."
338+
}
339+
}
340+
return@addSyncContributor builder.toSnapshot()
341+
}
342+
whenSyncPhaseCompleted(phase, disposable) { _ ->
343+
syncPhaseCompletionAssertions.trace {
344+
val completedStaticPhases = completedPhases.filterIsInstance<GradleSyncPhase.Static>()
345+
val completedBaseScriptPhases = completedPhases.filterIsInstance<GradleSyncPhase.BaseScript>()
346+
val completedDynamicPhases = completedPhases.filterIsInstance<GradleSyncPhase.Dynamic>()
347+
val expectedEntities = when (phase) {
348+
is GradleSyncPhase.Static -> when (isSecondarySync) {
349+
true -> completedStaticPhases + allDynamicPhases
350+
else -> completedStaticPhases
351+
}
352+
is GradleSyncPhase.BaseScript -> when (isSecondarySync) {
353+
true -> allStaticPhases + completedBaseScriptPhases + allDynamicPhases
354+
else -> completedBaseScriptPhases
355+
}
356+
is GradleSyncPhase.Dynamic -> when (isSecondarySync) {
357+
true -> allDynamicPhases
358+
else -> completedDynamicPhases
359+
}
360+
is GradleSyncPhase.DataServices -> error("Should not execute")
361+
}
362+
WorkspaceAssertions.assertEntities(myProject, expectedEntities.map { GradleTestEntityId(it) }) {
363+
"Entities should be created for completed phases.\n" +
364+
"Completed phases = $completedPhases\n"
365+
"isSecondarySync = $isSecondarySync"
366+
}
367+
}
368+
}
369+
}
370+
371+
initMultiModuleProject()
372+
importProject()
373+
assertMultiModuleProjectStructure()
374+
375+
syncContributorAssertions.assertListenerFailures()
376+
syncContributorAssertions.assertListenerState(allPhases.size) {
377+
"All requested sync phases should be handled."
378+
}
379+
syncPhaseCompletionAssertions.assertListenerFailures()
380+
syncPhaseCompletionAssertions.assertListenerState(allPhases.size) {
381+
"All requested sync phases should be completed."
382+
}
383+
384+
WorkspaceAssertions.assertEntities(myProject, allDynamicPhases.map { GradleTestEntityId(it) }) {
385+
"Entities should be created for completed phases.\n" +
386+
"Requested phases = $allPhases"
387+
"Completed phases = $completedPhases"
388+
}
389+
390+
val dataServicesEntities = myProject.workspaceModel.currentSnapshot.entitiesBySource {
391+
it is GradleEntitySource && it.phase == GradleSyncPhase.DATA_SERVICES_PHASE
392+
}
393+
Assertions.assertTrue(dataServicesEntities.toList().isNotEmpty()) {
394+
"There should be at least one entity with phase ${GradleSyncPhase.DATA_SERVICES_PHASE}"
395+
}
396+
}
397+
}
398+
}
399+
400+
304401
@Test
305402
fun `test phased Gradle sync for custom static phase without model provider`() {
306403
`test phased Gradle sync for custom phase without model provider`(

0 commit comments

Comments
 (0)