[devkit] IJPL-246844 Attach IntelliJ SDK sources automatically on Gradle sync

Implement DevKit's AuxiliaryArtifactProvider providing source artifacts for Platform.

(cherry picked from commit 9c94c33ab8fda057eed8facfa0c024e7b33d4e04)
IJ-MR-210892

GitOrigin-RevId: a58cfe6655d4d1db4815d24639438a8900e743dd
This commit is contained in:
Karol Lewandowski
2026-08-03 12:33:14 +00:00
committed by intellij-monorepo-bot
parent c033406cb1
commit 3610e4c936
7 changed files with 345 additions and 31 deletions
@@ -1,4 +1,4 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.idea.devkit.projectRoots;
import com.intellij.util.PlatformUtils;
@@ -10,6 +10,7 @@ import org.jetbrains.annotations.Nullable;
import java.util.Objects;
public enum IntelliJPlatformProduct {
// sync myCdnCoordinates with IntelliJPlatformAuxiliaryArtifactProvider.CDN_GROUPS
IDEA("IU", "IntelliJ IDEA", null, "com.jetbrains.intellij.idea:idea", "idea:idea"),
IDEA_IU("IU", "IntelliJ IDEA", null, "com.jetbrains.intellij.idea:ideaIU", "idea:ideaIU"),
IDEA_IC("IC", "IntelliJ IDEA Community Edition", PlatformUtils.IDEA_CE_PREFIX, "com.jetbrains.intellij.idea:ideaIC", "idea:ideaIC"),
@@ -0,0 +1,2 @@
# Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
org.jetbrains.idea.devkit.gradle.tooling.IntelliJPlatformAuxiliaryArtifactProvider
@@ -0,0 +1,188 @@
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.idea.devkit.gradle.tooling;
import com.intellij.gradle.toolingExtension.impl.model.dependencyDownloadPolicyModel.GradleDependencyDownloadPolicy;
import com.intellij.gradle.toolingExtension.impl.model.dependencyModel.auxiliary.AuxiliaryArtifactProvider;
import com.intellij.gradle.toolingExtension.impl.model.dependencyModel.auxiliary.AuxiliaryConfigurationArtifacts;
import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.component.ComponentIdentifier;
import org.gradle.api.artifacts.component.ModuleComponentIdentifier;
import org.gradle.api.artifacts.result.ResolvedComponentResult;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.util.*;
/**
* Provides source artifacts for IntelliJ Platform dependencies.
* <p>
* IntelliJ Platform sources are published under different Maven coordinates
* than the main artifacts. For example, PhpStorm sources come from IntelliJ IDEA Community (IC).
* This provider maps platform dependency coordinates to the correct source coordinates
* and resolves them via detached Gradle configurations.
* <p>
* This code is run on the Gradle daemon side.
*/
@SuppressWarnings("IO_FILE_USAGE") // API uses java.io.File
public final class IntelliJPlatformAuxiliaryArtifactProvider implements AuxiliaryArtifactProvider {
private static final String JETBRAINS_INTELLIJ_PREFIX = "com.jetbrains.intellij.";
@SuppressWarnings("SSBasedInspection") // don't use platform utils here
private static final Set<String> CDN_GROUPS = new HashSet<>(Arrays.asList(
// sync with IntelliJPlatformProduct
"idea",
"ruby",
"python",
"webide",
"webstorm",
"cpp",
"datagrip",
"rider",
"go",
"com.google.android.studio",
"idea/code-with-me",
"idea/gateway",
"aqua",
"rustrover",
"mps"
));
@Override
public @NotNull AuxiliaryConfigurationArtifacts resolve(
@NotNull Project project,
@NotNull Configuration configuration,
@NotNull GradleDependencyDownloadPolicy policy
) {
if (!policy.isDownloadSources()) {
return AuxiliaryConfigurationArtifacts.EMPTY;
}
Map<ComponentIdentifier, Set<File>> sources = new LinkedHashMap<>();
Set<ResolvedComponentResult> components;
try {
components = configuration.getIncoming().getResolutionResult().getAllComponents();
}
catch (Exception e) {
return AuxiliaryConfigurationArtifacts.EMPTY;
}
for (ResolvedComponentResult component : components) {
ComponentIdentifier id = component.getId();
if (!(id instanceof ModuleComponentIdentifier)) continue;
ModuleComponentIdentifier moduleId = (ModuleComponentIdentifier)id;
String group = moduleId.getGroup();
if (!isIntelliJPlatformComponent(group)) continue;
String name = moduleId.getModule();
String version = moduleId.getVersion();
String actualVersion = IntelliJPlatformSourceCoordinates.extractActualVersion(version);
int majorVersion = IntelliJPlatformSourceCoordinates.extractMajorVersion(actualVersion);
String sourceCoordinates = resolveSourceCoordinates(group, name, version, majorVersion);
if (sourceCoordinates == null) continue;
String sourceNotation = sourceCoordinates + ":" + actualVersion + ":sources";
File sourceFile = resolveSourceArtifact(project, sourceNotation);
if (sourceFile == null && majorVersion > 0) {
// Try ranged version fallback: find closest published version in the same major cycle
String rangedNotation = sourceCoordinates + ":[" + majorVersion + "," + actualVersion + "]!!" + actualVersion + ":sources";
sourceFile = resolveSourceArtifact(project, rangedNotation);
}
if (sourceFile == null && majorVersion > 0) {
// Try SNAPSHOT fallback
String snapshotNotation = sourceCoordinates + ":" + majorVersion + "-SNAPSHOT:sources";
sourceFile = resolveSourceArtifact(project, snapshotNotation);
}
if (sourceFile != null) {
sources.put(id, Collections.singleton(sourceFile));
}
}
if (sources.isEmpty()) {
return AuxiliaryConfigurationArtifacts.EMPTY;
}
return new AuxiliaryConfigurationArtifacts(sources, Collections.emptyMap());
}
private static boolean isIntelliJPlatformComponent(@NotNull String group) {
return group.startsWith(JETBRAINS_INTELLIJ_PREFIX)
|| CDN_GROUPS.contains(group)
|| "com.jetbrains.plugins".equals(group)
|| "localIde".equals(group)
|| "bundledPlugin".equals(group)
|| "bundledModule".equals(group);
}
private static @Nullable String resolveSourceCoordinates(
@NotNull String group, @NotNull String name, @NotNull String version, int majorVersion
) {
// PyCharm variants → PC sources
if (("com.jetbrains.intellij.pycharm".equals(group) && ("pycharmPY".equals(name) || "pycharmPC".equals(name)))
|| "python".equals(group)) {
return IntelliJPlatformSourceCoordinates.PYCHARM_COMMUNITY_SOURCES;
}
if (isIdea("ideaIU", group, name)) {
return IntelliJPlatformSourceCoordinates.ideaUltimateSources(majorVersion);
}
if (isIdea("ideaIC", group, name)) {
return IntelliJPlatformSourceCoordinates.defaultPlatformSources(majorVersion);
}
// IDEA (unified coordinates for 253+)
if (isIdea("idea", group, name)) {
return "com.jetbrains.intellij.idea:idea";
}
// localIde: artifact name is the product code (e.g., "IC", "IU")
if ("localIde".equals(group)) {
return IntelliJPlatformSourceCoordinates.sourceCoordinatesForProductCode(name, majorVersion);
}
// bundledPlugin / bundledModule: product code is in the version prefix (e.g., "IC-243.21565.193")
if ("bundledPlugin".equals(group) || "bundledModule".equals(group)) {
String productCode = IntelliJPlatformSourceCoordinates.extractProductCode(version);
if (productCode != null) {
return IntelliJPlatformSourceCoordinates.sourceCoordinatesForProductCode(productCode, majorVersion);
}
return IntelliJPlatformSourceCoordinates.defaultPlatformSources(majorVersion);
}
// Non-bundled plugins from JetBrains Maven repo
if ("com.jetbrains.plugins".equals(group)) {
return IntelliJPlatformSourceCoordinates.defaultPlatformSources(majorVersion);
}
// All other IntelliJ Platform products → IC/idea sources
if (group.startsWith(JETBRAINS_INTELLIJ_PREFIX) || CDN_GROUPS.contains(group)) {
return IntelliJPlatformSourceCoordinates.defaultPlatformSources(majorVersion);
}
return null;
}
private static boolean isIdea(@NotNull String expectedName, @NotNull String group, @NotNull String name) {
return ("com.jetbrains.intellij.idea".equals(group) && expectedName.equals(name))
|| ("idea".equals(group) && expectedName.equals(name));
}
private static @Nullable File resolveSourceArtifact(@NotNull Project project, @NotNull String notation) {
try {
Configuration detached = project.getConfigurations().detachedConfiguration(project.getDependencies().create(notation));
detached.setTransitive(false);
Set<File> files = detached.resolve();
if (files.size() == 1) {
return files.iterator().next();
}
return null;
}
catch (Exception e) {
return null;
}
}
}
@@ -0,0 +1,118 @@
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.idea.devkit.gradle.tooling;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Shared utility for resolving IntelliJ Platform source artifact coordinates.
* <p>
* Used by both the Gradle daemon side ({@link IntelliJPlatformAuxiliaryArtifactProvider})
* and the IDE side ({@code IntelliJPlatformAttachSourcesProvider}).
*/
public final class IntelliJPlatformSourceCoordinates {
public static final String PYCHARM_COMMUNITY_SOURCES = "com.jetbrains.intellij.pycharm:pycharmPC";
/**
* Source coordinates for IDEA Ultimate (version-dependent: ideaIC → ideaIU → idea).
*/
public static @NotNull String ideaUltimateSources(int majorVersion) {
if (majorVersion >= 253) return "com.jetbrains.intellij.idea:idea";
if (majorVersion >= 242) return "com.jetbrains.intellij.idea:ideaIU";
return "com.jetbrains.intellij.idea:ideaIC";
}
/**
* Source coordinates for IDEA Community or any other IntelliJ Platform product.
*/
public static @NotNull String defaultPlatformSources(int majorVersion) {
if (majorVersion >= 253) return "com.jetbrains.intellij.idea:idea";
return "com.jetbrains.intellij.idea:ideaIC";
}
/**
* Extracts the actual version from a possibly prefixed version string.
* Strips product-code prefix (e.g., "IC-") and build metadata suffix (e.g., "+445").
* <p>
* Examples:
* <ul>
* <li>"IC-243.21565.193" → "243.21565.193"</li>
* <li>"2024.1.1+445" → "2024.1.1"</li>
* <li>"243.21565.193" → "243.21565.193"</li>
* </ul>
*/
public static @NotNull String extractActualVersion(@NotNull String version) {
int dashIndex = version.indexOf('-');
if (dashIndex >= 0) {
version = version.substring(dashIndex + 1);
}
int plusIndex = version.indexOf('+');
if (plusIndex >= 0) {
version = version.substring(0, plusIndex);
}
return version;
}
/**
* Extracts the product code prefix from a version string, if present.
* <p>
* Examples:
* <ul>
* <li>"IC-243.21565.193" → "IC"</li>
* <li>"243.21565.193" → null</li>
* <li>"2024.1.1+445" → null</li>
* </ul>
*/
public static @Nullable String extractProductCode(@NotNull String version) {
int dashIndex = version.indexOf('-');
if (dashIndex > 0) {
String prefix = version.substring(0, dashIndex);
// Product codes are short alphabetic strings (e.g., IC, IU, PY, PC)
if (prefix.chars().allMatch(Character::isLetter)) {
return prefix;
}
}
return null;
}
/**
* Maps a product code to the appropriate source coordinates.
*/
public static @NotNull String sourceCoordinatesForProductCode(@NotNull String productCode, int majorVersion) {
switch (productCode) {
case "IU":
return ideaUltimateSources(majorVersion);
case "PY":
case "PC":
return PYCHARM_COMMUNITY_SOURCES;
default:
return defaultPlatformSources(majorVersion);
}
}
/**
* Extracts major version number from a version string.
* Handles both release format ("2024.1.1") and build number format ("243.21565.193").
*
* @return major version number, or -1 if the version string cannot be parsed
*/
public static int extractMajorVersion(@NotNull String version) {
try {
String[] parts = version.split("\\.");
int first = Integer.parseInt(parts[0]);
if (first >= 2000) {
// Release version format: 2024.1.1
int minor = parts.length > 1 ? Integer.parseInt(parts[1]) : 0;
return (first - 2000) * 10 + minor;
}
else {
// Build number format: 243.21565.193
return first;
}
}
catch (NumberFormatException e) {
return -1;
}
}
}
@@ -12,6 +12,7 @@
<extensions defaultExtensionNs="org.jetbrains.plugins.gradle">
<frameworkSupport implementation="org.jetbrains.idea.devkit.gradle.GradleIntellijPluginFrameworkSupportProvider"/>
<kotlinDslFrameworkSupport implementation="org.jetbrains.idea.devkit.gradle.KdslGradleIntellijPluginFrameworkSupportProvider"/>
<projectResolve implementation="org.jetbrains.idea.devkit.gradle.DevKitGradleProjectResolverExtension"/>
</extensions>
<extensions defaultExtensionNs="com.intellij">
@@ -0,0 +1,10 @@
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.idea.devkit.gradle
import org.jetbrains.idea.devkit.gradle.tooling.IntelliJPlatformAuxiliaryArtifactProvider
import org.jetbrains.plugins.gradle.service.project.AbstractProjectResolverExtension
internal class DevKitGradleProjectResolverExtension : AbstractProjectResolverExtension() {
override fun getToolingExtensionsClasses(): Set<Class<*>> =
setOf(IntelliJPlatformAuxiliaryArtifactProvider::class.java)
}
@@ -1,4 +1,4 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.idea.devkit.gradle
import com.intellij.codeInsight.AttachSourcesProvider
@@ -23,6 +23,7 @@ import com.intellij.workspaceModel.ide.legacyBridge.findModule
import org.jetbrains.annotations.PropertyKey
import org.jetbrains.idea.devkit.projectRoots.IntelliJPlatformProduct
import org.jetbrains.idea.devkit.run.ProductInfo
import org.jetbrains.idea.devkit.gradle.tooling.IntelliJPlatformSourceCoordinates
import org.jetbrains.idea.devkit.run.loadProductInfo
import org.jetbrains.plugins.gradle.execution.build.CachedModuleDataFinder
import org.jetbrains.plugins.gradle.service.project.GradleNotification
@@ -80,7 +81,7 @@ internal class IntelliJPlatformAttachSourcesProvider : AttachSourcesProvider {
// IntelliJ Platform dependency, such as `com.jetbrains.intellij.idea:ideaIC:2023.2.7`, `idea:ideaIC:aarch64:2024.3`, or `idea:ideaIC:2023.2.7`
product != null -> resolveIntelliJPlatformAction(psiFile, coordinates.version)
// IntelliJ Platform bundled plugin, such as `localIde:IC:2023.2.7+445`
// IntelliJ Platform local installation, such as `localIde:IC:2023.2.7+445`
coordinates.groupId == "localIde" -> createAttachLocalPlatformSourcesAction(psiFile, coordinates)
// IntelliJ Platform bundled plugin, such as `bundledPlugin:org.intellij.groovy:IC-243.21565.193`, `bundledPlugin:Git4Idea:2023.2.7+445`
@@ -115,7 +116,7 @@ internal class IntelliJPlatformAttachSourcesProvider : AttachSourcesProvider {
val buildNumber = productInfo.buildNumber
val majorVersion = buildNumber.substringBefore('.').toInt()
val rangedVersion = "[$majorVersion,$buildNumber]!!$buildNumber"
val productCoordinates = resolveProductCoordinates(product, majorVersion) ?: return null
val productCoordinates = resolveProductCoordinates(product, majorVersion)
return when {
// We're handing LSP API class, but IU is lower than 242 -> attach a standalone sources file
@@ -133,7 +134,7 @@ internal class IntelliJPlatformAttachSourcesProvider : AttachSourcesProvider {
* @param coordinates The Maven coordinates of the IntelliJ Platform whose sources need to be attached.
*/
private fun createAttachLocalPlatformSourcesAction(psiFile: PsiFile, coordinates: MavenCoordinates) =
resolveIntelliJPlatformAction(psiFile, coordinates.version.substringAfter('-').substringBefore('+'))
resolveIntelliJPlatformAction(psiFile, IntelliJPlatformSourceCoordinates.extractActualVersion(coordinates.version))
/**
* Creates an action to attach sources of bundled plugins for the IntelliJ Platform.
@@ -143,7 +144,7 @@ internal class IntelliJPlatformAttachSourcesProvider : AttachSourcesProvider {
*/
private fun createAttachBundledPluginSourcesAction(psiFile: PsiFile, coordinates: MavenCoordinates) =
createAttachSourcesArchiveAction(psiFile, ApiSourceArchive.entries.firstOrNull { it.id == coordinates.artifactId })
?: resolveIntelliJPlatformAction(psiFile, coordinates.version.substringAfter('-').substringBefore('+'))
?: resolveIntelliJPlatformAction(psiFile, IntelliJPlatformSourceCoordinates.extractActualVersion(coordinates.version))
/**
* Creates an action to attach sources of bundled modules for the IntelliJ Platform.
@@ -256,20 +257,22 @@ internal class IntelliJPlatformAttachSourcesProvider : AttachSourcesProvider {
}
}
downloadAndAttach(primaryNotation) {
downloadAndAttach(fallbackNotation) {
downloadAndAttach(nightlySnapshotNotation) {
downloadAndAttach(primaryNotation, onFailure = {
downloadAndAttach(fallbackNotation, onFailure = {
downloadAndAttach(nightlySnapshotNotation, onFailure = {
GradleNotification.gradleNotificationGroup
.createNotification(title = GradleBundle.message("gradle.notifications.sources.download.failed.title"),
content = GradleBundle.message("gradle.notifications.sources.download.failed.content", primaryNotation),
NotificationType.WARNING)
.createNotification(
title = GradleBundle.message("gradle.notifications.sources.download.failed.title"),
content = GradleBundle.message("gradle.notifications.sources.download.failed.content", primaryNotation),
type = NotificationType.WARNING
)
.setDisplayId("gradle.notifications.sources.download.failed")
.notify(project)
executionResult.setRejected()
}
}
}
})
})
})
return executionResult
}
@@ -339,20 +342,11 @@ internal class IntelliJPlatformAttachSourcesProvider : AttachSourcesProvider {
private fun resolveProductCoordinates(product: IntelliJPlatformProduct, majorVersion: Int) =
when (product) {
// For PyCharm Community and PyCharm, 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 >= 253 -> IntelliJPlatformProduct.IDEA
majorVersion >= 242 -> IntelliJPlatformProduct.IDEA_IU
else -> IntelliJPlatformProduct.IDEA_IC
}
// Any other IntelliJ Platform should use IC
else -> when {
majorVersion >= 253 -> IntelliJPlatformProduct.IDEA
else -> IntelliJPlatformProduct.IDEA_IC
}
}.mavenCoordinates
IntelliJPlatformProduct.PYCHARM, IntelliJPlatformProduct.PYCHARM_PC ->
IntelliJPlatformSourceCoordinates.PYCHARM_COMMUNITY_SOURCES
IntelliJPlatformProduct.IDEA ->
IntelliJPlatformSourceCoordinates.ideaUltimateSources(majorVersion)
else ->
IntelliJPlatformSourceCoordinates.defaultPlatformSources(majorVersion)
}
}