From 441f69da6e6647f0f3d25a010055d89da6694f0e Mon Sep 17 00:00:00 2001 From: Vladimir Krivosheev Date: Fri, 24 Oct 2025 14:23:58 +0200 Subject: [PATCH] IJPL-212573 IJPL-160311 do not embed x-include GitOrigin-RevId: f5b7605cb1f277658b131cd4a8717b4b380d632f --- .../intellij/build/IdeaCommunityProperties.kt | 10 +- .../ModuleBasedPluginXmlPathResolver.kt | 2 +- ...unningFromSourceModuleBasedPathResolver.kt | 5 +- .../build/JetBrainsProductProperties.kt | 1 - .../intellij/build/ProductProperties.kt | 11 + .../intellij/build/ProprietaryBuildTools.kt | 14 +- .../org/jetbrains/intellij/build/classpath.kt | 102 +++++++- .../intellij/build/dev/IdeBuilder.kt | 63 ++--- .../intellij/build/impl/BuildContextImpl.kt | 39 ++- .../build/impl/CachedDescriptorContainer.kt | 44 ++++ .../build/impl/DistributionBuilderState.kt | 2 +- .../build/impl/DistributionJARsBuilder.kt | 4 +- .../intellij/build/impl/PlatformLayout.kt | 3 + .../intellij/build/impl/PlatformModules.kt | 173 ++++++++----- .../intellij/build/impl/PluginXmlPatcher.kt | 6 +- .../build/impl/deprecatedClasspath.kt | 2 +- .../ProjectStructureMapping.kt | 67 +++-- .../intellij/build/impl/xIncludeResolver.kt | 127 +++++++++- .../productLayout/CommunityModuleSets.kt | 7 +- .../build/productLayout/ModuleSetBuilder.kt | 217 ++++++++++++---- .../intellij/build/productLayout/generator.kt | 7 +- .../productRunner/IntellijProcessStarter.kt | 2 +- .../PrivateModulesCheckerUtil.kt | 4 +- .../testFramework/buildScriptTestUtils.kt | 9 +- .../PluginDependenciesValidator.kt | 3 +- .../ide/plugins/ClassPathXmlPathResolver.kt | 29 ++- .../com/intellij/ide/plugins/PathResolver.kt | 10 +- .../ide/plugins/PluginDescriptorLoader.kt | 33 +-- .../ide/plugins/PluginXmlPathResolver.kt | 6 +- .../intellij.moduleSets.essential.xml | 99 +++++++- .../intellij.moduleSets.ide.common.xml | 234 +++++++++++++++++- .../intellij.moduleSets.libraries.xml | 100 +++++++- .../META-INF/intellij.moduleSets.vcs.xml | 15 +- .../ide/plugins/PluginDescriptorLoadUtils.kt | 4 +- .../intellij/ide/plugins/PluginManagerTest.kt | 4 +- .../platform/plugins/parser/impl/XmlReader.kt | 5 +- .../pycharm/PyCharmCommunityProperties.kt | 5 + 37 files changed, 1211 insertions(+), 257 deletions(-) create mode 100644 platform/build-scripts/src/org/jetbrains/intellij/build/impl/CachedDescriptorContainer.kt diff --git a/build/src/org/jetbrains/intellij/build/IdeaCommunityProperties.kt b/build/src/org/jetbrains/intellij/build/IdeaCommunityProperties.kt index 85474223f1a1..f1ac81604286 100644 --- a/build/src/org/jetbrains/intellij/build/IdeaCommunityProperties.kt +++ b/build/src/org/jetbrains/intellij/build/IdeaCommunityProperties.kt @@ -10,13 +10,18 @@ import org.jetbrains.intellij.build.impl.qodana.QodanaProductProperties import org.jetbrains.intellij.build.io.copyDir import org.jetbrains.intellij.build.io.copyFileToDir import org.jetbrains.intellij.build.kotlin.KotlinBinaries +import org.jetbrains.intellij.build.productLayout.CommunityModuleSets +import org.jetbrains.intellij.build.productLayout.ModuleSetProvider import java.nio.file.Path internal suspend fun createCommunityBuildContext( options: BuildOptions, projectHome: Path = COMMUNITY_ROOT.communityRoot, ): BuildContext = BuildContextImpl.createContext( - projectHome, IdeaCommunityProperties(COMMUNITY_ROOT.communityRoot), setupTracer = true, options = options + projectHome = projectHome, + productProperties = IdeaCommunityProperties(COMMUNITY_ROOT.communityRoot), + setupTracer = true, + options = options, ) open class IdeaCommunityProperties(private val communityHomeDir: Path) : BaseIdeaProperties() { @@ -36,6 +41,9 @@ open class IdeaCommunityProperties(private val communityHomeDir: Path) : BaseIde ) } + override val moduleSetsProviders: List + get() = listOf(CommunityModuleSets) + override val baseFileName: String get() = "idea" diff --git a/platform/bootstrap/src/com/intellij/platform/bootstrap/ModuleBasedPluginXmlPathResolver.kt b/platform/bootstrap/src/com/intellij/platform/bootstrap/ModuleBasedPluginXmlPathResolver.kt index b973a60eebea..c2127d6b5e22 100644 --- a/platform/bootstrap/src/com/intellij/platform/bootstrap/ModuleBasedPluginXmlPathResolver.kt +++ b/platform/bootstrap/src/com/intellij/platform/bootstrap/ModuleBasedPluginXmlPathResolver.kt @@ -31,7 +31,7 @@ internal class ModuleBasedPluginXmlPathResolver( val moduleDescriptor = includedModules.find { it.moduleDescriptor.moduleId.stringId == moduleName }?.moduleDescriptor if (moduleDescriptor != null) { val input = moduleDescriptor.readFile(path) ?: error("Cannot resolve $path in $moduleDescriptor") - val reader = PluginDescriptorFromXmlStreamConsumer(readContext, toXIncludeLoader(dataLoader)) + val reader = PluginDescriptorFromXmlStreamConsumer(readContext, createXIncludeLoader(this@ModuleBasedPluginXmlPathResolver, dataLoader)) reader.consume(input, path) return reader.getBuilder() } diff --git a/platform/bootstrap/src/com/intellij/platform/bootstrap/RunningFromSourceModuleBasedPathResolver.kt b/platform/bootstrap/src/com/intellij/platform/bootstrap/RunningFromSourceModuleBasedPathResolver.kt index 4693a61a6d04..1ba4ddd3053e 100644 --- a/platform/bootstrap/src/com/intellij/platform/bootstrap/RunningFromSourceModuleBasedPathResolver.kt +++ b/platform/bootstrap/src/com/intellij/platform/bootstrap/RunningFromSourceModuleBasedPathResolver.kt @@ -4,7 +4,7 @@ package com.intellij.platform.bootstrap import com.intellij.ide.plugins.DataLoader import com.intellij.ide.plugins.PathResolver import com.intellij.ide.plugins.PluginModuleId -import com.intellij.ide.plugins.toXIncludeLoader +import com.intellij.ide.plugins.createXIncludeLoader import com.intellij.platform.plugins.parser.impl.PluginDescriptorBuilder import com.intellij.platform.plugins.parser.impl.PluginDescriptorFromXmlStreamConsumer import com.intellij.platform.plugins.parser.impl.PluginDescriptorReaderContext @@ -25,7 +25,8 @@ internal class RunningFromSourceModuleBasedPathResolver( val moduleDescriptor = moduleRepository.resolveModule(RuntimeModuleId.module(moduleName)).resolvedModule if (moduleDescriptor != null) { val input = moduleDescriptor.readFile(path) ?: error("Cannot resolve $path in $moduleDescriptor") - val reader = PluginDescriptorFromXmlStreamConsumer(readContext, toXIncludeLoader(dataLoader)) + val reader = PluginDescriptorFromXmlStreamConsumer(readContext, + createXIncludeLoader(this@RunningFromSourceModuleBasedPathResolver, dataLoader)) reader.consume(input, path) return reader.getBuilder() } diff --git a/platform/build-scripts/src/org/jetbrains/intellij/build/JetBrainsProductProperties.kt b/platform/build-scripts/src/org/jetbrains/intellij/build/JetBrainsProductProperties.kt index c98d1c375278..03436f461d00 100644 --- a/platform/build-scripts/src/org/jetbrains/intellij/build/JetBrainsProductProperties.kt +++ b/platform/build-scripts/src/org/jetbrains/intellij/build/JetBrainsProductProperties.kt @@ -19,7 +19,6 @@ import com.jetbrains.plugin.structure.intellij.problems.TemplateWordInPluginId import com.jetbrains.plugin.structure.intellij.problems.TemplateWordInPluginName import com.jetbrains.plugin.structure.intellij.verifiers.DEFAULT_ILLEGAL_PREFIXES import com.jetbrains.plugin.structure.intellij.verifiers.PRODUCT_ID_RESTRICTED_WORDS -import kotlinx.collections.immutable.plus import org.jetbrains.annotations.ApiStatus import org.jetbrains.intellij.build.SoftwareBillOfMaterials.Companion.Suppliers import org.jetbrains.intellij.build.impl.PlatformJarNames.PLATFORM_CORE_NIO_FS diff --git a/platform/build-scripts/src/org/jetbrains/intellij/build/ProductProperties.kt b/platform/build-scripts/src/org/jetbrains/intellij/build/ProductProperties.kt index 1bfa64707066..f5ee999c7a85 100644 --- a/platform/build-scripts/src/org/jetbrains/intellij/build/ProductProperties.kt +++ b/platform/build-scripts/src/org/jetbrains/intellij/build/ProductProperties.kt @@ -16,6 +16,8 @@ import kotlinx.collections.immutable.persistentMapOf import org.jetbrains.annotations.ApiStatus import org.jetbrains.intellij.build.impl.PlatformLayout import org.jetbrains.intellij.build.impl.qodana.QodanaProductProperties +import org.jetbrains.intellij.build.productLayout.CommunityModuleSets +import org.jetbrains.intellij.build.productLayout.ModuleSetProvider import org.jetbrains.intellij.build.productLayout.ProductModulesContentSpec import org.jetbrains.intellij.build.productLayout.ProductModulesLayout import org.jetbrains.jps.model.JpsProject @@ -503,6 +505,15 @@ abstract class ProductProperties { is PluginCreationFail -> result.errorsAndWarnings } } + + /** + * List of module sets providers used to discover and resolve module set relationships. + * Used only for packaging tests to group modules by their module sets. + * + * For community builds: [CommunityModuleSets] + * For ultimate builds: both UltimateModuleSets and [CommunityModuleSets] + */ + abstract val moduleSetsProviders: List } /** diff --git a/platform/build-scripts/src/org/jetbrains/intellij/build/ProprietaryBuildTools.kt b/platform/build-scripts/src/org/jetbrains/intellij/build/ProprietaryBuildTools.kt index 5ecd42357c87..2766e50ca677 100644 --- a/platform/build-scripts/src/org/jetbrains/intellij/build/ProprietaryBuildTools.kt +++ b/platform/build-scripts/src/org/jetbrains/intellij/build/ProprietaryBuildTools.kt @@ -42,9 +42,11 @@ data class ProprietaryBuildTools( ) { companion object { internal val DUMMY_SIGN_TOOL: SignTool = object : SignTool { - override val macOsCodesignIdentity: MacOsCodesignIdentity? get() = null + override val macOsCodesignIdentity: MacOsCodesignIdentity? + get() = null - override val signNativeFileMode: SignNativeFileMode get() = SignNativeFileMode.DISABLED + override val signNativeFileMode: SignNativeFileMode + get() = SignNativeFileMode.DISABLED override suspend fun signFiles(files: List, context: BuildContext?, options: PersistentMap) { Span.current().addEvent("files won't be signed", Attributes.of( @@ -54,7 +56,7 @@ data class ProprietaryBuildTools( } override suspend fun signFilesWithGpg(files: List, context: BuildContext) { - signFiles(files, context, persistentMapOf()) + signFiles(files = files, context = context, options = persistentMapOf()) } override suspend fun getPresignedLibraryFile(path: String, libName: String, libVersion: String, context: BuildContext): Path { @@ -65,7 +67,11 @@ data class ProprietaryBuildTools( } val DUMMY: ProprietaryBuildTools = ProprietaryBuildTools( - DUMMY_SIGN_TOOL, scrambleTool = null, artifactsServer = null, featureUsageStatisticsProperties = null, licenseServerHost = null + signTool = DUMMY_SIGN_TOOL, + scrambleTool = null, + artifactsServer = null, + featureUsageStatisticsProperties = null, + licenseServerHost = null ) } } diff --git a/platform/build-scripts/src/org/jetbrains/intellij/build/classpath.kt b/platform/build-scripts/src/org/jetbrains/intellij/build/classpath.kt index 3a02dd57f77b..0ee2ec2db685 100644 --- a/platform/build-scripts/src/org/jetbrains/intellij/build/classpath.kt +++ b/platform/build-scripts/src/org/jetbrains/intellij/build/classpath.kt @@ -3,20 +3,32 @@ package org.jetbrains.intellij.build +import com.intellij.openapi.util.JDOMUtil +import com.intellij.openapi.util.io.BufferExposingByteArrayOutputStream import com.intellij.platform.util.putMoreLikelyPluginJarsFirst +import org.jdom.CDATA +import org.jdom.Element +import org.jetbrains.annotations.VisibleForTesting +import org.jetbrains.intellij.build.impl.CachedDescriptorContainer import org.jetbrains.intellij.build.impl.ModuleIncludeReasons import org.jetbrains.intellij.build.impl.ModuleOutputPatcher +import org.jetbrains.intellij.build.impl.ModuleOutputProvider import org.jetbrains.intellij.build.impl.PlatformJarNames import org.jetbrains.intellij.build.impl.PlatformJarNames.APP_BACKEND_JAR import org.jetbrains.intellij.build.impl.PlatformJarNames.APP_JAR import org.jetbrains.intellij.build.impl.PlatformJarNames.PLATFORM_CORE_NIO_FS import org.jetbrains.intellij.build.impl.PlatformJarNames.PRODUCT_BACKEND_JAR +import org.jetbrains.intellij.build.impl.PlatformLayout import org.jetbrains.intellij.build.impl.PluginLayout +import org.jetbrains.intellij.build.impl.XIncludeElementResolver +import org.jetbrains.intellij.build.impl.createXIncludePathResolver +import org.jetbrains.intellij.build.impl.findFileInModuleSources import org.jetbrains.intellij.build.impl.projectStructureMapping.CustomAssetEntry import org.jetbrains.intellij.build.impl.projectStructureMapping.DistributionFileEntry import org.jetbrains.intellij.build.impl.projectStructureMapping.ModuleOutputEntry import org.jetbrains.intellij.build.impl.projectStructureMapping.ModuleOwnedFileEntry -import org.jetbrains.intellij.build.impl.projectStructureMapping.ProjectLibraryEntry +import org.jetbrains.intellij.build.impl.resolveNonXIncludeElementFromCache +import org.jetbrains.intellij.build.impl.toLoadPath import org.jetbrains.intellij.build.io.ZipEntryProcessorResult import org.jetbrains.intellij.build.io.readZipFile import java.io.ByteArrayOutputStream @@ -85,27 +97,97 @@ internal data class PluginBuildDescriptor( @JvmField val moduleNames: List, ) -internal fun writePluginClassPathHeader(out: DataOutputStream, isJarOnly: Boolean, pluginCount: Int, moduleOutputPatcher: ModuleOutputPatcher, context: BuildContext) { +internal fun writePluginClassPathHeader( + out: DataOutputStream, + isJarOnly: Boolean, + pluginCount: Int, + platformLayout: PlatformLayout, + context: BuildContext, +) { // format version out.write(2) // jarOnly out.write(if (isJarOnly) 1 else 0) - // main plugin - val mainDescriptor = moduleOutputPatcher.getPatchedContent(context.productProperties.applicationInfoModule).let { - it.get("META-INF/plugin.xml") ?: it.get("META-INF/${context.productProperties.platformPrefix}Plugin.xml") + val mainPluginDescriptorContent = BufferExposingByteArrayOutputStream().use { + JDOMUtil.write(createCachedProductDescriptor(platformLayout, context), it) + it } - val mainPluginDescriptorContent = requireNotNull(mainDescriptor) { - "Cannot find core plugin descriptor (module=${context.productProperties.applicationInfoModule})" - } - out.writeInt(mainPluginDescriptorContent.size) - out.write(mainPluginDescriptorContent) + out.writeInt(mainPluginDescriptorContent.size()) + out.write(mainPluginDescriptorContent.internalBuffer, 0, mainPluginDescriptorContent.size()) // bundled plugin metadata out.writeShort(pluginCount) } +@VisibleForTesting +fun createCachedProductDescriptor(platformLayout: PlatformLayout, context: BuildContext): Element { + val cachedDescriptorContainer = platformLayout.cachedDescriptorContainer + val mainPluginDescriptor = requireNotNull(cachedDescriptorContainer.productDescriptor) { + "Cannot find core plugin descriptor (module=${context.productProperties.applicationInfoModule})" + } + + val xIncludeResolver = object : XIncludeElementResolver { + private val default by lazy { + createXIncludePathResolver( + includedPlatformModulesPartialList = platformLayout.includedModules.asSequence().map { it.moduleName }.distinct().toList(), + context = context, + ) + } + + override fun resolveElement(relativePath: String, isOptional: Boolean, isDynamic: Boolean): Element? { + platformLayout.cachedDescriptorContainer.getCachedFileData(toLoadPath(relativePath))?.let { + return JDOMUtil.load(it) + } + return default.resolvePath(relativePath = relativePath, base = null, isOptional = isOptional, isDynamic = isDynamic)?.let { + JDOMUtil.load(it) + } + } + } + + for (content in mainPluginDescriptor.getChildren("content")) { + for (moduleElement in content.getChildren("module")) { + processProductModule( + moduleElement = moduleElement, + cachedDescriptorContainer = cachedDescriptorContainer, + xIncludeResolver = xIncludeResolver, + moduleOutputProvider = context, + ) + } + } + + return mainPluginDescriptor +} + +private fun processProductModule( + moduleElement: Element, + cachedDescriptorContainer: CachedDescriptorContainer, + moduleOutputProvider: ModuleOutputProvider, + xIncludeResolver: XIncludeElementResolver, +) { + if (!moduleElement.content.isEmpty()) { + return + } + + val moduleName = moduleElement.getAttributeValue("name") ?: return + val descriptorFile = "${moduleName.replace('/', '.')}.xml" + + val cachedFileData = cachedDescriptorContainer.getCachedFileData(descriptorFile) + val xml = if (cachedFileData == null) { + val file = requireNotNull(findFileInModuleSources(module = moduleOutputProvider.findRequiredModule(moduleName), relativePath = descriptorFile)) { + "Cannot find file $descriptorFile in module $moduleName" + } + JDOMUtil.load(file) + } + else { + JDOMUtil.load(cachedFileData) + } + + resolveNonXIncludeElementFromCache(original = xml, elementResolver = xIncludeResolver) + moduleElement.setContent(CDATA(JDOMUtil.write(xml))) +} + internal fun generatePluginClassPath(pluginEntries: List>>, moduleOutputPatcher: ModuleOutputPatcher): ByteArray { val byteOut = ByteArrayOutputStream() val out = DataOutputStream(byteOut) diff --git a/platform/build-scripts/src/org/jetbrains/intellij/build/dev/IdeBuilder.kt b/platform/build-scripts/src/org/jetbrains/intellij/build/dev/IdeBuilder.kt index a68f4ed40194..113d85efbd6b 100644 --- a/platform/build-scripts/src/org/jetbrains/intellij/build/dev/IdeBuilder.kt +++ b/platform/build-scripts/src/org/jetbrains/intellij/build/dev/IdeBuilder.kt @@ -266,30 +266,12 @@ internal suspend fun buildProduct(request: BuildRequest, createProductProperties ) } - launch { - val (pluginEntries, additionalEntries) = pluginDistributionEntriesDeferred.await() - spanBuilder("generate plugin classpath").use(Dispatchers.IO) { - val mainData = generatePluginClassPath(pluginEntries, moduleOutputPatcher) - val additionalData = additionalEntries?.let { generatePluginClassPathFromPrebuiltPluginFiles(it) } - - val byteOut = ByteArrayOutputStream() - val out = DataOutputStream(byteOut) - val pluginCount = pluginEntries.size + (additionalEntries?.size ?: 0) - platformDistributionEntriesDeferred.join() - writePluginClassPathHeader(out = out, isJarOnly = !request.isUnpackedDist, pluginCount = pluginCount, moduleOutputPatcher = moduleOutputPatcher, context = context) - out.write(mainData) - additionalData?.let { out.write(it) } - out.close() - Files.write(runDir.resolve(PLUGIN_CLASSPATH), byteOut.toByteArray()) - } - } - if (context.generateRuntimeModuleRepository) { launch { val allDistributionEntries = platformDistributionEntriesDeferred.await().asSequence() + pluginDistributionEntriesDeferred.await().first.asSequence().flatMap { it.second } spanBuilder("generate runtime repository").use(Dispatchers.IO) { - generateRuntimeModuleRepositoryForDevBuild(allDistributionEntries, runDir, context) + generateRuntimeModuleRepositoryForDevBuild(entries = allDistributionEntries, targetDirectory = runDir, context = context) } } } @@ -303,22 +285,45 @@ internal suspend fun buildProduct(request: BuildRequest, createProductProperties ) } - launch(Dispatchers.IO) { + launch { // ensure platform dist files added to the list val platformFileEntries = platformDistributionEntriesDeferred.await() // ensure plugin dist files added to the list - pluginDistributionEntriesDeferred.await() + val pluginDistributionEntries = pluginDistributionEntriesDeferred.await() + val platformLayout = platformLayout.await() + // must be before generatePluginClassPath, because we modify plugin descriptors (e.g., rename classes) spanBuilder("scramble platform").use { - request.scrambleTool?.scramble(platform = platformLayout.await(), platformFileEntries = platformFileEntries, context = context) + request.scrambleTool?.scramble(platform = platformLayout, platformFileEntries = platformFileEntries, context = context) + } + + launch { + val (pluginEntries, additionalEntries) = pluginDistributionEntries + spanBuilder("generate plugin classpath").use(Dispatchers.IO) { + val mainData = generatePluginClassPath(pluginEntries, moduleOutputPatcher) + val additionalData = additionalEntries?.let { generatePluginClassPathFromPrebuiltPluginFiles(it) } + + val byteOut = ByteArrayOutputStream() + val out = DataOutputStream(byteOut) + val pluginCount = pluginEntries.size + (additionalEntries?.size ?: 0) + platformDistributionEntriesDeferred.join() + writePluginClassPathHeader(out = out, isJarOnly = !request.isUnpackedDist, pluginCount = pluginCount, platformLayout = platformLayout, context = context) + out.write(mainData) + additionalData?.let { out.write(it) } + out.close() + Files.write(runDir.resolve(PLUGIN_CLASSPATH), byteOut.toByteArray()) + } + } + + withContext(Dispatchers.IO) { + copyDistFiles( + context = context, + newDir = runDir, + os = request.os, + arch = JvmArchitecture.currentJvmArch, + libcImpl = LibcImpl.current(OsFamily.currentOs), + ) } - copyDistFiles( - context = context, - newDir = runDir, - os = request.os, - arch = JvmArchitecture.currentJvmArch, - libcImpl = LibcImpl.current(OsFamily.currentOs), - ) } }.invokeOnCompletion { // close debug logging to prevent locking of the output directory on Windows diff --git a/platform/build-scripts/src/org/jetbrains/intellij/build/impl/BuildContextImpl.kt b/platform/build-scripts/src/org/jetbrains/intellij/build/impl/BuildContextImpl.kt index 8d8d9ffcfbe6..d75b2cf60966 100644 --- a/platform/build-scripts/src/org/jetbrains/intellij/build/impl/BuildContextImpl.kt +++ b/platform/build-scripts/src/org/jetbrains/intellij/build/impl/BuildContextImpl.kt @@ -144,10 +144,6 @@ class BuildContextImpl internal constructor( override val isNightlyBuild: Boolean = options.isNightlyBuild || isNightly(buildNumber) - private fun isNightly(buildNumber: String): Boolean { - return buildNumber.count { it == '.' } <= 1 - } - init { @Suppress("DEPRECATION") if (productProperties.productCode == null) { @@ -181,9 +177,17 @@ class BuildContextImpl internal constructor( options: BuildOptions = BuildOptions(), ): BuildContext { val compilationContext = CompilationContextImpl.createCompilationContext( - projectHome, createBuildOutputRootEvaluator(projectHome, productProperties, options), options, setupTracer + projectHome = projectHome, + buildOutputRootEvaluator = createBuildOutputRootEvaluator(projectHome, productProperties, options), + options = options, + setupTracer = setupTracer ).asBazelIfNeeded - return createContext(compilationContext, projectHome, productProperties, proprietaryBuildTools) + return createContext( + compilationContext = compilationContext, + projectHome = projectHome, + productProperties = productProperties, + proprietaryBuildTools = proprietaryBuildTools, + ) } fun createContext( @@ -203,7 +207,7 @@ class BuildContextImpl internal constructor( productProperties.createMacCustomizer(projectHomeAsString), proprietaryBuildTools, ApplicationInfoPropertiesImpl(compilationContext.project, productProperties, compilationContext.options), - jarCacheManager + jarCacheManager, ) } @@ -429,7 +433,7 @@ class BuildContextImpl internal constructor( } override fun loadRawProductModules(rootModuleName: String, productMode: ProductMode): RawProductModules { - val productModulesFile = findProductModulesFile(this, rootModuleName) + val productModulesFile = findProductModulesFile(context = this, clientMainModuleName = rootModuleName) ?: error("Cannot find product-modules.xml file in $rootModuleName") val resolver = object : ResourceFileResolver { override fun readResourceFile(moduleId: RuntimeModuleId, relativePath: String): InputStream? { @@ -448,9 +452,9 @@ class BuildContextImpl internal constructor( } override suspend fun createProductRunner(additionalPluginModules: List): IntellijProductRunner { - when { - additionalPluginModules.isEmpty() -> return devModeProductRunner.await() - else -> return createDevModeProductRunner(additionalPluginModules = additionalPluginModules, context = this) + return when { + additionalPluginModules.isEmpty() -> devModeProductRunner.await() + else -> createDevModeProductRunner(additionalPluginModules = additionalPluginModules, context = this) } } @@ -462,8 +466,13 @@ class BuildContextImpl internal constructor( attachStdOutToException: Boolean, ) { runProcess( - args, workingDir, timeout, additionalEnvVariables, attachStdOutToException = attachStdOutToException, - stdOutConsumer = messages::info, stdErrConsumer = messages::warning, + args = args, + workingDir = workingDir, + timeout = timeout, + additionalEnvVariables = additionalEnvVariables, + attachStdOutToException = attachStdOutToException, + stdOutConsumer = messages::info, + stdErrConsumer = messages::warning, ) } @@ -488,3 +497,7 @@ private fun createBuildOutputRootEvaluator(projectHome: Path, productProperties: val appInfo = ApplicationInfoPropertiesImpl(project, productProperties, buildOptions) projectHome.resolve("out/${productProperties.getOutputDirectoryName(appInfo)}") } + +private fun isNightly(buildNumber: String): Boolean { + return buildNumber.count { it == '.' } <= 1 +} \ No newline at end of file diff --git a/platform/build-scripts/src/org/jetbrains/intellij/build/impl/CachedDescriptorContainer.kt b/platform/build-scripts/src/org/jetbrains/intellij/build/impl/CachedDescriptorContainer.kt new file mode 100644 index 000000000000..09bee6053c2b --- /dev/null +++ b/platform/build-scripts/src/org/jetbrains/intellij/build/impl/CachedDescriptorContainer.kt @@ -0,0 +1,44 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +@file:Suppress("ReplacePutWithAssignment", "ReplaceGetOrSet") + +package org.jetbrains.intellij.build.impl + +import kotlinx.collections.immutable.persistentHashMapOf +import org.jdom.Element + +class CachedDescriptorContainer internal constructor() { + private var cachedXmlFiles = persistentHashMapOf() + + @get:Synchronized + @set:Synchronized + @JvmField + internal var productDescriptor: Element? = null + + fun getCachedFileData(name: String): ByteArray? = cachedXmlFiles.get(name) + + fun mutate(): Mutator { + return object : Mutator { + private var map = persistentHashMapOf() + + @Synchronized + override fun put(name: String, data: ByteArray) { + map = map.put(name, data) + } + + @Synchronized + override fun apply() { + if (map.isNotEmpty()) { + synchronized(cachedXmlFiles) { + cachedXmlFiles = cachedXmlFiles.putAll(map) + } + } + } + } + } + + interface Mutator { + fun put(name: String, data: ByteArray) + + fun apply() + } +} \ No newline at end of file diff --git a/platform/build-scripts/src/org/jetbrains/intellij/build/impl/DistributionBuilderState.kt b/platform/build-scripts/src/org/jetbrains/intellij/build/impl/DistributionBuilderState.kt index 2c3ff1f0fc80..12de4bd94e79 100644 --- a/platform/build-scripts/src/org/jetbrains/intellij/build/impl/DistributionBuilderState.kt +++ b/platform/build-scripts/src/org/jetbrains/intellij/build/impl/DistributionBuilderState.kt @@ -12,7 +12,7 @@ suspend fun createDistributionBuilderState(pluginsToPublish: Set, suspend fun createDistributionBuilderState(context: BuildContext): DistributionBuilderState { val platform = createPlatformLayout(context) - return DistributionBuilderState(platform, pluginsToPublish = emptySet(), context) + return DistributionBuilderState(platform = platform, pluginsToPublish = emptySet(), context = context) } class DistributionBuilderState internal constructor( diff --git a/platform/build-scripts/src/org/jetbrains/intellij/build/impl/DistributionJARsBuilder.kt b/platform/build-scripts/src/org/jetbrains/intellij/build/impl/DistributionJARsBuilder.kt index bf2b8b2ff96c..7bd27cd1f1d6 100644 --- a/platform/build-scripts/src/org/jetbrains/intellij/build/impl/DistributionJARsBuilder.kt +++ b/platform/build-scripts/src/org/jetbrains/intellij/build/impl/DistributionJARsBuilder.kt @@ -259,6 +259,7 @@ private suspend fun buildBundledPluginsForAllPlatforms( common = common, specific = specific, additional = additionalDeferred.await(), + platformLayout = state.platform, context = context, ) common + specific.values.flatten() @@ -270,6 +271,7 @@ private fun writePluginInfo( common: List>>, specific: Map>>>, additional: List>>?, + platformLayout: PlatformLayout, context: BuildContext, ) { val commonClassPath = generatePluginClassPath(pluginEntries = common, moduleOutputPatcher = moduleOutputPatcher) @@ -282,7 +284,7 @@ private fun writePluginInfo( val byteOut = ByteArrayOutputStream() val out = DataOutputStream(byteOut) val pluginCount = common.size + (additional?.size ?: 0) + (specificList?.size ?: 0) - writePluginClassPathHeader(out = out, isJarOnly = true, pluginCount = pluginCount, moduleOutputPatcher = moduleOutputPatcher, context = context) + writePluginClassPathHeader(out = out, isJarOnly = true, pluginCount = pluginCount, platformLayout = platformLayout, context = context) out.write(commonClassPath) additionalClassPath?.let { out.write(it) } specificClasspath?.let { out.write(it) } diff --git a/platform/build-scripts/src/org/jetbrains/intellij/build/impl/PlatformLayout.kt b/platform/build-scripts/src/org/jetbrains/intellij/build/impl/PlatformLayout.kt index 575e46374ced..d7442f299703 100644 --- a/platform/build-scripts/src/org/jetbrains/intellij/build/impl/PlatformLayout.kt +++ b/platform/build-scripts/src/org/jetbrains/intellij/build/impl/PlatformLayout.kt @@ -26,6 +26,9 @@ class PlatformLayout : BaseLayout() { private val projectLibraryToPolicy: MutableMap = HashMap() + @JvmField + val cachedDescriptorContainer: CachedDescriptorContainer = CachedDescriptorContainer() + @get:TestOnly val excludedProjectLibraries: Sequence get() = projectLibraryToPolicy.asSequence().filter { it.value == ProjectLibraryPackagingPolicy.EXCLUDE }.map { it.key } diff --git a/platform/build-scripts/src/org/jetbrains/intellij/build/impl/PlatformModules.kt b/platform/build-scripts/src/org/jetbrains/intellij/build/impl/PlatformModules.kt index 0afb1c6096ef..7a2f26f80c42 100644 --- a/platform/build-scripts/src/org/jetbrains/intellij/build/impl/PlatformModules.kt +++ b/platform/build-scripts/src/org/jetbrains/intellij/build/impl/PlatformModules.kt @@ -1,9 +1,9 @@ // Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. @file:Suppress("ReplaceJavaStaticMethodWithKotlinAnalog", "RedundantSuppression", "ReplaceGetOrSet", "ReplacePutWithAssignment") - package org.jetbrains.intellij.build.impl import com.intellij.openapi.util.JDOMUtil +import com.intellij.platform.plugins.parser.impl.elements.ModuleLoadingRule import io.opentelemetry.api.trace.Span import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet import kotlinx.collections.immutable.PersistentList @@ -15,6 +15,7 @@ import kotlinx.coroutines.withContext import org.jdom.CDATA import org.jdom.Element import org.jetbrains.intellij.build.BuildContext +import org.jetbrains.intellij.build.ContentModuleFilter import org.jetbrains.intellij.build.FrontendModuleFilter import org.jetbrains.intellij.build.PLATFORM_LOADER_JAR import org.jetbrains.intellij.build.UTIL_8_JAR @@ -381,7 +382,7 @@ fun collectExportedLibrariesFromLibraryModules( .filter { it.moduleName.startsWith(LIB_MODULE_PREFIX) } .forEach { moduleItem -> val module = context.findRequiredModule(moduleItem.moduleName) - // Get all library dependencies from the module + // get all library dependencies from the module module.dependenciesList.dependencies .asSequence() .filterIsInstance() @@ -391,7 +392,7 @@ fun collectExportedLibrariesFromLibraryModules( } .mapNotNull { it.library?.name } .forEach { libName -> - result[libName] = moduleItem.moduleName + result.put(libName, moduleItem.moduleName) } } @@ -429,7 +430,7 @@ internal fun computeProjectLibsUsedByPlugins(enabledPluginModules: Set, } val packMode = PLATFORM_CUSTOM_PACK_MODE.getOrDefault(libName, LibraryPackMode.MERGED) - result.addOrGet(ProjectLibraryData(libName, packMode, reason = "<- $moduleName")) + result.addOrGet(ProjectLibraryData(libraryName = libName, packMode = packMode, reason = "<- $moduleName")) .dependentModules .computeIfAbsent(plugin.directoryName) { mutableListOf() } .add(moduleName) @@ -447,7 +448,7 @@ suspend fun getEnabledPluginModules(pluginsToPublish: Set, context } private fun isModuleCloseSource(moduleName: String, context: BuildContext): Boolean { - if (moduleName.endsWith(".resources") || moduleName.endsWith(".icons")) { + if (moduleName.endsWith(".resources") || moduleName.endsWith(".icons") || moduleName.startsWith(LIB_MODULE_PREFIX)) { return false } @@ -552,7 +553,7 @@ private fun computeTransitive( } } -private val regenerateProductSpec = System.getProperty("intellij.build.regenerate.product.spec") != null +private val regenerateProductSpec = System.getProperty("intellij.build.regenerate.product.spec", "true").toBoolean() // result _must be_ consistent, do not use Set.of or HashSet here private suspend fun processAndGetProductPluginContentModules( @@ -560,7 +561,7 @@ private suspend fun processAndGetProductPluginContentModules( layout: PlatformLayout, includedPlatformModulesPartialList: Sequence, ): Set { - val xIncludePathResolver = createXIncludePathResolver(includedPlatformModulesPartialList, context) + val xIncludePathResolver = createXIncludePathResolver(includedPlatformModulesPartialList.distinct().toList(), context) return withContext(Dispatchers.IO) { val productPluginSourceModuleName = context.productProperties.applicationInfoModule val file = requireNotNull( @@ -568,43 +569,46 @@ private suspend fun processAndGetProductPluginContentModules( ?: context.findFileInModuleSources(moduleName = productPluginSourceModuleName, relativePath = "META-INF/${context.productProperties.platformPrefix}Plugin.xml") ) { "Cannot find product plugin descriptor in '$productPluginSourceModuleName' module" } + val originalContent: String val xml: Element // process programmatic content modules if defined val programmaticModulesSpec = context.productProperties.getProductContentDescriptor() - val result = if (programmaticModulesSpec == null || !regenerateProductSpec) { - xml = JDOMUtil.load(file) + if (programmaticModulesSpec == null || !regenerateProductSpec) { + originalContent = Files.readString(file) + xml = JDOMUtil.load(originalContent) resolveNonXIncludeElement(original = xml, base = file, pathResolver = xIncludePathResolver, trackSourceFile = true) - collectAndEmbedProductModules(root = xml, xIncludePathResolver = xIncludePathResolver, context = context) } else { val sb = StringBuilder() val contentBlocks = buildProductContentXml( spec = programmaticModulesSpec, moduleOutputProvider = context, - sb = sb, inlineXmlIncludes = true + sb = sb, + inlineXmlIncludes = true, ) Span.current().addEvent("Generated ${contentBlocks.size} content blocks with ${contentBlocks.sumOf { it.modules.size }} total modules") + originalContent = sb.toString() xml = JDOMUtil.load(sb) resolveNonXIncludeElement(original = xml, base = file, pathResolver = xIncludePathResolver, trackSourceFile = false) - - collectAndEmbedProductModules(root = xml, xIncludePathResolver = xIncludePathResolver, context = context) } + val moduleItems = collectAndEmbedProductModules(root = xml, xIncludePathResolver = xIncludePathResolver, context = context) val data = JDOMUtil.write(xml) - val fileName = file.fileName.toString() - layout.withPatch { moduleOutputPatcher, _, _ -> - moduleOutputPatcher.patchModuleOutput(moduleName = productPluginSourceModuleName, path = "META-INF/$fileName", content = data) + if (data != originalContent) { + layout.withPatch { moduleOutputPatcher, _, _ -> + moduleOutputPatcher.patchModuleOutput(moduleName = productPluginSourceModuleName, path = "META-INF/${file.fileName}", content = data) + } } + layout.cachedDescriptorContainer.productDescriptor = xml - result + moduleItems } } // todo implement correct processing @Suppress("RemoveRedundantQualifierName") private val excludedPaths = java.util.Set.of( - "/META-INF/ultimate.xml", "/META-INF/cwmBackendConnection.xml", "/META-INF/cwmConnectionFrontend.xml", "/META-INF/clientUltimate.xml", @@ -616,7 +620,10 @@ private val COMMUNITY_IMPL_EXTENSIONS = setOf( "/META-INF/community-extensions.xml" ) -fun createXIncludePathResolver(includedPlatformModulesPartialList: Sequence, context: BuildContext): XIncludePathResolver { +fun createXIncludePathResolver( + includedPlatformModulesPartialList: List, + context: BuildContext, +): XIncludePathResolver { return object : XIncludePathResolver { override fun resolvePath(relativePath: String, base: Path?, isOptional: Boolean, isDynamic: Boolean): Path? { if ((isOptional || isDynamic || excludedPaths.contains(relativePath)) && !COMMUNITY_IMPL_EXTENSIONS.contains(relativePath)) { @@ -627,6 +634,17 @@ fun createXIncludePathResolver(includedPlatformModulesPartialList: Sequence relativePath.substring(1) relativePath.startsWith("intellij.") || relativePath.startsWith("fleet.") -> relativePath @@ -700,50 +718,89 @@ private fun getModuleDescriptor(moduleName: String, jpsModuleName: String, xIncl private suspend fun collectAndEmbedProductModules(root: Element, xIncludePathResolver: XIncludePathResolver, context: BuildContext): Set { val frontendModuleFilter = context.getFrontendModuleFilter() val contentModuleFilter = context.getContentModuleFilter() - val result = LinkedHashSet() - val moduleElements = root.getChildren("content").flatMap { it.getChildren("module") } - for (moduleElement in moduleElements) { - val moduleName = moduleElement.getAttributeValue("name") ?: continue - val loadingRule = moduleElement.getAttributeValue("loading") - val dependencyHelper = (context as BuildContextImpl).jarPackagerDependencyHelper - if (dependencyHelper.isOptionalLoadingRule(loadingRule) && !contentModuleFilter.isOptionalModuleIncluded(moduleName, pluginMainModuleName = null)) { - Span.current().addEvent("Tag for module '$moduleName' is removed from the core plugin by $contentModuleFilter") - moduleElement.parent.removeContent(moduleElement) - continue - } - - val isEmbedded = loadingRule == "embedded" - val relativeOutFile = if (isEmbedded && isModuleCloseSource(moduleName = moduleName, context = context)) { - if (frontendModuleFilter.isBackendModule(moduleName)) PRODUCT_BACKEND_JAR else PRODUCT_JAR - } - else { - "$moduleName.jar" - } - - // extract module set from parent element's source-file attribute - val contentElement = moduleElement.parentElement - val moduleSet = contentElement?.getAttributeValue(SOURCE_FILE_ATTRIBUTE) - result.add( - ModuleItem( - moduleName = moduleName, - relativeOutputFile = relativeOutFile, - reason = if (isEmbedded) ModuleIncludeReasons.PRODUCT_EMBEDDED_MODULES else ModuleIncludeReasons.PRODUCT_MODULES, - moduleSet = moduleSet, + val moduleItems = LinkedHashSet() + for (content in root.getChildren("content")) { + val iterator = content.getChildren("module").iterator() + while (iterator.hasNext()) { + processProductModule( + iterator = iterator, + context = context, + contentModuleFilter = contentModuleFilter, + frontendModuleFilter = frontendModuleFilter, + result = moduleItems, + xIncludePathResolver = xIncludePathResolver, ) - ) - PRODUCT_MODULE_IMPL_COMPOSITION.get(moduleName)?.let { list -> - list - .filter { !context.productProperties.productLayout.productImplementationModules.contains(it) } - .mapTo(result) { subModuleName -> - ModuleItem(moduleName = subModuleName, relativeOutputFile = relativeOutFile, reason = ModuleIncludeReasons.PRODUCT_MODULES, moduleSet = moduleSet) - } } + } + return moduleItems +} +private fun processProductModule( + iterator: MutableIterator, + context: BuildContext, + contentModuleFilter: ContentModuleFilter, + frontendModuleFilter: FrontendModuleFilter, + result: LinkedHashSet, + xIncludePathResolver: XIncludePathResolver, +) { + val moduleElement = iterator.next() + val moduleName = moduleElement.getAttributeValue("name") ?: return + val loadingRule = moduleElement.getAttributeValue("loading") + val dependencyHelper = (context as BuildContextImpl).jarPackagerDependencyHelper + if (dependencyHelper.isOptionalLoadingRule(loadingRule) && !contentModuleFilter.isOptionalModuleIncluded(moduleName = moduleName, pluginMainModuleName = null)) { + Span.current().addEvent("Tag for module '$moduleName' is removed from the core plugin by $contentModuleFilter") + iterator.remove() + return + } + + val isEmbedded = loadingRule == ModuleLoadingRule.EMBEDDED.name.lowercase() + val isInScrambledFile = isEmbedded && isModuleCloseSource(moduleName = moduleName, context = context) + val relativeOutFile = if (isInScrambledFile) { + if (frontendModuleFilter.isBackendModule(moduleName)) PRODUCT_BACKEND_JAR else PRODUCT_JAR + } + else { + "$moduleName.jar" + } + + // extract module set from parent element's source-file attribute + val contentElement = moduleElement.parentElement + val moduleSet = contentElement?.getAttributeValue(SOURCE_FILE_ATTRIBUTE) + result.add( + ModuleItem( + moduleName = moduleName, + relativeOutputFile = relativeOutFile, + reason = if (isEmbedded) ModuleIncludeReasons.PRODUCT_EMBEDDED_MODULES else ModuleIncludeReasons.PRODUCT_MODULES, + moduleSet = moduleSet, + ) + ) + PRODUCT_MODULE_IMPL_COMPOSITION.get(moduleName)?.let { list -> + list + .filter { !context.productProperties.productLayout.productImplementationModules.contains(it) } + .mapTo(result) { subModuleName -> + ModuleItem(moduleName = subModuleName, relativeOutputFile = relativeOutFile, reason = ModuleIncludeReasons.PRODUCT_MODULES, moduleSet = moduleSet) + } + } + + // We do not embed the module descriptor because scrambling can rename classes. + // + // However, we cannot rely solely on the `PLUGIN_CLASSPATH` descriptor: for non-embedded modules, + // xi:included files (e.g., META-INF/VcsExtensionPoints.xml) are not resolvable from the core classpath, + // since a non-embedded module uses a separate classloader. + // + // Because scrambling applies only (by policy) to embedded modules, we embed the module descriptor + // for non-embedded modules to address this. + // + // Note: We could implement runtime loading via the module’s classloader, but that would + // significantly complicate the runtime code. + if (!isInScrambledFile) { check(moduleElement.content.isEmpty()) val moduleDescriptor = getModuleDescriptor(moduleName = moduleName, jpsModuleName = moduleName, xIncludePathResolver = xIncludePathResolver, context = context) moduleElement.setContent(CDATA(JDOMUtil.write(moduleDescriptor))) } - return result + // For Gateway or a module-based loader, where PLUGIN_CLASSPATH isn’t used, performance will be slightly affected + // (most product modules shouldn’t be embedded anyway). + // That’s acceptable because remote development will be migrated to the path-based class loader anyway. + // We prefer not to increase code complexity without a strong reason. } // Contrary to what it looks like, this is not a step back. diff --git a/platform/build-scripts/src/org/jetbrains/intellij/build/impl/PluginXmlPatcher.kt b/platform/build-scripts/src/org/jetbrains/intellij/build/impl/PluginXmlPatcher.kt index 7f66e137460b..74edc4caacc3 100644 --- a/platform/build-scripts/src/org/jetbrains/intellij/build/impl/PluginXmlPatcher.kt +++ b/platform/build-scripts/src/org/jetbrains/intellij/build/impl/PluginXmlPatcher.kt @@ -79,7 +79,9 @@ internal suspend fun patchPluginXml( xml = element, file = findFileInModuleSources(module = pluginModule, relativePath = "META-INF/plugin.xml")!!, xIncludePathResolver = createXIncludePathResolver( - includedPlatformModulesPartialList = plugin.includedModules.asSequence().map { it.moduleName } + platformLayout.includedModules.asSequence().map { it.moduleName }, + includedPlatformModulesPartialList = (plugin.includedModules.asSequence().map { it.moduleName } + platformLayout.includedModules.asSequence().map { it.moduleName }) + .distinct() + .toList(), context = context, ), layout = plugin, @@ -164,7 +166,7 @@ fun getOrCreateTopElement(rootElement: Element, tagName: String, anchors: List { val contentReport = generateProjectStructureMapping(platformLayout, context) diff --git a/platform/build-scripts/src/org/jetbrains/intellij/build/impl/projectStructureMapping/ProjectStructureMapping.kt b/platform/build-scripts/src/org/jetbrains/intellij/build/impl/projectStructureMapping/ProjectStructureMapping.kt index f750c98f62a3..14239e6d00c7 100644 --- a/platform/build-scripts/src/org/jetbrains/intellij/build/impl/projectStructureMapping/ProjectStructureMapping.kt +++ b/platform/build-scripts/src/org/jetbrains/intellij/build/impl/projectStructureMapping/ProjectStructureMapping.kt @@ -11,11 +11,12 @@ import org.jetbrains.intellij.build.BuildPaths import org.jetbrains.intellij.build.DistFile import org.jetbrains.intellij.build.MAVEN_REPO import org.jetbrains.intellij.build.PluginBuildDescriptor -import org.jetbrains.intellij.build.impl.MODULE_SET_CHAIN_SEPARATOR import org.jetbrains.intellij.build.impl.ModuleIncludeReasons import org.jetbrains.intellij.build.impl.ModuleItem import org.jetbrains.intellij.build.impl.ProjectLibraryData import org.jetbrains.intellij.build.io.ZipFileWriter +import org.jetbrains.intellij.build.productLayout.buildModuleSetIncludes +import org.jetbrains.intellij.build.productLayout.buildModuleToSetMapping import java.io.ByteArrayOutputStream import java.io.File import java.nio.file.Path @@ -29,55 +30,73 @@ internal fun getIncludedModules(entries: Sequence): Seque internal fun buildJarContentReport(contentReport: ContentReport, zipFileWriter: ZipFileWriter, buildPaths: BuildPaths, context: BuildContext) { val (fileToEntry, productModules) = groupPlatformEntries(contentReport = contentReport, buildPaths = buildPaths) + // Build module-to-set mapping from all configured module sets providers + val moduleToSetsMapping = buildModuleToSetMapping(context.productProperties.moduleSetsProviders) + val allModuleSets = TreeMap>>>() - val moduleSetIncludes = TreeMap>() - val directModuleSets = TreeMap>>>() + // Group modules by their module sets using the build-time mapping for (entry in productModules) { - val chain = entry.first.moduleSet ?: continue + val moduleName = entry.first.moduleName - // parse chain once - val parts = chain.split(MODULE_SET_CHAIN_SEPARATOR) - val moduleSetName = parts.last() - - // skip non-module-sets - if (!moduleSetName.startsWith("intellij.moduleSets.")) { + // Look up which module set(s) this module belongs to + val moduleSets = moduleToSetsMapping[moduleName] + if (moduleSets == null) { + // Module not in any module set (e.g., additional product modules) continue } - // add to allModuleSets (all module sets used by product) - allModuleSets.computeIfAbsent(moduleSetName) { mutableListOf() }.add(entry) - - // add to directModuleSets if no separator (directly included by product) - if (parts.size == 1) { - directModuleSets.computeIfAbsent(moduleSetName) { mutableListOf() }.add(entry) + for (moduleSetName in moduleSets) { + allModuleSets.computeIfAbsent(moduleSetName) { mutableListOf() }.add(entry) } + } - // extract include relationship if chain exists (parent includes child) - if (parts.size >= 2) { - val parentModuleSet = parts[parts.size - 2] - if (parentModuleSet.startsWith("intellij.moduleSets.")) { - moduleSetIncludes.computeIfAbsent(parentModuleSet) { TreeSet() }.add(moduleSetName) + // Build module set includes from all configured module sets providers + val moduleSetIncludes = buildModuleSetIncludes(context.productProperties.moduleSetsProviders) + + // Filter to only root module sets (not nested in other product module sets) + val rootModuleSets = TreeMap>>>() + val nestedModuleSetNames = mutableSetOf() + + // Recursively collect all nested module sets at any depth + fun collectNestedSetsRecursively(moduleSetName: String) { + moduleSetIncludes[moduleSetName]?.forEach { nestedSetName -> + if (nestedModuleSetNames.add(nestedSetName)) { + // Recursively collect nested sets of this nested set + collectNestedSetsRecursively(nestedSetName) } } } + // Collect all nested module set names from module sets used by product + for (moduleSetName in allModuleSets.keys) { + collectNestedSetsRecursively(moduleSetName) + } + + // Only include module sets that aren't nested in other product module sets + for ((moduleSetName, modules) in allModuleSets) { + if (moduleSetName !in nestedModuleSetNames) { + rootModuleSets[moduleSetName] = modules + } + } + val platformData = buildPlatformContentReport( contentReport = contentReport, buildPaths = buildPaths, distFiles = context.getDistFiles(os = null, arch = null, libcImpl = null), fileToEntry = fileToEntry, productModules = productModules, - moduleSets = directModuleSets, + moduleSets = rootModuleSets, ) zipFileWriter.uncompressedData("platform.yaml", platformData) zipFileWriter.uncompressedData("product-modules.yaml", buildProductModuleContentReport(productModules, buildPaths)) // Write module set YAMLs with both direct modules and included module sets for ((moduleSetName, modules) in allModuleSets) { - val entries = mutableListOf() + // Use Set to avoid duplicates (same module can appear in multiple JARs) + val entries = TreeSet() - // Add direct module names + // Add direct module names (deduplicated via Set) entries.addAll(modules.map { it.first.moduleName }) // Add included module set names (preserving hierarchy) diff --git a/platform/build-scripts/src/org/jetbrains/intellij/build/impl/xIncludeResolver.kt b/platform/build-scripts/src/org/jetbrains/intellij/build/impl/xIncludeResolver.kt index 76563c6a5452..40cfd55c1b1f 100644 --- a/platform/build-scripts/src/org/jetbrains/intellij/build/impl/xIncludeResolver.kt +++ b/platform/build-scripts/src/org/jetbrains/intellij/build/impl/xIncludeResolver.kt @@ -9,7 +9,8 @@ import org.jdom.Element import org.jdom.Namespace import java.io.IOException import java.nio.file.Path -import java.util.* +import java.util.ArrayDeque +import java.util.Deque /** * The original element will be mutated in place. @@ -179,4 +180,128 @@ private fun doResolveNonXIncludeElement(original: Element, bases: Deque, p interface XIncludePathResolver { // return null if there is no need to resolve x-include fun resolvePath(relativePath: String, base: Path?, isOptional: Boolean, isDynamic: Boolean): Path? +} + +/** + * Resolver that returns JDOM Elements directly instead of file paths. + * Useful when working with preloaded/cached XML documents. + */ +interface XIncludeElementResolver { + // Return the Element for the given href, or null if not found/optional + fun resolveElement(relativePath: String, isOptional: Boolean, isDynamic: Boolean): Element? +} + +/** + * Resolve XIncludes using preloaded Elements instead of file paths. + * Useful when working with cached/scrambled descriptors already in memory. + * The original element will be mutated in place. + */ +internal fun resolveNonXIncludeElementFromCache(original: Element, elementResolver: XIncludeElementResolver, trackSourceFile: Boolean = false) { + check(!isIncludeElement(original)) + doResolveNonXIncludeElementFromCache(original = original, elementResolver = elementResolver, trackSourceFile = trackSourceFile) +} + +private fun resolveXIncludeElementFromCache( + element: Element, + elementResolver: XIncludeElementResolver, + trackSourceFile: Boolean +): MutableList? { + val href = requireNotNull(element.getAttributeValue("href")) { "Missing href attribute" } + + val baseAttribute = element.getAttributeValue("base", Namespace.XML_NAMESPACE) + if (baseAttribute != null) { + throw UnsupportedOperationException("`base` attribute is not supported") + } + + val fallbackElement = element.getChild("fallback", element.namespace) + val remoteElement = elementResolver.resolveElement( + relativePath = href, + isOptional = fallbackElement != null, + isDynamic = element.getAttribute("includeUnless") != null || element.getAttribute("includeIf") != null, + ) ?: return null + + val remoteParsed = extractNeededChildren(element, remoteElement) + + // Add source-file attribute to elements if tracking is enabled + if (trackSourceFile) { + for (resolvedElement in remoteParsed) { + if (resolvedElement.name == "content") { + val existingChain = resolvedElement.getAttributeValue(SOURCE_FILE_ATTRIBUTE) + // For cached elements, we use href as the source identifier + val currentName = href.removeSuffix(".xml") + + if (currentName.startsWith("intellij.moduleSets.")) { + val newChain = if (existingChain != null) { + "$currentName$MODULE_SET_CHAIN_SEPARATOR$existingChain" + } + else { + currentName + } + resolvedElement.setAttribute(SOURCE_FILE_ATTRIBUTE, newChain) + } + } + } + } + + var i = 0 + while (true) { + if (i >= remoteParsed.size) { + break + } + + val o = remoteParsed.get(i) + if (isIncludeElement(o)) { + val elements = resolveXIncludeElementFromCache( + element = o, + elementResolver = elementResolver, + trackSourceFile = trackSourceFile + ) + if (elements != null) { + remoteParsed.addAll(i, elements) + i += elements.size - 1 + remoteParsed.removeAt(i) + } + } + else { + doResolveNonXIncludeElementFromCache( + original = o, + elementResolver = elementResolver, + trackSourceFile = trackSourceFile + ) + } + + i++ + } + + for (element in remoteParsed) { + element.detach() + } + return remoteParsed +} + +private fun doResolveNonXIncludeElementFromCache( + original: Element, + elementResolver: XIncludeElementResolver, + trackSourceFile: Boolean +) { + val contentList = original.content + for (i in contentList.size - 1 downTo 0) { + val content = contentList.get(i) + if (content is Element) { + if (isIncludeElement(content)) { + val result = resolveXIncludeElementFromCache(element = content, elementResolver = elementResolver, trackSourceFile = trackSourceFile) + if (result != null) { + original.setContent(i, result) + } + } + else { + // process child element to resolve possible includes + doResolveNonXIncludeElementFromCache( + original = content, + elementResolver = elementResolver, + trackSourceFile = trackSourceFile + ) + } + } + } } \ No newline at end of file diff --git a/platform/build-scripts/src/org/jetbrains/intellij/build/productLayout/CommunityModuleSets.kt b/platform/build-scripts/src/org/jetbrains/intellij/build/productLayout/CommunityModuleSets.kt index 2187fc5da246..7aeef9b61b8b 100644 --- a/platform/build-scripts/src/org/jetbrains/intellij/build/productLayout/CommunityModuleSets.kt +++ b/platform/build-scripts/src/org/jetbrains/intellij/build/productLayout/CommunityModuleSets.kt @@ -3,6 +3,7 @@ package org.jetbrains.intellij.build.productLayout import com.intellij.openapi.application.PathManager import com.intellij.platform.plugins.parser.impl.elements.ModuleLoadingRule +import org.jetbrains.intellij.build.BuildPaths import java.nio.file.Path /** @@ -12,7 +13,11 @@ import java.nio.file.Path * Products call these functions directly in ProductProperties.getProductContentModules() to create * ModuleSet instances that will be automatically injected into the product plugin.xml during build. */ -object CommunityModuleSets { +object CommunityModuleSets : ModuleSetProvider { + override fun getOutputDirectory(paths: BuildPaths): Path { + return paths.communityHomeDir.resolve("platform/platform-resources/generated") + } + /** * Main method to regenerate all community module set XML files from Kotlin definitions. * Run this whenever module sets are modified to keep XML files in sync. diff --git a/platform/build-scripts/src/org/jetbrains/intellij/build/productLayout/ModuleSetBuilder.kt b/platform/build-scripts/src/org/jetbrains/intellij/build/productLayout/ModuleSetBuilder.kt index c2ba53383f3d..cea671d8179c 100644 --- a/platform/build-scripts/src/org/jetbrains/intellij/build/productLayout/ModuleSetBuilder.kt +++ b/platform/build-scripts/src/org/jetbrains/intellij/build/productLayout/ModuleSetBuilder.kt @@ -2,6 +2,7 @@ package org.jetbrains.intellij.build.productLayout import com.intellij.platform.plugins.parser.impl.elements.ModuleLoadingRule +import org.jetbrains.intellij.build.BuildPaths import java.lang.invoke.MethodHandles import java.lang.invoke.MethodType import java.nio.file.Files @@ -23,6 +24,17 @@ data class ModuleSet( @JvmField val alias: String? = null, ) +/** + * Interface for module set providers that can generate XML files. + * Provides the output directory where generated module set XML files are stored. + */ +interface ModuleSetProvider { + /** + * Returns the path to the META-INF directory where this provider's module set XML files are generated. + */ + fun getOutputDirectory(paths: BuildPaths): Path +} + /** * DSL builder for creating ModuleSets with reduced boilerplate. */ @@ -88,6 +100,62 @@ internal fun buildModuleAliasXml(alias: String?): String { return if (alias == null) "" else " \n" } +/** + * Appends a single module XML element to the StringBuilder. + */ +private fun appendModuleXml(sb: StringBuilder, module: ContentModule) { + sb.append(" ") + sb.append("\n") +} + +/** + * Recursively collects all module aliases from a module set and its nested sets. + */ +private fun collectAllAliases(moduleSet: ModuleSet): List { + val aliases = mutableListOf() + if (moduleSet.alias != null) { + aliases.add(moduleSet.alias) + } + moduleSet.nestedSets.forEach { aliases.addAll(collectAllAliases(it)) } + return aliases +} + +/** + * Recursively appends modules from a module set, including nested sets. + * Handles nested sets at any depth with breadcrumb trail showing full hierarchy. + */ +private fun appendModuleSetContent(sb: StringBuilder, moduleSet: ModuleSet, indent: String = " ", breadcrumb: String = "") { + // Get direct modules (not from nested sets) + val nestedModuleNames = moduleSet.nestedSets.flatMap { it.modules.map { m -> m.name } }.toHashSet() + val directModules = moduleSet.modules.filter { it.name !in nestedModuleNames } + + // Recursively append nested sets first + for (nestedSet in moduleSet.nestedSets) { + // Build breadcrumb path + val nestedBreadcrumb = if (breadcrumb.isEmpty()) { + nestedSet.name + } else { + "$breadcrumb > ${nestedSet.name}" + } + + sb.append("$indent\n") + appendModuleSetContent(sb, nestedSet, indent, nestedBreadcrumb) // RECURSIVE CALL with breadcrumb + sb.append("\n") + } + + // Then append direct modules + if (directModules.isNotEmpty()) { + if (moduleSet.nestedSets.isNotEmpty()) { + sb.append("$indent\n") + } + directModules.forEach { appendModuleXml(sb, it) } + } +} + /** * Builds the XML content for a module set. * @@ -96,12 +164,6 @@ internal fun buildModuleAliasXml(alias: String?): String { * @return XML string representation of the module set */ internal fun buildModuleSetXml(moduleSet: ModuleSet, label: String): String { - val hasNestedSets = moduleSet.nestedSets.isNotEmpty() - - // Get direct modules (not from nested sets) - val nestedModuleNames = moduleSet.nestedSets.flatMap { it.modules.map { m -> m.name } }.toSet() - val directModules = moduleSet.modules.filter { it.name !in nestedModuleNames } - val sb = StringBuilder() // Add generated file header @@ -111,47 +173,25 @@ internal fun buildModuleSetXml(moduleSet: ModuleSet, label: String): String { sb.append("\n") sb.append("\n") - // Opening tag - if (hasNestedSets) { - sb.append("") - } else { - sb.append("") - } + // Opening tag (no xmlns:xi needed since we inline nested sets) + sb.append("") sb.append("\n") - // Module alias (if present) - val aliasXml = buildModuleAliasXml(moduleSet.alias) - if (aliasXml.isNotEmpty()) { - sb.append(aliasXml) + // Output all module aliases (recursively collected from this set and nested sets) + val allAliases = collectAllAliases(moduleSet) + if (allAliases.isNotEmpty()) { + allAliases.forEach { sb.append(" \n") } sb.append("\n") } - // `xi:include`s for nested sets - if (hasNestedSets) { - for (nestedSet in moduleSet.nestedSets) { - sb.append(" ") - sb.append("\n") - } - - // Add blank line after `xi:include`s if there are direct modules - if (directModules.isNotEmpty()) { - sb.append("\n") - } - } - - // Direct content modules - if (directModules.isNotEmpty()) { + // Collect all modules (nested + direct) and output in a single content block + val hasAnyModules = moduleSet.nestedSets.isNotEmpty() || moduleSet.modules.isNotEmpty() + if (hasAnyModules) { sb.append(" ") sb.append("\n") - for (module in directModules) { - sb.append(" ") - sb.append("\n") - } + // Recursively append all content (handles nested sets at any depth) + appendModuleSetContent(sb, moduleSet) sb.append(" ") sb.append("\n") @@ -189,6 +229,99 @@ private fun discoverModuleSets(obj: Any): List { return result } +/** + * Builds a reverse index mapping each module name to the list of module sets that contain it. + * Recursively walks through all module sets and their nested sets. + * + * This is useful for tracking which module set(s) a module belongs to, especially when + * module sets are inlined (no xi:include) and runtime tracking is unavailable. + * + * @param moduleSetProviders List of objects containing module set definitions (e.g., UltimateModuleSets, CommunityModuleSets) + * @return Map from module name to list of module set names (with "intellij.moduleSets." prefix) + */ +fun buildModuleToSetMapping(moduleSetProviders: List): Map> { + val moduleToSets = mutableMapOf>() + val processedSets = mutableSetOf() + + /** + * Recursively collects modules from a module set and its nested sets. + */ + fun collectModulesRecursively(set: ModuleSet, setName: String) { + // Skip if already processed (prevents duplicates when a module set is both top-level and nested) + if (!processedSets.add(setName)) { + return + } + + // Get direct modules (not from nested sets) + val nestedModuleNames = set.nestedSets.flatMap { it.modules.map { m -> m.name } }.toHashSet() + val directModules = set.modules.filter { it.name !in nestedModuleNames } + + // Add direct modules to this set + for (module in directModules) { + moduleToSets.computeIfAbsent(module.name) { mutableListOf() }.add(setName) + } + + // Recursively process nested sets + for (nestedSet in set.nestedSets) { + collectModulesRecursively(nestedSet, "intellij.moduleSets.${nestedSet.name}") + } + } + + // Process all providers + for (provider in moduleSetProviders) { + val moduleSets = discoverModuleSets(provider) + // Process all top-level module sets from this provider + for (moduleSet in moduleSets) { + collectModulesRecursively(moduleSet, "intellij.moduleSets.${moduleSet.name}") + } + } + + return moduleToSets +} + +/** + * Builds a mapping of parent module sets to their nested module sets. + * This preserves the hierarchical structure after inlining. + * + * @param moduleSetProviders List of objects containing module set definitions (e.g., UltimateModuleSets, CommunityModuleSets) + * @return Map from module set name to set of nested module set names (with "intellij.moduleSets." prefix) + */ +fun buildModuleSetIncludes(moduleSetProviders: List): Map> { + val moduleSetToIncludes = mutableMapOf>() + val processedSets = mutableSetOf() + + /** + * Recursively collects nested module sets. + */ + fun collectNestedSetsRecursively(set: ModuleSet, setName: String) { + // Skip if already processed (prevents duplicates when a module set is both top-level and nested) + if (!processedSets.add(setName)) { + return + } + + if (set.nestedSets.isNotEmpty()) { + val includes = moduleSetToIncludes.computeIfAbsent(setName) { mutableSetOf() } + for (nestedSet in set.nestedSets) { + val nestedSetName = "intellij.moduleSets.${nestedSet.name}" + includes.add(nestedSetName) + // Recursively process nested sets + collectNestedSetsRecursively(nestedSet, nestedSetName) + } + } + } + + // Process all providers + for (provider in moduleSetProviders) { + val moduleSets = discoverModuleSets(provider) + // Process all top-level module sets from this provider + for (moduleSet in moduleSets) { + collectNestedSetsRecursively(moduleSet, "intellij.moduleSets.${moduleSet.name}") + } + } + + return moduleSetToIncludes +} + /** * Generates all module set XMLs for the given object. * Discovers all ModuleSet functions via reflection, generates XML files, and prints results. @@ -209,7 +342,7 @@ fun generateAllModuleSets(obj: Any, outputDir: Path, label: String, printSummary if (printSummary) { val result = ModuleSetGenerationResult(label, outputDir, fileResults) - printGenerationSummary(listOf(result), null, System.currentTimeMillis() - startTime) + printGenerationSummary(moduleSetResults = listOf(result), productResult = null, durationMs = System.currentTimeMillis() - startTime) } } @@ -222,8 +355,8 @@ fun doGenerateAllModuleSetsInternal(obj: Any, outputDir: Path, label: String): M val moduleSets = discoverModuleSets(obj) val fileResults = moduleSets.map { moduleSet -> - generateModuleSetXml(moduleSet, outputDir, label) + generateModuleSetXml(moduleSet = moduleSet, outputDir = outputDir, label = label) } - return ModuleSetGenerationResult(label, outputDir, fileResults) + return ModuleSetGenerationResult(label = label, outputDir = outputDir, files = fileResults) } \ No newline at end of file diff --git a/platform/build-scripts/src/org/jetbrains/intellij/build/productLayout/generator.kt b/platform/build-scripts/src/org/jetbrains/intellij/build/productLayout/generator.kt index 5ca82765ee19..23b7bacc1055 100644 --- a/platform/build-scripts/src/org/jetbrains/intellij/build/productLayout/generator.kt +++ b/platform/build-scripts/src/org/jetbrains/intellij/build/productLayout/generator.kt @@ -30,11 +30,12 @@ fun generateModuleSetXml(moduleSet: ModuleSet, outputDir: Path, label: String): val xml = buildModuleSetXml(moduleSet, label) - // Determine change status + // determine change status val status = if (Files.exists(outputPath)) { val existingContent = Files.readString(outputPath) if (existingContent == xml) FileChangeStatus.UNCHANGED else FileChangeStatus.MODIFIED - } else { + } + else { FileChangeStatus.CREATED } @@ -92,7 +93,7 @@ fun generateProductXml( spec: ProductModulesContentSpec, productName: String, moduleOutputProvider: ModuleOutputProvider, - projectRoot: Path + projectRoot: Path, ): ProductFileResult { // Determine which generator to recommend based on plugin.xml file location // Community products are under community/ directory, Ultimate products are not diff --git a/platform/build-scripts/src/org/jetbrains/intellij/build/productRunner/IntellijProcessStarter.kt b/platform/build-scripts/src/org/jetbrains/intellij/build/productRunner/IntellijProcessStarter.kt index b04a503d28d4..6832d6ac98f0 100644 --- a/platform/build-scripts/src/org/jetbrains/intellij/build/productRunner/IntellijProcessStarter.kt +++ b/platform/build-scripts/src/org/jetbrains/intellij/build/productRunner/IntellijProcessStarter.kt @@ -70,7 +70,7 @@ suspend fun runApplicationStarter( debugPropertyValue?.let { jvmArgs.add("-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=*:$it") } - val actualTimeout = if (debugPropertyValue != null) 20.minutes else timeout + val actualTimeout = if (debugPropertyValue == null) timeout else 20.minutes val effectiveIdeClasspath = if (isFinalClassPath) classpath else prepareFlatClasspath(classpath = classpath, tempDir = tempDir, context = context) try { diff --git a/platform/build-scripts/testFramework/src/com/intellij/platform/buildScripts/testFramework/PrivateModulesCheckerUtil.kt b/platform/build-scripts/testFramework/src/com/intellij/platform/buildScripts/testFramework/PrivateModulesCheckerUtil.kt index dca01fec1814..88157a40bfaa 100644 --- a/platform/build-scripts/testFramework/src/com/intellij/platform/buildScripts/testFramework/PrivateModulesCheckerUtil.kt +++ b/platform/build-scripts/testFramework/src/com/intellij/platform/buildScripts/testFramework/PrivateModulesCheckerUtil.kt @@ -17,7 +17,9 @@ suspend fun checkPrivatePluginModulesAreNotPublic( softly: SoftAssertions, ) { val privateModules = getPrivateModules(context) - if (privateModules.isEmpty()) return + if (privateModules.isEmpty()) { + return + } val visited = mutableSetOf() val bundledPrivateModules = context.productProperties.productLayout.bundledPluginModules.asSequence() diff --git a/platform/build-scripts/testFramework/src/com/intellij/platform/buildScripts/testFramework/buildScriptTestUtils.kt b/platform/build-scripts/testFramework/src/com/intellij/platform/buildScripts/testFramework/buildScriptTestUtils.kt index fe0503fead2c..ba8a461953fd 100644 --- a/platform/build-scripts/testFramework/src/com/intellij/platform/buildScripts/testFramework/buildScriptTestUtils.kt +++ b/platform/build-scripts/testFramework/src/com/intellij/platform/buildScripts/testFramework/buildScriptTestUtils.kt @@ -128,6 +128,7 @@ fun runTestBuild( buildTools: ProprietaryBuildTools = ProprietaryBuildTools.DUMMY, isReproducibilityTestAllowed: Boolean = true, checkIntegrityOfEmbeddedFrontend: Boolean = true, + checkPrivatePluginModulesAreNotPublic: Boolean = true, build: suspend (BuildContext) -> Unit = { buildDistributions(context = it) }, onSuccess: suspend (BuildContext) -> Unit = {}, buildOptionsCustomizer: (BuildOptions) -> Unit = {} @@ -154,6 +155,7 @@ fun runTestBuild( traceSpanName = "${testInfo.spanName}#${iterationNumber}", writeTelemetry = false, checkIntegrityOfEmbeddedFrontend = checkIntegrityOfEmbeddedFrontend, + checkPrivatePluginModulesAreNotPublic = checkPrivatePluginModulesAreNotPublic, checkThatBundledPluginInFrontendArePresent = checkIntegrityOfEmbeddedFrontend, build = { context -> build(context) @@ -195,6 +197,7 @@ suspend fun runTestBuild( testInfo: TestInfo, context: suspend () -> BuildContext, checkThatBundledPluginInFrontendArePresent: Boolean = true, + checkPrivatePluginModulesAreNotPublic: Boolean = true, build: suspend (BuildContext) -> Unit = { buildDistributions(it) } ) { doRunTestBuild( @@ -203,6 +206,7 @@ suspend fun runTestBuild( writeTelemetry = true, checkIntegrityOfEmbeddedFrontend = true, checkThatBundledPluginInFrontendArePresent = checkThatBundledPluginInFrontendArePresent, + checkPrivatePluginModulesAreNotPublic = checkPrivatePluginModulesAreNotPublic, build = build, ) } @@ -215,6 +219,7 @@ private suspend fun doRunTestBuild( writeTelemetry: Boolean, checkIntegrityOfEmbeddedFrontend: Boolean, checkThatBundledPluginInFrontendArePresent: Boolean, + checkPrivatePluginModulesAreNotPublic: Boolean = true, build: suspend (context: BuildContext) -> Unit, ) { var outDir: Path? = null @@ -248,7 +253,9 @@ private suspend fun doRunTestBuild( } } - checkPrivatePluginModulesAreNotPublic(context, softly) + if (checkPrivatePluginModulesAreNotPublic) { + checkPrivatePluginModulesAreNotPublic(context, softly) + } softly.assertAll() } catch (e: CancellationException) { diff --git a/platform/build-scripts/testFramework/src/com/intellij/platform/buildScripts/testFramework/pluginModel/PluginDependenciesValidator.kt b/platform/build-scripts/testFramework/src/com/intellij/platform/buildScripts/testFramework/pluginModel/PluginDependenciesValidator.kt index 5399d64c43dc..1083b5d8a3a8 100644 --- a/platform/build-scripts/testFramework/src/com/intellij/platform/buildScripts/testFramework/pluginModel/PluginDependenciesValidator.kt +++ b/platform/build-scripts/testFramework/src/com/intellij/platform/buildScripts/testFramework/pluginModel/PluginDependenciesValidator.kt @@ -375,8 +375,7 @@ class PluginDependenciesValidator private constructor( private val customConfigFileToModule: Map, embeddedContentModules: List, private val xIncludeLoader: PluginMainModuleFromSourceXIncludeLoader - ) : PathResolver { - + ) : PathResolver, XIncludeLoader by xIncludeLoader { private val embeddedContentModules = embeddedContentModules.toSet() override fun loadXIncludeReference(dataLoader: DataLoader, path: String): LoadedXIncludeReference? { diff --git a/platform/core-impl/src/com/intellij/ide/plugins/ClassPathXmlPathResolver.kt b/platform/core-impl/src/com/intellij/ide/plugins/ClassPathXmlPathResolver.kt index afc01d66810a..a801169b639e 100644 --- a/platform/core-impl/src/com/intellij/ide/plugins/ClassPathXmlPathResolver.kt +++ b/platform/core-impl/src/com/intellij/ide/plugins/ClassPathXmlPathResolver.kt @@ -14,19 +14,25 @@ class ClassPathXmlPathResolver( private val classLoader: ClassLoader, @JvmField val isRunningFromSourcesWithoutDevBuild: Boolean, private val isOptionalProductModule: (moduleId: String) -> Boolean, -) : PathResolver { +) : PathResolver, XIncludeLoader { override val isFlat: Boolean get() = true + override fun loadXIncludeReference(path: String): LoadedXIncludeReference? { + return LoadedXIncludeReference(inputStream = doLoadXIncludeReference(path) ?: return null, diagnosticReferenceLocation = toString()) + } + override fun loadXIncludeReference(dataLoader: DataLoader, path: String): LoadedXIncludeReference? { - val input: ByteArray? + return LoadedXIncludeReference(inputStream = doLoadXIncludeReference(path) ?: return null, diagnosticReferenceLocation = dataLoader.toString()) + } + + private fun doLoadXIncludeReference(path: String): ByteArray? { if (classLoader is UrlClassLoader) { - input = classLoader.getResourceAsBytes(path, true) + return classLoader.getResourceAsBytes(path, true) } else { - input = classLoader.getResourceAsStream(path)?.use { it.readBytes() } + return classLoader.getResourceAsStream(path)?.use { it.readBytes() } } - return LoadedXIncludeReference(input ?: return null, dataLoader.toString()) } override fun resolveModuleFile(readContext: PluginDescriptorReaderContext, dataLoader: DataLoader, path: String): PluginDescriptorBuilder { @@ -36,7 +42,7 @@ class ClassPathXmlPathResolver( } else { classLoader.getResourceAsStream(path)?.let { - val reader = PluginDescriptorFromXmlStreamConsumer(readContext, toXIncludeLoader(dataLoader)) + val reader = PluginDescriptorFromXmlStreamConsumer(readContext, createXIncludeLoader(this@ClassPathXmlPathResolver, dataLoader)) reader.consume(it, dataLoader.toString()) return reader.getBuilder() } @@ -72,7 +78,7 @@ class ClassPathXmlPathResolver( } } - val consumer = PluginDescriptorFromXmlStreamConsumer(readContext, toXIncludeLoader(dataLoader)) + val consumer = PluginDescriptorFromXmlStreamConsumer(readContext, createXIncludeLoader(this@ClassPathXmlPathResolver, dataLoader)) consumer.consume(resource, dataLoader.toString()) return consumer.getBuilder() } @@ -80,7 +86,7 @@ class ClassPathXmlPathResolver( override fun resolvePath(readContext: PluginDescriptorReaderContext, dataLoader: DataLoader, relativePath: String): PluginDescriptorBuilder? { val path = LoadPathUtil.toLoadPath(relativePath) val reader = getXmlReader(classLoader = classLoader, path = path, dataLoader = dataLoader) ?: return null - return PluginDescriptorFromXmlStreamConsumer(readContext, toXIncludeLoader(dataLoader)).let { + return PluginDescriptorFromXmlStreamConsumer(readContext, createXIncludeLoader(this@ClassPathXmlPathResolver, dataLoader)).let { it.consume(reader) it.getBuilder() } @@ -94,4 +100,11 @@ class ClassPathXmlPathResolver( return createNonCoalescingXmlStreamReader(classLoader.getResourceAsStream(path) ?: return null, dataLoader.toString()) } } + + override fun toString(): String { + return "ClassPathXmlPathResolver(" + + "classLoader=${classLoader.javaClass.simpleName}(files=${(classLoader as? UrlClassLoader)?.files}), " + + "isRunningFromSourcesWithoutDevBuild=$isRunningFromSourcesWithoutDevBuild" + + ")" + } } \ No newline at end of file diff --git a/platform/core-impl/src/com/intellij/ide/plugins/PathResolver.kt b/platform/core-impl/src/com/intellij/ide/plugins/PathResolver.kt index 8e71e2a71093..06f3e1cc4543 100644 --- a/platform/core-impl/src/com/intellij/ide/plugins/PathResolver.kt +++ b/platform/core-impl/src/com/intellij/ide/plugins/PathResolver.kt @@ -5,10 +5,10 @@ import com.intellij.platform.plugins.parser.impl.LoadedXIncludeReference import com.intellij.platform.plugins.parser.impl.PluginDescriptorBuilder import com.intellij.platform.plugins.parser.impl.PluginDescriptorReaderContext import com.intellij.platform.plugins.parser.impl.XIncludeLoader -import org.jetbrains.annotations.ApiStatus +import org.jetbrains.annotations.ApiStatus.Internal import java.nio.file.Path -@ApiStatus.Internal +@Internal interface PathResolver { val isFlat: Boolean get() = false @@ -32,10 +32,10 @@ interface PathResolver { } } -@ApiStatus.Internal -fun PathResolver.toXIncludeLoader(dataLoader: DataLoader): XIncludeLoader = object : XIncludeLoader { +@Internal +fun createXIncludeLoader(pathResolver: PathResolver, dataLoader: DataLoader): XIncludeLoader = object : XIncludeLoader { override fun loadXIncludeReference(path: String): LoadedXIncludeReference? { - return loadXIncludeReference(dataLoader, path) + return pathResolver.loadXIncludeReference(dataLoader, path) } override fun toString(): String = dataLoader.toString() diff --git a/platform/core-impl/src/com/intellij/ide/plugins/PluginDescriptorLoader.kt b/platform/core-impl/src/com/intellij/ide/plugins/PluginDescriptorLoader.kt index c032162ed900..e2d35199c5e9 100644 --- a/platform/core-impl/src/com/intellij/ide/plugins/PluginDescriptorLoader.kt +++ b/platform/core-impl/src/com/intellij/ide/plugins/PluginDescriptorLoader.kt @@ -12,10 +12,7 @@ import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.extensions.PluginId import com.intellij.openapi.util.BuildNumber import com.intellij.openapi.util.io.NioFiles -import com.intellij.platform.plugins.parser.impl.PluginDescriptorBuilder -import com.intellij.platform.plugins.parser.impl.PluginDescriptorFromXmlStreamConsumer -import com.intellij.platform.plugins.parser.impl.consume -import com.intellij.platform.plugins.parser.impl.readBasicDescriptorData +import com.intellij.platform.plugins.parser.impl.* import com.intellij.platform.util.putMoreLikelyPluginJarsFirst import com.intellij.util.PlatformUtils import com.intellij.util.concurrency.annotations.RequiresBackgroundThread @@ -174,7 +171,7 @@ private fun loadDescriptorFromStream( pool: ZipEntryResolverPool, id: PluginId? = null, ): PluginMainDescriptor { - val raw = PluginDescriptorFromXmlStreamConsumer(loadingContext.readContext, pathResolver.toXIncludeLoader(dataLoader)).let { + val raw = PluginDescriptorFromXmlStreamConsumer(loadingContext.readContext, createXIncludeLoader(pathResolver, dataLoader)).let { it.consume(input, fileOrDir.toString()) loadingContext.patchPlugin(it.getBuilder()) if (id != null) { @@ -604,12 +601,14 @@ internal fun CoroutineScope.loadPluginDescriptorsForPathBasedLoader( val descriptorSize = input.readInt() val descriptorStart = bundledPluginClasspathBytes.size - byteInput.available() input.skipBytes(descriptorSize) - // Gateway will be removed soon val core = async { loadCoreProductPlugin( loadingContext = loadingContext, pathResolver = ClassPathXmlPathResolver(classLoader = mainClassLoader, isRunningFromSourcesWithoutDevBuild = false, isOptionalProductModule = { false }), useCoreClassLoader = platformPrefix.startsWith("CodeServer") || forceUseCoreClassloader(), + // GatewayStarter.kt adds JARs from the main IDE to the classpath and runs it with platformPrefix=Gateway. + // So, there are two plugin.xml files in the product classpath (IDEA's one and Gateway's one - our cache contains product's ones). + // (Gateway will be removed soon). reader = if (PlatformUtils.isGateway()) { getResourceReader(path = PluginManagerCore.PLUGIN_XML_PATH, classLoader = mainClassLoader)!! } @@ -718,7 +717,7 @@ private fun loadPluginDescriptor( val item = fileItems.first() val pluginPathResolver = PluginXmlPathResolver.DEFAULT_PATH_RESOLVER val descriptorInput = createNonCoalescingXmlStreamReader(input = pluginDescriptorData, locationSource = item.path) - val raw = PluginDescriptorFromXmlStreamConsumer(loadingContext.readContext, pluginPathResolver.toXIncludeLoader(dataLoader)).let { + val raw = PluginDescriptorFromXmlStreamConsumer(loadingContext.readContext, createXIncludeLoader(pluginPathResolver, dataLoader)).let { it.consume(descriptorInput) loadingContext.patchPlugin(it.getBuilder()) it.build() @@ -735,14 +734,14 @@ private fun loadPluginDescriptor( loadModuleFromSeparateJar(pool = zipPool, jarFile = jarFile, subDescriptorFile = subDescriptorFile, loadingContext = loadingContext) } else { - PluginDescriptorFromXmlStreamConsumer(loadingContext.readContext, pluginPathResolver.toXIncludeLoader(dataLoader)).let { + PluginDescriptorFromXmlStreamConsumer(loadingContext.readContext, createXIncludeLoader(pluginPathResolver, dataLoader)).let { it.consume(input, null) it.getBuilder() } } } else { - val subRaw = PluginDescriptorFromXmlStreamConsumer(loadingContext.readContext, pluginPathResolver.toXIncludeLoader(dataLoader)).let { + val subRaw = PluginDescriptorFromXmlStreamConsumer(loadingContext.readContext, createXIncludeLoader(pluginPathResolver, dataLoader)).let { try{ it.consume(createXmlStreamReader(module.descriptorContent)) } @@ -874,7 +873,8 @@ internal fun loadCoreProductPlugin( override fun toString() = "product classpath (platformPrefix=${PlatformUtils.getPlatformPrefix()})" } - val consumer = PluginDescriptorFromXmlStreamConsumer(loadingContext.readContext, pathResolver.toXIncludeLoader(dataLoader)) + val xIncludeLoader = pathResolver as? XIncludeLoader ?: createXIncludeLoader(pathResolver, dataLoader) + val consumer = PluginDescriptorFromXmlStreamConsumer(loadingContext.readContext, xIncludeLoader) consumer.consume(reader) loadingContext.patchPlugin(consumer.getBuilder()) val raw = consumer.build() @@ -887,6 +887,7 @@ internal fun loadCoreProductPlugin( jarFileForModule = jarFileForModule, loadingContext = loadingContext, dataLoader = dataLoader, + xIncludeLoader = xIncludeLoader, isRunningFromSourcesWithoutDevBuild = isRunningFromSourcesWithoutDevBuild, ) loadPluginDependencyDescriptors(descriptor = descriptor, loadingContext = loadingContext, pathResolver = pathResolver, dataLoader = dataLoader) @@ -900,6 +901,7 @@ private fun loadContentModuleDescriptors( jarFileForModule: (moduleId: PluginModuleId, moduleDir: Path) -> Path?, loadingContext: PluginDescriptorLoadingContext, dataLoader: DataLoader, + xIncludeLoader: XIncludeLoader, isRunningFromSourcesWithoutDevBuild: Boolean, ) { val moduleDirExists = Files.isDirectory(moduleDir) @@ -919,8 +921,7 @@ private fun loadContentModuleDescriptors( module = module, subDescriptorFile = subDescriptorFile, loadingContext = loadingContext, - pathResolver = pathResolver, - dataLoader = dataLoader, + xIncludeLoader = xIncludeLoader, containerDescriptor = descriptor, )) { continue @@ -941,8 +942,7 @@ private fun loadProductModule( module: PluginContentDescriptor.ModuleItem, subDescriptorFile: String, loadingContext: PluginDescriptorLoadingContext, - pathResolver: PathResolver, - dataLoader: DataLoader, + xIncludeLoader: XIncludeLoader, containerDescriptor: PluginMainDescriptor, ): Boolean { val moduleId = module.moduleId @@ -957,7 +957,7 @@ private fun loadProductModule( "Product module ${module.moduleId} descriptor content is not embedded - corrupted distribution " + "(jarFile=$jarFile, containerDescriptor=$containerDescriptor, siblings=${containerDescriptor.content.modules.joinToString()})" }) - PluginDescriptorFromXmlStreamConsumer(loadingContext.readContext, pathResolver.toXIncludeLoader(dataLoader)).let { + PluginDescriptorFromXmlStreamConsumer(readContext = loadingContext.readContext, xIncludeLoader = xIncludeLoader).let { it.consume(reader) it.getBuilder() } @@ -1206,7 +1206,7 @@ internal fun testOrDeprecatedLoadDescriptorFromResource( else -> return null } - val raw = PluginDescriptorFromXmlStreamConsumer(loadingContext.readContext, pathResolver.toXIncludeLoader(dataLoader)).let { + val raw = PluginDescriptorFromXmlStreamConsumer(loadingContext.readContext, createXIncludeLoader(pathResolver, dataLoader)).let { it.consume(input, file.toString()) loadingContext.patchPlugin(it.getBuilder()) it.build() @@ -1236,6 +1236,7 @@ internal fun testOrDeprecatedLoadDescriptorFromResource( jarFileForModule = { moduleId, moduleDir -> ProductLoadingStrategy.strategy.findProductContentModuleClassesRoot(moduleId, moduleDir) }, loadingContext = loadingContext, dataLoader = dataLoader, + xIncludeLoader = createXIncludeLoader(pathResolver, dataLoader), isRunningFromSourcesWithoutDevBuild = pathResolver.isRunningFromSourcesWithoutDevBuild, ) } diff --git a/platform/core-impl/src/com/intellij/ide/plugins/PluginXmlPathResolver.kt b/platform/core-impl/src/com/intellij/ide/plugins/PluginXmlPathResolver.kt index 084a9ad00329..0c8a32a71840 100644 --- a/platform/core-impl/src/com/intellij/ide/plugins/PluginXmlPathResolver.kt +++ b/platform/core-impl/src/com/intellij/ide/plugins/PluginXmlPathResolver.kt @@ -48,7 +48,7 @@ class PluginXmlPathResolver(private val pluginJarFiles: List, private val override fun resolvePath(readContext: PluginDescriptorReaderContext, dataLoader: DataLoader, relativePath: String): PluginDescriptorBuilder? { val path = LoadPathUtil.toLoadPath(relativePath) dataLoader.load(path, pluginDescriptorSourceOnly = false)?.let { input -> - return PluginDescriptorFromXmlStreamConsumer(readContext, toXIncludeLoader(dataLoader)).let { + return PluginDescriptorFromXmlStreamConsumer(readContext, createXIncludeLoader(this@PluginXmlPathResolver, dataLoader)).let { it.consume(input, null) it.getBuilder() } @@ -58,7 +58,7 @@ class PluginXmlPathResolver(private val pluginJarFiles: List, private val val fromJar = findInJarFiles(dataLoader = dataLoader, relativePath = path, pool = pool) if (fromJar != null) { return fromJar.inputStream.let { input -> - PluginDescriptorFromXmlStreamConsumer(readContext, toXIncludeLoader(dataLoader)).let { + PluginDescriptorFromXmlStreamConsumer(readContext, createXIncludeLoader(this@PluginXmlPathResolver, dataLoader)).let { it.consume(input, null) it.getBuilder() } @@ -88,7 +88,7 @@ class PluginXmlPathResolver(private val pluginJarFiles: List, private val throw RuntimeException("Cannot resolve $path (dataLoader=$dataLoader, pluginJarFiles=${pluginJarFiles.joinToString(separator = "\n ")})") } - val builder = PluginDescriptorFromXmlStreamConsumer(readContext, toXIncludeLoader(dataLoader)).let { + val builder = PluginDescriptorFromXmlStreamConsumer(readContext, createXIncludeLoader(this@PluginXmlPathResolver, dataLoader)).let { it.consume(input, null) it.getBuilder() } diff --git a/platform/platform-resources/generated/META-INF/intellij.moduleSets.essential.xml b/platform/platform-resources/generated/META-INF/intellij.moduleSets.essential.xml index a2541af6cefb..687970c77316 100644 --- a/platform/platform-resources/generated/META-INF/intellij.moduleSets.essential.xml +++ b/platform/platform-resources/generated/META-INF/intellij.moduleSets.essential.xml @@ -2,10 +2,103 @@ - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/platform/platform-resources/generated/META-INF/intellij.moduleSets.ide.common.xml b/platform/platform-resources/generated/META-INF/intellij.moduleSets.ide.common.xml index 7a877fc4bbfb..c0ce012dc8ba 100644 --- a/platform/platform-resources/generated/META-INF/intellij.moduleSets.ide.common.xml +++ b/platform/platform-resources/generated/META-INF/intellij.moduleSets.ide.common.xml @@ -2,15 +2,235 @@ - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/platform/platform-resources/generated/META-INF/intellij.moduleSets.libraries.xml b/platform/platform-resources/generated/META-INF/intellij.moduleSets.libraries.xml index 38f51a59e806..9bdc2615f21b 100644 --- a/platform/platform-resources/generated/META-INF/intellij.moduleSets.libraries.xml +++ b/platform/platform-resources/generated/META-INF/intellij.moduleSets.libraries.xml @@ -2,9 +2,99 @@ - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/platform/platform-resources/generated/META-INF/intellij.moduleSets.vcs.xml b/platform/platform-resources/generated/META-INF/intellij.moduleSets.vcs.xml index 89fda721ff07..34da0f3cd044 100644 --- a/platform/platform-resources/generated/META-INF/intellij.moduleSets.vcs.xml +++ b/platform/platform-resources/generated/META-INF/intellij.moduleSets.vcs.xml @@ -2,11 +2,18 @@ - - - - + + + + + + + + + + + diff --git a/platform/platform-tests/testSrc/com/intellij/ide/plugins/PluginDescriptorLoadUtils.kt b/platform/platform-tests/testSrc/com/intellij/ide/plugins/PluginDescriptorLoadUtils.kt index f390fd3e62ec..673f91bffa2b 100644 --- a/platform/platform-tests/testSrc/com/intellij/ide/plugins/PluginDescriptorLoadUtils.kt +++ b/platform/platform-tests/testSrc/com/intellij/ide/plugins/PluginDescriptorLoadUtils.kt @@ -20,7 +20,7 @@ fun readDescriptorFromBytesForTest(path: Path, isBundled: Boolean, input: ByteAr val rawBuilder = PluginDescriptorFromXmlStreamConsumer(object : PluginDescriptorReaderContext { override val interner = NoOpXmlInterner override val isMissingIncludeIgnored = false - }, pathResolver.toXIncludeLoader(dataLoader)).let { + }, createXIncludeLoader(pathResolver, dataLoader)).let { it.consume(input, path.toString()) it.getBuilder() } @@ -49,7 +49,7 @@ fun readDescriptorFromBytesForTest( pathResolver: PathResolver, dataLoader: DataLoader, ): PluginMainDescriptor { - val raw = PluginDescriptorFromXmlStreamConsumer(loadingContext.readContext, pathResolver.toXIncludeLoader(dataLoader)).let { + val raw = PluginDescriptorFromXmlStreamConsumer(loadingContext.readContext, createXIncludeLoader(pathResolver, dataLoader)).let { it.consume(data, path.toString()) loadingContext.patchPlugin(it.getBuilder()) it.build() diff --git a/platform/platform-tests/testSrc/com/intellij/ide/plugins/PluginManagerTest.kt b/platform/platform-tests/testSrc/com/intellij/ide/plugins/PluginManagerTest.kt index 3aa715e6d7bd..df9869a164a9 100644 --- a/platform/platform-tests/testSrc/com/intellij/ide/plugins/PluginManagerTest.kt +++ b/platform/platform-tests/testSrc/com/intellij/ide/plugins/PluginManagerTest.kt @@ -428,7 +428,7 @@ class PluginManagerTest { val url = child.getAttributeValue("descriptor-url")!! if (url.endsWith("/$relativePath")) { try { - val reader = PluginDescriptorFromXmlStreamConsumer(readContext, this.toXIncludeLoader(dataLoader)) + val reader = PluginDescriptorFromXmlStreamConsumer(readContext, createXIncludeLoader(this, dataLoader)) reader.consume(elementAsBytes(child), null) return reader.getBuilder() } @@ -492,7 +492,7 @@ private fun readModuleDescriptorForTest(input: ByteArray): PluginDescriptorBuild return PluginDescriptorFromXmlStreamConsumer(readContext = object : PluginDescriptorReaderContext { override val interner = NoOpXmlInterner override val isMissingIncludeIgnored = false - }, xIncludeLoader = PluginXmlPathResolver.DEFAULT_PATH_RESOLVER.toXIncludeLoader(object : DataLoader { + }, xIncludeLoader = createXIncludeLoader(PluginXmlPathResolver.DEFAULT_PATH_RESOLVER, object : DataLoader { override fun load(path: String, pluginDescriptorSourceOnly: Boolean) = throw UnsupportedOperationException() override fun toString() = "" })).let { diff --git a/platform/plugins/parser/impl/src/com/intellij/platform/plugins/parser/impl/XmlReader.kt b/platform/plugins/parser/impl/src/com/intellij/platform/plugins/parser/impl/XmlReader.kt index 897563dadfca..bde1a7905227 100644 --- a/platform/plugins/parser/impl/src/com/intellij/platform/plugins/parser/impl/XmlReader.kt +++ b/platform/plugins/parser/impl/src/com/intellij/platform/plugins/parser/impl/XmlReader.kt @@ -812,14 +812,15 @@ private fun readInclude( } var readError: IOException? = null + val targetPath = LoadPathUtil.toLoadPath(relativePath = path, baseDir = consumer.includeBase) val loadedXInclude = try { - val targetPath = LoadPathUtil.toLoadPath(relativePath = path, baseDir = consumer.includeBase) xIncludeLoader.loadXIncludeReference(path = targetPath) } catch (e: IOException) { readError = e null } + if (loadedXInclude != null) { consumer.pushIncludeBase(LoadPathUtil.getChildBaseDir(base = consumer.includeBase, relativePath = path)) try { @@ -840,7 +841,7 @@ private fun readInclude( return } else { - throw RuntimeException("Cannot resolve $path (loader=${consumer.xIncludeLoader})", readError) + throw RuntimeException("Cannot resolve $path (targetPath=$targetPath, loader=${consumer.xIncludeLoader})", readError) } } diff --git a/python/build/src/org/jetbrains/intellij/build/pycharm/PyCharmCommunityProperties.kt b/python/build/src/org/jetbrains/intellij/build/pycharm/PyCharmCommunityProperties.kt index 1f5a17e1a0c3..9e28417a3ae7 100644 --- a/python/build/src/org/jetbrains/intellij/build/pycharm/PyCharmCommunityProperties.kt +++ b/python/build/src/org/jetbrains/intellij/build/pycharm/PyCharmCommunityProperties.kt @@ -7,6 +7,8 @@ import kotlinx.collections.immutable.plus import org.jetbrains.intellij.build.* import org.jetbrains.intellij.build.impl.qodana.QodanaProductProperties import org.jetbrains.intellij.build.io.copyFileToDir +import org.jetbrains.intellij.build.productLayout.CommunityModuleSets +import org.jetbrains.intellij.build.productLayout.ModuleSetProvider import java.nio.file.Files import java.nio.file.Path @@ -46,6 +48,9 @@ open class PyCharmCommunityProperties(protected val communityHome: Path) : PyCha qodanaProductProperties = QodanaProductProperties(@Suppress("SpellCheckingInspection") "QDPYC", "Qodana Community for Python") } + override val moduleSetsProviders: List + get() = listOf(CommunityModuleSets) + override suspend fun copyAdditionalFiles(context: BuildContext, targetDir: Path) { super.copyAdditionalFiles(context, targetDir)