[devkit] IJPL-158473 rewrite IntelliJPlatformAttachSourcesProvider; attach sources for bundled plugins and local IDE

(cherry picked from commit bd26f2b62cc44770d5c325b5d133d28f39db842c)

IJ-CR-140104

GitOrigin-RevId: 2927df3806baf020de0824f4b43158c8c4cadb21
This commit is contained in:
Jakub Chrzanowski
2024-07-17 00:42:52 +02:00
committed by intellij-monorepo-bot
parent e6c0b47b6d
commit 5e46db7296
2 changed files with 154 additions and 53 deletions

View File

@@ -89,6 +89,15 @@ public enum IntelliJPlatformProduct {
return IDEA;
}
public static @Nullable IntelliJPlatformProduct fromProductCode(String productCode) {
for (IntelliJPlatformProduct product : values()) {
if (Objects.equals(product.myProductCode, productCode)) {
return product;
}
}
return null;
}
public static @Nullable IntelliJPlatformProduct fromMavenCoordinates(String groupId, String artifactId) {
return ContainerUtil.find(values(), product -> Objects.equals(product.getMavenCoordinates(), groupId + ":" + artifactId));
}

View File

@@ -13,9 +13,13 @@ import com.intellij.openapi.vfs.VfsUtilCore
import com.intellij.openapi.vfs.findFile
import com.intellij.psi.PsiFile
import org.jetbrains.idea.devkit.projectRoots.IntelliJPlatformProduct
import org.jetbrains.idea.devkit.run.ProductInfo
import org.jetbrains.idea.devkit.run.loadProductInfo
import org.jetbrains.plugins.gradle.execution.build.CachedModuleDataFinder
import org.jetbrains.plugins.gradle.util.GradleDependencySourceDownloader
import java.io.File
import kotlin.io.path.Path
import kotlin.io.path.pathString
/**
* Attaches sources to the IntelliJ Platform dependencies in projects using IntelliJ Platform Gradle Plugin 2.x.
@@ -24,18 +28,68 @@ import java.io.File
*/
internal class IntelliJPlatformAttachSourcesProvider : AttachSourcesProvider {
override fun getActions(orderEntries: MutableList<out LibraryOrderEntry>, psiFile: PsiFile): List<AttachSourcesAction> {
// Search for a product that matches any of the entry coordinates. Return both product and coordinates, to refer to the same version.
val (product, libraryCoordinates) = orderEntries.mapNotNull {
it.library?.getMavenCoordinates()
}.firstNotNullOfOrNull { coordinates ->
val product = IntelliJPlatformProduct.fromMavenCoordinates(coordinates.groupId, coordinates.artifactId)
?: IntelliJPlatformProduct.fromCdnCoordinates(coordinates.groupId, coordinates.artifactId)
if (product == null) {
return@firstNotNullOfOrNull null
}
product to coordinates
} ?: return emptyList()
override fun getActions(orderEntries: MutableList<out LibraryOrderEntry>, psiFile: PsiFile) =
orderEntries
.mapNotNull { it.library?.getMavenCoordinates() }
.firstNotNullOfOrNull { coordinates -> createAction(coordinates, psiFile) }
.let { listOfNotNull(it) }
private fun createAction(coordinates: MavenCoordinates, psiFile: PsiFile): AttachSourcesAction? {
val product = IntelliJPlatformProduct.fromMavenCoordinates(coordinates.groupId, coordinates.artifactId)
?: IntelliJPlatformProduct.fromCdnCoordinates(coordinates.groupId, coordinates.artifactId)
// IntelliJ Platform dependency, such as `com.jetbrains.intellij.idea:ideaIC:2023.2.7` or `idea:ideaIC:2023.2.7`
val isIntelliJPlatform = product != null
// IntelliJ Platform bundled plugin, such as `localIde:IC:2023.2.7+445`
val isLocalIntelliJPlatform = coordinates.groupId == "localIde"
// IntelliJ Platform bundled plugin, such as `bundledPlugin:Git4Idea:2023.2.7+445`
val isBundledPlugin = coordinates.groupId == "bundledPlugin"
return when {
isIntelliJPlatform -> resolveIntelliJPlatformAction(psiFile, requireNotNull(product), coordinates.version)
isLocalIntelliJPlatform -> createAttachLocalPlatformSourcesAction(psiFile, coordinates)
isBundledPlugin -> createAttachBundledPluginSourcesAction(psiFile, coordinates)
else -> null
}
}
/**
* Resolve and attach IntelliJ Platform sources to the currently handled dependency in a requested version.
*
* Requests PyCharm Community sources if PyCharm Community or PyCharm Professional.
* Requests IntelliJ IDEA Ultimate sources if IntelliJ IDEA Ultimate 2024.2+.
* In all other cases, requests IntelliJ IDEA Community sources.
*
* If the LSP API class is detected while targeting IntelliJ IDEA Ultimate <2024.2, attaches bundled LSP API sources archive file.
*
* @param psiFile The PSI file that represents the currently handled class.
* @param product The IntelliJ Platform defined by the dependency entry.
* @param version The version of the product.
*/
private fun resolveIntelliJPlatformAction(
psiFile: PsiFile,
product: IntelliJPlatformProduct,
version: String,
): AttachSourcesAction? {
val productInfo = resolveProductInfo(psiFile) ?: return null
val majorVersion = productInfo.buildNumber.substringBefore('.').toInt()
val productCoordinates =
when (product) {
// For PyCharm Community and PyCharm Professional, we use PC sources.
IntelliJPlatformProduct.PYCHARM, IntelliJPlatformProduct.PYCHARM_PC -> IntelliJPlatformProduct.PYCHARM_PC
// IntelliJ IDEA Ultimate has sources published since 242; otherwise we use IC.
IntelliJPlatformProduct.IDEA -> when {
majorVersion >= 242 -> IntelliJPlatformProduct.IDEA
else -> IntelliJPlatformProduct.IDEA_IC
}
// Any other IntelliJ Platform should use IC
else -> IntelliJPlatformProduct.IDEA_IC
}.mavenCoordinates ?: return null
// We're checking if the compiled class belongs to the `/com/intellij/platform/lsp/` package, but not `.../impl` as it's not part of the API
val isLspApi = with(psiFile.virtualFile.path.substringAfter('!')) {
@@ -46,60 +100,47 @@ internal class IntelliJPlatformAttachSourcesProvider : AttachSourcesProvider {
} && product == IntelliJPlatformProduct.IDEA // LSP API sources are provided only with IU
}
val action = when {
isLspApi -> createAttachLSPSourcesAction(psiFile)
else -> createAttachPlatformSourcesAction(psiFile, product, libraryCoordinates)
}
return when {
// We're handing LSP API class, but IU is lower than 242 -> attach a standalone sources file
isLspApi && majorVersion < 242 -> createAttachLSPSourcesAction(psiFile)
return action?.let { listOf(it) } ?: emptyList()
// Create the actual IntelliJ Platform sources attaching action
else -> createAttachPlatformSourcesAction(psiFile, productCoordinates, version)
}
}
/**
* Resolve and attach IntelliJ IDEA Community or PyCharm Community sources to the IntelliJ Platform dependency in requested version.
* Creates an action to attach sources of a local IntelliJ Platform.
*
* @param psiFile The PSI file representing the currently handled class.
* @param coordinates The Maven coordinates of the IntelliJ Platform whose sources need to be attached.
*/
private fun createAttachPlatformSourcesAction(
psiFile: PsiFile,
product: IntelliJPlatformProduct,
libraryCoordinates: MavenCoordinates,
): AttachSourcesAction? {
val productCoordinates =
when (product) {
IntelliJPlatformProduct.PYCHARM, IntelliJPlatformProduct.PYCHARM_PC -> IntelliJPlatformProduct.PYCHARM_PC
else -> IntelliJPlatformProduct.IDEA_IC
}.mavenCoordinates ?: return null
private fun createAttachLocalPlatformSourcesAction(psiFile: PsiFile, coordinates: MavenCoordinates): AttachSourcesAction? {
val product = IntelliJPlatformProduct.fromProductCode(coordinates.artifactId) ?: return null
val version = coordinates.version.substringBefore('+')
return object : AttachSourcesAction {
override fun getName() = DevKitGradleBundle.message("attachSources.action.name")
return resolveIntelliJPlatformAction(psiFile, product, version)
}
override fun getBusyText() = DevKitGradleBundle.message("attachSources.action.busyText")
/**
* Creates an action to attach sources of bundled plugins for the IntelliJ Platform.
*
* @param psiFile The PSI file that represents the currently handled class.
* @param coordinates The Maven coordinates of the bundled plugin whose sources need to be attached.
*/
private fun createAttachBundledPluginSourcesAction(psiFile: PsiFile, coordinates: MavenCoordinates): AttachSourcesAction? {
val productInfo = resolveProductInfo(psiFile) ?: return null
val product = IntelliJPlatformProduct.fromProductCode(productInfo.productCode) ?: return null
val version = coordinates.version.substringBefore('+')
override fun perform(orderEntries: MutableList<out LibraryOrderEntry>): ActionCallback {
val externalProjectPath = CachedModuleDataFinder.getGradleModuleData(orderEntries.first().ownerModule)?.directoryToRunTask
?: return ActionCallback.REJECTED
val executionResult = ActionCallback()
val project = psiFile.project
val sourceArtifactNotation = "$productCoordinates:${libraryCoordinates.version}:sources"
GradleDependencySourceDownloader.downloadSources(project, name, sourceArtifactNotation, externalProjectPath).whenComplete { path, error ->
if (error != null) {
executionResult.setRejected()
}
else {
attachSources(path, orderEntries) {
executionResult.setDone()
}
}
}
return executionResult
}
}
return resolveIntelliJPlatformAction(psiFile, product, version)
}
/**
* When targeting IntelliJ IDEA Ultimate, it is possible to attach LSP module sources.
* If the compiled class belongs to `com/intellij/platform/lsp/`, suggest attaching relevant ZIP archive with LSP sources.
*
* @param psiFile The PSI file that represents the currently handled class.
*/
private fun createAttachLSPSourcesAction(psiFile: PsiFile): AttachSourcesAction? {
val jarFile = VfsUtilCore.getVirtualFileForJar(psiFile.virtualFile) ?: return null
@@ -122,6 +163,47 @@ internal class IntelliJPlatformAttachSourcesProvider : AttachSourcesProvider {
}
}
/**
* Creates an action to attach IntelliJ Platform sources to a library within the project.
*
* @param psiFile The PSI file that represents the currently handled class.
* @param productCoordinates The Maven coordinates of the IntelliJ Platform whose sources we load.
* @param version The version of the product.
*/
private fun createAttachPlatformSourcesAction(
psiFile: PsiFile,
productCoordinates: String,
version: String,
) = object : AttachSourcesAction {
override fun getName() = DevKitGradleBundle.message("attachSources.action.name")
override fun getBusyText() = DevKitGradleBundle.message("attachSources.action.busyText")
override fun perform(orderEntries: MutableList<out LibraryOrderEntry>): ActionCallback {
val externalProjectPath = CachedModuleDataFinder.getGradleModuleData(orderEntries.first().ownerModule)?.directoryToRunTask
?: return ActionCallback.REJECTED
val executionResult = ActionCallback()
val project = psiFile.project
val sourceArtifactNotation = "$productCoordinates:$version:sources"
GradleDependencySourceDownloader
.downloadSources(project, name, sourceArtifactNotation, externalProjectPath)
.whenComplete { path, error ->
if (error != null) {
executionResult.setRejected()
}
else {
attachSources(path, orderEntries) {
executionResult.setDone()
}
}
}
return executionResult
}
}
/**
* Attaches sources jar to the specified libraries and executes the provided block of code.
*/
@@ -131,4 +213,14 @@ internal class IntelliJPlatformAttachSourcesProvider : AttachSourcesProvider {
block()
}
}
/**
* Resolve the [ProductInfo] of the current IntelliJ Platform.
*/
private fun resolveProductInfo(psiFile: PsiFile): ProductInfo? {
val jarPath = Path(psiFile.virtualFile.path)
return generateSequence(jarPath) { it.parent }
.takeWhile { it != it.root }
.firstNotNullOfOrNull { loadProductInfo(it.pathString) }
}
}