Skip to content
Open
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
1 change: 1 addition & 0 deletions plugins/gradle/plugin-resources/META-INF/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@
<syncContributor implementation="org.jetbrains.plugins.gradle.service.syncAction.impl.contributors.GradleContentRootSyncContributor"/>
<syncContributor implementation="org.jetbrains.plugins.gradle.service.syncAction.impl.contributors.GradleVersionCatalogSyncContributor"/>
<syncExtension implementation="org.jetbrains.plugins.gradle.service.syncAction.impl.extensions.GradleJpsSyncExtension"/>
<syncExtension implementation="org.jetbrains.plugins.gradle.service.syncAction.impl.extensions.GradleDependencySyncExtension"/>
<syncExtension implementation="org.jetbrains.plugins.gradle.service.syncAction.impl.extensions.GradleBaseSyncExtension"/>
<syncExtension implementation="org.jetbrains.plugins.gradle.service.syncAction.impl.extensions.GradleDependencySubstitutionSyncExtension"/>

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.plugins.gradle.service.syncAction.impl.extensions

import com.intellij.openapi.externalSystem.util.Order
import com.intellij.platform.workspace.jps.entities.InheritedSdkDependency
import com.intellij.platform.workspace.jps.entities.LibraryDependency
import com.intellij.platform.workspace.jps.entities.ModuleEntity
import com.intellij.platform.workspace.jps.entities.SdkDependency
import com.intellij.platform.workspace.jps.entities.modifyModuleEntity
import com.intellij.platform.workspace.storage.MutableEntityStorage
import com.intellij.platform.workspace.storage.createEntityTreeCopy
import org.jetbrains.plugins.gradle.service.project.ProjectResolverContext
import org.jetbrains.plugins.gradle.service.syncAction.GradleSyncExtension
import org.jetbrains.plugins.gradle.service.syncAction.GradleSyncPhase

/**
* Since dependencies are not expressed as entities, they are kept manually by this sync extension.
*
* By nature, this only preserves the already existing dependencies, and doesn't allow any cleanup of dependencies the entity source for
* each dependency is not known. Cleanup is expected to be done by data services instead.
*/
@Order(GradleDependencySyncExtension.ORDER)
class GradleDependencySyncExtension : GradleSyncExtension {
override fun updateProjectModel(
context: ProjectResolverContext,
syncStorage: MutableEntityStorage,
projectStorage: MutableEntityStorage,
phase: GradleSyncPhase,
) {
syncStorage.entitiesToReplace<ModuleEntity>(context, phase).forEach { moduleEntity ->
val existingDependencies = projectStorage.resolve(moduleEntity.symbolicId)?.dependencies ?: return@forEach
val syncDependencies = moduleEntity.dependencies.toHashSet()
val explicitSdk = moduleEntity.dependencies.find { it is SdkDependency }

syncStorage.modifyModuleEntity(moduleEntity) {
// Clean up all existing SDKs and set the explicitly set SDK if known already in the sync storage,
// otherwise use the project's explicit SDK, and otherwise inherit.
dependencies.removeAll { it is InheritedSdkDependency || it is SdkDependency }
dependencies.add(explicitSdk ?: existingDependencies.find { it is SdkDependency } ?: InheritedSdkDependency)
val dependenciesToAdd = existingDependencies.filterNot { syncDependencies.contains(it) || it is SdkDependency || it is InheritedSdkDependency }
dependencies.addAll(dependenciesToAdd)
// Add the corresponding library entities from project storage to sync storage as well.
dependenciesToAdd
.filterIsInstance<LibraryDependency>()
.filterNot { syncStorage.contains(it.library) }
.forEach {
val existingProjectEntity = projectStorage.resolve(it.library)
existingProjectEntity?.let { syncStorage.addEntity(it.createEntityTreeCopy())}
}
}
}
}

companion object {

const val ORDER: Int = GradleBaseSyncExtension.ORDER - 500
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import com.intellij.openapi.roots.libraries.LibraryTablesRegistrar;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.testFramework.RunAll;
import com.intellij.util.ArrayUtil;
Expand All @@ -29,6 +30,7 @@
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.jps.util.JpsPathUtil;
import org.jetbrains.plugins.gradle.frameworkSupport.buildscript.GradleBuildScriptBuilderUtil;
import org.jetbrains.plugins.gradle.service.resolve.VersionCatalogsLocator;
import org.jetbrains.plugins.gradle.settings.GradleSystemSettings;
Expand Down Expand Up @@ -483,6 +485,44 @@ public void testLocalFileDepsImportedAsModuleLibraries() throws Exception {
}
}

@Test
public void testLocalFileDepsImportedAsModuleLibraries_nonExistentPath() throws Exception {
Registry.get("gradle.phased.sync.bridge.disabled").setValue(true, getTestRootDisposable());

var jarPath = "deps/dep.jar";
var expectedPath = JpsPathUtil.getLibraryRootUrl(getProjectPath(jarPath));

String config = createBuildScriptBuilder()
.allprojects(p -> {
p
.withJavaPlugin()
.addImplementationDependency(p.code("files('" + jarPath + "')"));
})
.generate();

importProject(config);

assertModules("project", "project.main", "project.test");

var moduleLibDeps = getModuleLibDeps("project.main", "Gradle: dep.jar");
assertEquals("Should have a single module level dependency", 1, moduleLibDeps.size());

var libDep = moduleLibDeps.getFirst();
assertTrue("Dependency must be module level: " + libDep.toString(), libDep.isModuleLevel());
assertEquals("URLs must be in the correct format", expectedPath, libDep.getLibrary().getUrls(OrderRootType.CLASSES)[0]);

// Try another import attempt and make sure it doesn't throw anything
importProject();
assertModules("project", "project.main", "project.test");

moduleLibDeps = getModuleLibDeps("project.main", "Gradle: dep.jar");
assertEquals("Should have a single module level dependency", 1, moduleLibDeps.size());

libDep = moduleLibDeps.getFirst();
assertTrue("Dependency must be module level: " + libDep.toString(), libDep.isModuleLevel());
assertEquals("URLs must be in the correct format", expectedPath, libDep.getLibrary().getUrls(OrderRootType.CLASSES)[0]);
}

@Test
public void testProjectWithUnresolvedDependency() throws Exception {
final VirtualFile depJar = createProjectJarSubFile("lib/dep/dep/1.0/dep-1.0.jar");
Expand Down
Loading