diff --git a/build/src/org/jetbrains/intellij/build/IdeaCommunityProperties.kt b/build/src/org/jetbrains/intellij/build/IdeaCommunityProperties.kt index 0a7c9cae9205..91b38d327845 100644 --- a/build/src/org/jetbrains/intellij/build/IdeaCommunityProperties.kt +++ b/build/src/org/jetbrains/intellij/build/IdeaCommunityProperties.kt @@ -65,10 +65,6 @@ open class IdeaCommunityProperties(private val communityHomeDir: Path) : JetBrai CommunityRepositoryModules.groovyPlugin(), ) - productLayout.addPlatformSpec { layout, _ -> - layout.withModule("intellij.platform.structuralSearch") - } - productLayout.skipUnresolvedContentModules = true mavenArtifacts.forIdeModules = true diff --git a/java/ide-resources/resources/META-INF/JavaIdePlugin.xml b/java/ide-resources/resources/META-INF/JavaIdePlugin.xml index f6ceffe1f23b..b6f5bac5c183 100644 --- a/java/ide-resources/resources/META-INF/JavaIdePlugin.xml +++ b/java/ide-resources/resources/META-INF/JavaIdePlugin.xml @@ -25,9 +25,7 @@ - - - + >() val aliasToSource = if (collectModuleSetAliases) mutableMapOf() else null val processedSets = HashSet() - val contentBlockByName = mutableMapOf() + val contentBlockByName = HashMap() fun traverse(moduleSet: ModuleSet, chain: List, overrides: Map) { val setName = "$MODULE_SET_PREFIX${moduleSet.name}" @@ -30,7 +30,7 @@ internal fun buildContentBlocksAndChainMapping( // Check if already processed val alreadyProcessed = !processedSets.add(setName) if (alreadyProcessed) { - // If already processed but now we have overrides, update the existing content block + // If already processed, but now we have overrides, update the existing content block if (overrides.isNotEmpty()) { val existingBlock = contentBlockByName[moduleSet.name] if (existingBlock != null) { @@ -69,13 +69,9 @@ internal fun buildContentBlocksAndChainMapping( } val currentChain = chain + setName - - // Get direct modules for this set - val directModules = getDirectModules(moduleSet, spec.excludedModules) - // Build content block and track chains/duplicates in single pass val modulesWithLoading = mutableListOf() - for (module in directModules) { + for (module in moduleSet.modules) { // Track for duplicate detection moduleToSets.computeIfAbsent(module.name) { mutableListOf() }.add(moduleSet.name) // Track chain @@ -104,7 +100,7 @@ internal fun buildContentBlocksAndChainMapping( // Validate that all overridden modules exist as direct modules in their respective module sets for (moduleSetWithOverrides in spec.moduleSets) { - validateModuleSetOverrides(moduleSetWithOverrides, spec) + validateModuleSetOverrides(moduleSetWithOverrides) } // Check for duplicates and FAIL if found @@ -113,9 +109,7 @@ internal fun buildContentBlocksAndChainMapping( // Add additional modules if any val additionalModulesWithLoading = mutableListOf() for (module in spec.additionalModules) { - if (module.name !in spec.excludedModules) { - additionalModulesWithLoading.add(ModuleWithLoading(module.name, module.loading)) - } + additionalModulesWithLoading.add(ModuleWithLoading(module.name, module.loading)) } if (additionalModulesWithLoading.isNotEmpty()) { @@ -137,7 +131,7 @@ internal fun collectAndValidateAliases( spec: ProductModulesContentSpec, moduleSetAliases: Map ): List { - val allAliases = moduleSetAliases.toMutableMap() + val allAliases = HashMap(moduleSetAliases) // Collect product-level aliases and check for conflicts with module set aliases for (alias in spec.productModuleAliases) { diff --git a/platform/build-scripts/product-dsl/src/ModuleSetBuilder.kt b/platform/build-scripts/product-dsl/src/ModuleSetBuilder.kt index 5d811565ccae..846826d1b188 100644 --- a/platform/build-scripts/product-dsl/src/ModuleSetBuilder.kt +++ b/platform/build-scripts/product-dsl/src/ModuleSetBuilder.kt @@ -70,15 +70,14 @@ class ModuleSetBuilder { } /** - * Include all modules from another ModuleSet. + * Include another ModuleSet. */ fun moduleSet(set: ModuleSet) { - modules.addAll(set.modules) nestedSets.add(set) } @PublishedApi - internal fun build(): Pair, List> = Pair(modules, nestedSets) + internal fun build(): Pair, List> = Pair(java.util.List.copyOf(modules), java.util.List.copyOf(nestedSets)) } /** @@ -124,7 +123,7 @@ private fun appendModuleXml(sb: StringBuilder, module: ContentModule) { */ private fun appendModuleSetContent(sb: StringBuilder, moduleSet: ModuleSet, indent: String = " ", breadcrumb: String = "") { // Get direct modules (not from nested sets) - val directModules = getDirectModules(moduleSet) + val directModules = moduleSet.modules // Recursively append nested sets first for (nestedSet in moduleSet.nestedSets) { @@ -167,9 +166,6 @@ private fun appendModuleSetContent(sb: StringBuilder, moduleSet: ModuleSet, inde * @return ModuleSetBuildResult containing XML and direct module count */ internal fun buildModuleSetXml(moduleSet: ModuleSet, label: String): ModuleSetBuildResult { - // Count direct modules (excluding nested) - do this once upfront - val directModuleCount = getDirectModules(moduleSet).size - val xml = buildString { // Add generated file header val mainClass = if (label == "community") "CommunityModuleSets" else "UltimateModuleSets" @@ -203,7 +199,7 @@ internal fun buildModuleSetXml(moduleSet: ModuleSet, label: String): ModuleSetBu append("") } - return ModuleSetBuildResult(xml, directModuleCount) + return ModuleSetBuildResult(xml, moduleSet.modules.size) } /** diff --git a/platform/build-scripts/product-dsl/src/ModuleSetJsonExport.kt b/platform/build-scripts/product-dsl/src/ModuleSetJsonExport.kt index 8164193456f8..a248c6f60121 100644 --- a/platform/build-scripts/product-dsl/src/ModuleSetJsonExport.kt +++ b/platform/build-scripts/product-dsl/src/ModuleSetJsonExport.kt @@ -180,11 +180,6 @@ private fun enrichProductsWithMetrics( allModules.add(module.name) } - // Remove excluded modules - for (excludedModule in contentSpec.excludedModules) { - allModules.remove(excludedModule) - } - val totalModuleCount = allModules.size val directModuleCount = contentSpec.additionalModules.size val moduleSetCount = contentSpec.moduleSets.size @@ -518,7 +513,7 @@ private fun writeDuplicateAnalysis( for ((moduleSet, _, _) in allModuleSets) { val allModules = collectAllModuleNames(moduleSet) for (moduleName in allModules) { - moduleToSets.getOrPut(moduleName) { mutableListOf() }.add(moduleSet.name) + moduleToSets.computeIfAbsent(moduleName) { mutableListOf() }.add(moduleSet.name) } } @@ -674,10 +669,10 @@ private fun writeModuleDistribution( // Build module → {inModuleSets: [], inProducts: []} mapping val moduleMap = mutableMapOf() - // Collect modules from module sets + // Collect modules from module sets (recursively including nested sets) for ((moduleSet, _, _) in allModuleSets) { - for (module in moduleSet.modules) { - val info = moduleMap.getOrPut(module.name) { ModuleDistributionInfo() } + visitAllModules(moduleSet) { module -> + val info = moduleMap.computeIfAbsent(module.name) { ModuleDistributionInfo() } info.inModuleSets.add(moduleSet.name) } } @@ -702,7 +697,7 @@ private fun writeModuleDistribution( // Add to module map for (moduleName in allModulesInProduct) { - val info = moduleMap.getOrPut(moduleName) { ModuleDistributionInfo() } + val info = moduleMap.computeIfAbsent(moduleName) { ModuleDistributionInfo() } if (product.name !in info.inProducts) { info.inProducts.add(product.name) } @@ -827,10 +822,10 @@ private fun writeModuleUsageIndex( // Build comprehensive usage index val usageIndex = mutableMapOf() - // Collect from module sets + // Collect from module sets (recursively including nested sets) for ((moduleSet, location, sourceFile) in allModuleSets) { - for (module in moduleSet.modules) { - val info = usageIndex.getOrPut(module.name) { ModuleUsageInfo() } + visitAllModules(moduleSet) { module -> + val info = usageIndex.computeIfAbsent(module.name) { ModuleUsageInfo() } info.moduleSets.add(ModuleSetReference(moduleSet.name, location, sourceFile)) } } @@ -855,7 +850,7 @@ private fun writeModuleUsageIndex( // Add to index for (moduleName in allModulesInProduct) { - val info = usageIndex.getOrPut(moduleName) { ModuleUsageInfo() } + val info = usageIndex.computeIfAbsent(moduleName) { ModuleUsageInfo() } info.products.add(ProductReference(product.name, product.sourceFile)) } } @@ -1402,7 +1397,9 @@ private fun suggestModuleSetUnification( p.contentSpec?.moduleSets?.any { it.moduleSet.name == msEntry.moduleSet.name } == true } - if (usedByProducts.size <= 1 && msEntry.moduleSet.modules.size <= 5) { + // Use total module count (including nested sets) for inline candidate detection + val totalModuleCount = collectAllModuleNames(msEntry.moduleSet).size + if (usedByProducts.size <= 1 && totalModuleCount <= 5) { suggestions.add(UnificationSuggestion( priority = "low", strategy = "inline", @@ -1412,10 +1409,10 @@ private fun suggestModuleSetUnification( moduleSet2 = null, products = null, sharedModuleSets = null, - reason = "Used by only ${usedByProducts.size} product(s) and contains only ${msEntry.moduleSet.modules.size} modules. Consider inlining into the product directly.", + reason = "Used by only ${usedByProducts.size} product(s) and contains only $totalModuleCount modules. Consider inlining into the product directly.", impact = mapOf( "moduleSetsSaved" to 1, - "moduleCount" to msEntry.moduleSet.modules.size, + "moduleCount" to totalModuleCount, "affectedProducts" to usedByProducts.map { it.name } ) )) @@ -1449,7 +1446,9 @@ private fun suggestModuleSetUnification( // Strategy 4: Split large module sets if (strategy == "split" || strategy == "all") { for (msEntry in allModuleSets) { - if (msEntry.moduleSet.modules.size > 200) { + // Use total module count (including nested sets) for split suggestions + val totalModuleCount = collectAllModuleNames(msEntry.moduleSet).size + if (totalModuleCount > 200) { suggestions.add(UnificationSuggestion( priority = "low", strategy = "split", @@ -1459,8 +1458,8 @@ private fun suggestModuleSetUnification( moduleSet2 = null, products = null, sharedModuleSets = null, - reason = "Module set contains ${msEntry.moduleSet.modules.size} modules. Consider splitting into smaller, more focused sets for better maintainability.", - impact = mapOf("moduleCount" to msEntry.moduleSet.modules.size) + reason = "Module set contains $totalModuleCount modules. Consider splitting into smaller, more focused sets for better maintainability.", + impact = mapOf("moduleCount" to totalModuleCount) )) } } diff --git a/platform/build-scripts/product-dsl/src/ProductModulesContentSpec.kt b/platform/build-scripts/product-dsl/src/ProductModulesContentSpec.kt index b54818aa4ad1..a20b56df339f 100644 --- a/platform/build-scripts/product-dsl/src/ProductModulesContentSpec.kt +++ b/platform/build-scripts/product-dsl/src/ProductModulesContentSpec.kt @@ -115,11 +115,6 @@ class ProductModulesContentSpec( */ @JvmField val additionalModules: List, - /** - * Module names to exclude from the final set (after resolving all module sets and additions). - * This is useful to remove specific modules from included module sets. - */ - @JvmField val excludedModules: Set, /** * Composition graph tracking how this spec was assembled. @@ -198,7 +193,6 @@ class ProductModulesContentSpecBuilder @PublishedApi internal constructor() { xmlIncludes.addAll(spec.deprecatedXmlIncludes) moduleSets.addAll(spec.moduleSets) additionalModules.addAll(spec.additionalModules) - excludedModules.addAll(spec.excludedModules) // Also preserve the nested spec's composition graph for deep analysis compositionGraph.addAll(spec.compositionGraph) @@ -323,7 +317,6 @@ class ProductModulesContentSpecBuilder @PublishedApi internal constructor() { deprecatedXmlIncludes = java.util.List.copyOf(xmlIncludes), moduleSets = java.util.List.copyOf(moduleSets), additionalModules = java.util.List.copyOf(additionalModules), - excludedModules = java.util.Set.copyOf(excludedModules), compositionGraph = java.util.List.copyOf(compositionGraph), metadata = metadata, ) diff --git a/platform/build-scripts/product-dsl/src/ValidationUtils.kt b/platform/build-scripts/product-dsl/src/ValidationUtils.kt index d5838527680c..87e3cce13621 100644 --- a/platform/build-scripts/product-dsl/src/ValidationUtils.kt +++ b/platform/build-scripts/product-dsl/src/ValidationUtils.kt @@ -30,15 +30,13 @@ internal fun formatValidationError( * Throws an error if invalid overrides are found. * * @param moduleSetWithOverrides The module set with overrides to validate - * @param spec The product modules specification (for excluded modules) */ internal fun validateModuleSetOverrides( - moduleSetWithOverrides: ModuleSetWithOverrides, - spec: ProductModulesContentSpec + moduleSetWithOverrides: ModuleSetWithOverrides ) { if (moduleSetWithOverrides.loadingOverrides.isEmpty()) return - val directModules = getDirectModules(moduleSetWithOverrides.moduleSet, spec.excludedModules).map { it.name }.toSet() + val directModules = moduleSetWithOverrides.moduleSet.modules.mapTo(LinkedHashSet()) { it.name } val invalidOverrides = moduleSetWithOverrides.loadingOverrides.keys.filter { it !in directModules } if (invalidOverrides.isNotEmpty()) { @@ -135,7 +133,7 @@ internal fun validateAndRecordAlias( /** * Validates that products don't reference redundant module sets. - * A module set is redundant if it's already nested inside another module set the product uses + * A module set is redundant if it's already nested inside another module set the product uses, * and the product doesn't apply any overrides to it. * * This validation ensures product specifications are correct and maintainable. diff --git a/platform/build-scripts/product-dsl/src/XmlGenerationUtils.kt b/platform/build-scripts/product-dsl/src/XmlGenerationUtils.kt index f6f55c5d1367..9b8ff1d4812b 100644 --- a/platform/build-scripts/product-dsl/src/XmlGenerationUtils.kt +++ b/platform/build-scripts/product-dsl/src/XmlGenerationUtils.kt @@ -94,39 +94,19 @@ internal fun collectAllModuleNames(moduleSet: ModuleSet, excludedModules: Set = emptySet()): List { - // Fast path: no filtering needed (very common for leaf nodes) - if (moduleSet.nestedSets.isEmpty() && excludedModules.isEmpty()) { - return moduleSet.modules - } - - // Fast path: only check exclusions (no nested sets) - if (moduleSet.nestedSets.isEmpty()) { - return moduleSet.modules.filter { it.name !in excludedModules } - } - - // Build set of module names from nested sets - val nestedModuleNames = HashSet() - for (nestedSet in moduleSet.nestedSets) { - for (module in nestedSet.modules) { - nestedModuleNames.add(module.name) - } - } - - // Full filtering: check both nested modules and exclusions - val directModules = mutableListOf() +internal fun visitAllModules(moduleSet: ModuleSet, visitor: (ContentModule) -> Unit) { for (module in moduleSet.modules) { - if (module.name !in nestedModuleNames && module.name !in excludedModules) { - directModules.add(module) - } + visitor(module) + } + for (nestedSet in moduleSet.nestedSets) { + visitAllModules(nestedSet, visitor) } - return directModules } /** diff --git a/platform/build-scripts/product-dsl/src/generator.kt b/platform/build-scripts/product-dsl/src/generator.kt index 92f5f4276388..30cca4b3af7b 100644 --- a/platform/build-scripts/product-dsl/src/generator.kt +++ b/platform/build-scripts/product-dsl/src/generator.kt @@ -117,8 +117,7 @@ fun buildProductContentXml( isUltimateBuild: Boolean, ): ProductContentBuildResult { // Build content blocks, chain mapping, and collect module set aliases in single traversal - val (contentBlocks, moduleToSetChainMapping, moduleSetAliases) = - buildContentBlocksAndChainMapping(spec, collectModuleSetAliases = inlineModuleSets) + val (contentBlocks, moduleToSetChainMapping, moduleSetAliases) = buildContentBlocksAndChainMapping(spec, collectModuleSetAliases = inlineModuleSets) val xml = buildString { appendXmlHeader(generatorCommand, productPropertiesClass) diff --git a/platform/build-scripts/src/org/jetbrains/intellij/build/ApplicationInfoProperties.kt b/platform/build-scripts/src/org/jetbrains/intellij/build/ApplicationInfoProperties.kt index 4aae287c9b68..e0b4afd9d423 100644 --- a/platform/build-scripts/src/org/jetbrains/intellij/build/ApplicationInfoProperties.kt +++ b/platform/build-scripts/src/org/jetbrains/intellij/build/ApplicationInfoProperties.kt @@ -12,7 +12,7 @@ interface ApplicationInfoProperties { val isEAP: Boolean val versionSuffix: String? /** - * The first number from 'minor' part of the version. This property is temporary added because some products specify composite number (like '1.3') + * The first number from 'minor' part of the version. This property is temporarily added because some products specify composite number (like '1.3') * in 'minor version' attribute instead of using 'micro version' (i.e. set minor='1' micro='3'). */ val minorVersionMainPart: String @@ -39,5 +39,6 @@ interface ApplicationInfoProperties { * another product, to get the instance of the product currently being build, use [BuildContext.applicationInfo] instead. */ @Internal -fun BuildContext.loadApplicationInfoPropertiesForProduct(productProperties: ProductProperties): ApplicationInfoProperties = - ApplicationInfoPropertiesImpl(project, productProperties, options) +fun loadApplicationInfoPropertiesForProduct(productProperties: ProductProperties, context: BuildContext): ApplicationInfoProperties { + return ApplicationInfoPropertiesImpl(context.project, productProperties, context.options) +} 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 da99fbbda2c0..40fb79a4941a 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 @@ -35,7 +35,6 @@ object CommunityModuleSets : ModuleSetProvider { /** * Essential platform modules required by most IDE products. - * Corresponds to intellij.moduleSets.essential.xml and includes libraries. */ fun essential(): ModuleSet = moduleSet("essential") { // Include libraries first (they are xi:included in essential.xml) @@ -130,7 +129,6 @@ object CommunityModuleSets : ModuleSetProvider { /** * All library module sets combined (meta-set that includes core, ktor, misc, temporaryBundled). - * Corresponds to intellij.moduleSets.libraries.xml */ fun libraries(): ModuleSet = moduleSet("libraries") { moduleSet(librariesCore()) @@ -141,7 +139,6 @@ object CommunityModuleSets : ModuleSetProvider { /** * Core library modules. - * Corresponds to intellij.moduleSets.libraries.core.xml */ fun librariesCore(): ModuleSet = moduleSet("libraries.core") { embeddedModule("intellij.libraries.kotlin.reflect") @@ -229,7 +226,6 @@ object CommunityModuleSets : ModuleSetProvider { /** * Ktor library modules. - * Corresponds to intellij.moduleSets.libraries.ktor.xml */ fun librariesKtor(): ModuleSet = moduleSet("libraries.ktor") { embeddedModule("intellij.libraries.ktor.io") @@ -241,7 +237,6 @@ object CommunityModuleSets : ModuleSetProvider { /** * Miscellaneous library modules. - * Corresponds to intellij.moduleSets.libraries.misc.xml */ fun librariesMisc(): ModuleSet = moduleSet("libraries.misc") { // all libs here must not be embedded, if it is embedded, it should be moved to libs-core.xml @@ -253,7 +248,6 @@ object CommunityModuleSets : ModuleSetProvider { /** * Temporarily bundled library modules (planned to be removed). - * Corresponds to intellij.moduleSets.libraries.temporaryBundled.xml */ fun librariesTemporaryBundled(): ModuleSet = moduleSet("libraries.temporaryBundled") { // Currently used only by DBE (see https://youtrack.jetbrains.com/issue/IJPL-211789/CNFE-org.codehaus.jettison.mapped.Configuration). @@ -268,7 +262,6 @@ object CommunityModuleSets : ModuleSetProvider { /** * VCS (Version Control System) modules including shared and frontend parts. - * Corresponds to intellij.moduleSets.vcs.xml */ fun vcs(): ModuleSet = moduleSet("vcs") { module("intellij.platform.vcs.impl") @@ -290,7 +283,6 @@ object CommunityModuleSets : ModuleSetProvider { /** * VCS shared modules (used by both frontend and backend). - * Corresponds to intellij.moduleSets.vcs.shared.xml */ fun vcsShared(): ModuleSet = moduleSet("vcs.shared") { embeddedModule("intellij.platform.vcs.core") @@ -301,7 +293,6 @@ object CommunityModuleSets : ModuleSetProvider { /** * VCS frontend modules. - * Corresponds to intellij.moduleSets.vcs.frontend.xml */ fun vcsFrontend(): ModuleSet = moduleSet("vcs.frontend") { module("intellij.platform.vcs.impl.frontend") @@ -309,7 +300,6 @@ object CommunityModuleSets : ModuleSetProvider { /** * XML support modules. - * Corresponds to intellij.moduleSets.xml.xml */ fun xml(): ModuleSet = moduleSet("xml", alias = "com.intellij.modules.xml") { embeddedModule("intellij.xml.dom") @@ -334,7 +324,6 @@ object CommunityModuleSets : ModuleSetProvider { /** * Duplicates analysis modules. - * Corresponds to intellij.moduleSets.duplicates.xml */ fun duplicates(): ModuleSet = moduleSet("duplicates") { embeddedModule("intellij.platform.duplicates.analysis") @@ -342,7 +331,6 @@ object CommunityModuleSets : ModuleSetProvider { /** * Stream debugger modules. - * Corresponds to intellij.moduleSets.debugger.streams.xml */ fun debuggerStreams(): ModuleSet = moduleSet("debugger.streams") { module("intellij.debugger.streams.core") @@ -352,7 +340,6 @@ object CommunityModuleSets : ModuleSetProvider { /** * Process elevation support (for operations requiring elevated privileges). - * Corresponds to intellij.moduleSets.elevation.xml */ fun elevation(): ModuleSet = moduleSet("elevation") { module("intellij.execution.process.elevation") @@ -363,7 +350,6 @@ object CommunityModuleSets : ModuleSetProvider { /** * Compose UI modules. - * Corresponds to intellij.moduleSets.compose.xml */ fun compose(): ModuleSet = moduleSet("compose") { module("intellij.libraries.skiko") @@ -386,7 +372,6 @@ object CommunityModuleSets : ModuleSetProvider { /** * Grid/data viewer core modules. - * Corresponds to intellij.moduleSets.grid.core.xml */ fun gridCore(): ModuleSet = moduleSet("grid.core") { module("intellij.grid") @@ -398,7 +383,6 @@ object CommunityModuleSets : ModuleSetProvider { /** * Remote development common modules. - * Corresponds to intellij.moduleSets.rd.common.xml */ fun rdCommon(): ModuleSet = moduleSet("rd.common") { module("intellij.rd.ide.model.generated") @@ -408,7 +392,6 @@ object CommunityModuleSets : ModuleSetProvider { /** * IDE common modules (includes essential, compose, grid.core, vcs, xml, duplicates). - * Corresponds to intellij.moduleSets.ide.common.xml */ fun ideCommon(): ModuleSet = moduleSet("ide.common") { // Include essential first (which includes libraries) @@ -454,10 +437,10 @@ object CommunityModuleSets : ModuleSetProvider { moduleSet(vcs()) moduleSet(xml()) moduleSet(duplicates()) + module("intellij.platform.structuralSearch") // Note: rd.common is intentionally NOT included in ide.common // Reason: Rider uses custom module loading mode due to early backend startup requirements. // Products that need rd.common include it explicitly in their product files. } - } \ No newline at end of file diff --git a/platform/build-scripts/src/org/jetbrains/intellij/build/productLayout/ModuleSetRunner.kt b/platform/build-scripts/src/org/jetbrains/intellij/build/productLayout/ModuleSetRunner.kt index 8841fbd76651..c733d8561ce7 100644 --- a/platform/build-scripts/src/org/jetbrains/intellij/build/productLayout/ModuleSetRunner.kt +++ b/platform/build-scripts/src/org/jetbrains/intellij/build/productLayout/ModuleSetRunner.kt @@ -7,7 +7,7 @@ import kotlinx.serialization.json.Json import java.nio.file.Path /** - * Parses JSON argument from command line in the format --json or --json='{"filter":"...","value":"..."}'. + * Parses JSON argument from command line in the format `--json` or `--json='{"filter":"...","value":"..."}'`. * Returns null for full JSON output, or JsonFilter for filtered output. * * @param arg The command line argument (e.g., "--json" or "--json={...}") @@ -36,12 +36,10 @@ fun parseJsonArgument(arg: String): JsonFilter? { * 1. JSON mode (--json): Outputs comprehensive analysis as JSON to stdout * 2. Default mode: Generates XML files for module sets and products * - * This is the generic orchestration logic that was previously duplicated in UltimateModuleSets.kt. - * Now module set providers (community, ultimate) can call this function with their specific data. - * * @param args Command line arguments * @param communityModuleSets Module sets from community * @param ultimateModuleSets Module sets from ultimate (or empty for community-only) + * @param testProducts Test product specifications (name to ProductModulesContentSpec pairs) * @param communitySourceFile Source file path for community module sets * @param ultimateSourceFile Source file path for ultimate module sets (or null for community-only) * @param projectRoot Project root path @@ -51,45 +49,49 @@ fun runModuleSetMain( args: Array, communityModuleSets: List, ultimateModuleSets: List, + testProducts: List> = emptyList(), communitySourceFile: String, ultimateSourceFile: String?, projectRoot: Path, - generateXmlImpl: suspend () -> Unit -) { - // Parse --json arg with optional filter + generateXmlImpl: suspend () -> Unit, +): Unit = runBlocking(Dispatchers.Default) { + // Parse `--json` arg with optional filter val jsonArg = args.firstOrNull { it.startsWith("--json") } when { jsonArg != null -> { - runBlocking(Dispatchers.Default) { - // Prepare all module sets with metadata - val communityModuleSetsWithMeta = communityModuleSets.map { - ModuleSetMetadata(it, "community", communitySourceFile) - } - val ultimateModuleSetsWithMeta = if (ultimateSourceFile != null) { - ultimateModuleSets.map { - ModuleSetMetadata(it, "ultimate", ultimateSourceFile) - } - } else { - emptyList() - } - val allModuleSets = communityModuleSetsWithMeta + ultimateModuleSetsWithMeta - - // Discover all products (reuse existing logic from productXmlFileGenerator) - val products = discoverAllProductsForJson(projectRoot) - - // Parse filter from --json='{"filter":"..."}' format - val filter = parseJsonArgument(jsonArg) - - // Stream JSON to stdout - streamModuleAnalysisJson(allModuleSets, products, projectRoot, filter) + // Prepare all module sets with metadata + val communityModuleSetsWithMeta = communityModuleSets.map { + ModuleSetMetadata(it, "community", communitySourceFile) } + val ultimateModuleSetsWithMeta = if (ultimateSourceFile == null) { + emptyList() + } + else { + ultimateModuleSets.map { + ModuleSetMetadata(it, "ultimate", ultimateSourceFile) + } + } + val allModuleSets = communityModuleSetsWithMeta + ultimateModuleSetsWithMeta + + // Discover regular products and add passed test products + val regularProducts = discoverAllProductsForJson(projectRoot) + val testProductSpecs = testProducts.asSequence().map { (name, spec) -> + ProductSpec( + name = name, + className = "test-product", + sourceFile = "test-product", + pluginXmlPath = "ultimate/platform-ultimate/testResources/META-INF/${name}Plugin.xml", + contentSpec = spec, + buildModules = emptyList() + ) + } + + streamModuleAnalysisJson(allModuleSets = allModuleSets, products = (regularProducts + testProductSpecs).toList(), projectRoot = projectRoot, filter = parseJsonArgument(jsonArg)) } else -> { // Default mode: Generate XML files - runBlocking(Dispatchers.Default) { - generateXmlImpl() - } + generateXmlImpl() } } } diff --git a/platform/build-scripts/src/org/jetbrains/intellij/build/productLayout/ProductModulesHelpers.kt b/platform/build-scripts/src/org/jetbrains/intellij/build/productLayout/ProductModulesHelpers.kt deleted file mode 100644 index 3cdd206293f6..000000000000 --- a/platform/build-scripts/src/org/jetbrains/intellij/build/productLayout/ProductModulesHelpers.kt +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -package org.jetbrains.intellij.build.productLayout - -/** - * Product spec helper functions for reusable product content fragments. - * These functions return ProductModulesContentSpec instances that can be composed using include(). - * - * Use these helpers to reduce duplication across product specifications while keeping - * ProductModulesContentSpec immutable and declarative (suitable for future YAML representation). - */ - -/** - * Common capability aliases shared across most ultimate products. - * Bundles frequently repeated capability declarations to reduce duplication. - * - * Includes: - * - Run targets support - * - Microservices capabilities - * - ML inline completion - * - IDE provisioner - * - Marketplace integration - * - * Usage: - * ``` - * override fun getProductContentDescriptor(): ProductModulesContentSpec = productModules { - * include(commonCapabilityAliases()) - * // ... - * } - * ``` - */ -fun commonCapabilityAliases(): ProductModulesContentSpec = productModules { - alias("com.intellij.modules.run.targets") - alias("com.intellij.modules.microservices") - alias("com.intellij.ml.inline.completion") - alias("com.intellij.platform.ide.provisioner") - alias("com.intellij.marketplace") -} - -/** - * Python capability aliases for non-PyCharm products. - * Enables Python support in multi-language IDEs like GoLand, DataGrip, CLion, RustRover, etc. - * - * Includes: - * - Python core capabilities - * - Python in mini-IDE capabilities - * - Python in non-PyCharm IDE capabilities - * - * Usage: - * ``` - * override fun getProductContentDescriptor(): ProductModulesContentSpec = productModules { - * include(pythonMiniIdeCapabilities()) - * // ... - * } - * ``` - */ -fun pythonMiniIdeCapabilities(): ProductModulesContentSpec = productModules { - alias("com.intellij.modules.python-core-capable") - alias("com.intellij.modules.python-in-mini-ide-capable") - alias("com.intellij.modules.python-in-non-pycharm-ide-capable") -} - -/** - * Common platform includes repeated across most ultimate products. - * Bundles deprecatedInclude statements for legacy XML resource inclusion. - * - * Includes: - * - Platform lang plugin resources - * - Structural search resources - * - Remote servers implementation - * - Ultimate edition resources - * - * Usage: - * ``` - * override fun getProductContentDescriptor(): ProductModulesContentSpec = productModules { - * include(platformCommonIncludes()) - * // ... - * } - * ``` - */ -fun platformCommonIncludes(): ProductModulesContentSpec = productModules { - deprecatedInclude("intellij.platform.resources", "META-INF/PlatformLangPlugin.xml") - deprecatedInclude("intellij.platform.structuralSearch", "META-INF/structuralsearch.xml") - deprecatedInclude("intellij.platform.remoteServers.impl", "intellij.platform.remoteServers.impl.xml") - deprecatedInclude("intellij.platform.commercial", "META-INF/ultimate.xml") -} - -/** - * Extensions for native development IDEs (CLion, GoLand, RustRover). - * Combines process elevation support with native debugger capability. - * - * Includes: - * - Process elevation module set (for operations requiring elevated privileges) - * - Native debugger plugin capability alias - * - * Usage: - * ``` - * override fun getProductContentDescriptor(): ProductModulesContentSpec = productModules { - * include(nativeDevExtensions()) - * // ... - * } - * ``` - */ -fun nativeDevExtensions(): ProductModulesContentSpec = productModules { - moduleSet(CommunityModuleSets.elevation()) - alias("com.intellij.modules.nativeDebug-plugin-capable") -} diff --git a/platform/build-scripts/src/org/jetbrains/intellij/build/productLayout/productXmlFileGenerator.kt b/platform/build-scripts/src/org/jetbrains/intellij/build/productLayout/productXmlFileGenerator.kt index 0fe607c4b67e..351b0748413d 100644 --- a/platform/build-scripts/src/org/jetbrains/intellij/build/productLayout/productXmlFileGenerator.kt +++ b/platform/build-scripts/src/org/jetbrains/intellij/build/productLayout/productXmlFileGenerator.kt @@ -17,11 +17,11 @@ import java.nio.file.Path * Test products have properties = null (they don't have ProductProperties classes). */ internal data class DiscoveredProduct( - val name: String, - val config: ProductConfiguration, - val properties: ProductProperties?, - val spec: ProductModulesContentSpec?, - val pluginXmlPath: String? + @JvmField val name: String, + @JvmField val config: ProductConfiguration, + @JvmField val properties: ProductProperties?, + @JvmField val spec: ProductModulesContentSpec?, + @JvmField val pluginXmlPath: String?, ) /** @@ -31,11 +31,12 @@ internal data class DiscoveredProduct( */ internal fun DiscoveredProduct.toProductSpec(projectRoot: Path): ProductSpec { // For test products (properties = null), use "test-product" as source file - val sourceFile = if (properties != null) { - getProductPropertiesSourceFile(properties.javaClass, projectRoot) - } else { + val sourceFile = if (properties == null) { "test-product" } + else { + getProductPropertiesSourceFile(properties.javaClass, projectRoot) + } return ProductSpec( name = name, @@ -74,7 +75,7 @@ private suspend fun discoverAllProductsInternal(projectRoot: Path): List { - // Build a registry of test product name -> (xmlPath, contentSpec) - val testProductRegistry = mutableMapOf>() - - // Get ultimate test product specs (only if ultimate directory exists) - if (Files.exists(projectRoot.resolve("ultimate"))) { - try { - // Load UltimateModuleSets via reflection to avoid hard dependency - val ultimateModuleSetsClass = Class.forName("com.intellij.platform.commercial.buildScripts.productLayout.UltimateModuleSets") - val instanceField = ultimateModuleSetsClass.getDeclaredField("INSTANCE") - val ultimateModuleSetsInstance = instanceField.get(null) - val getTestProductSpecsMethod = ultimateModuleSetsClass.getDeclaredMethod("getTestProductSpecs") - @Suppress("UNCHECKED_CAST") - val ultimateSpecs = getTestProductSpecsMethod.invoke(ultimateModuleSetsInstance) as List> - - for ((name, spec) in ultimateSpecs) { - val xmlPath = "ultimate/platform-ultimate/testResources/META-INF/${name}Plugin.xml" - testProductRegistry[name] = xmlPath to spec - } - } catch (e: ClassNotFoundException) { - // Ultimate module not available, skip ultimate test products - } - } - - // Build DiscoveredProduct instances for test products that exist on disk - return testProductRegistry.mapNotNull { (name, pair) -> - val (xmlPath, spec) = pair - val xmlFile = projectRoot.resolve(xmlPath) - if (!Files.exists(xmlFile)) return@mapNotNull null - - DiscoveredProduct( - name = name, - config = ProductConfiguration( - className = "test-product", // test products don't have Properties class - modules = emptyList(), - pluginXmlPath = xmlPath - ), - properties = null, // test products don't have ProductProperties - spec = spec, - pluginXmlPath = xmlPath - ) - } -} /** * Discovers all products from dev-build.json registry and converts to ProductSpec for JSON output. * Public function that can be called from other modules (like ultimate buildScripts). + * Test products should be passed separately via runModuleSetMain's testProducts parameter. * * @param projectRoot The project root path * @return List of products as ProductSpec (simple data class with no internal dependencies) */ -suspend fun discoverAllProductsForJson(projectRoot: Path): List { +suspend fun discoverAllProductsForJson(projectRoot: Path): Sequence { val regularProducts = discoverAllProductsInternal(projectRoot) - val testProducts = discoverTestProducts(projectRoot) - val allProducts = regularProducts + testProducts - return allProducts.map { it.toProductSpec(projectRoot) } + return regularProducts.asSequence().map { it.toProductSpec(projectRoot) } } /** @@ -169,14 +118,37 @@ suspend fun discoverAllProductsForValidation(projectRoot: Path): List> = emptyList(), +): ProductGenerationResult { val regularProducts = discoverAllProductsInternal(projectRoot) - val testProducts = discoverTestProducts(projectRoot) + + // Convert test product specs to DiscoveredProduct instances + val testProducts = testProductSpecs.mapNotNull { (name, spec) -> + val xmlPath = "ultimate/platform-ultimate/testResources/META-INF/${name}Plugin.xml" + val xmlFile = projectRoot.resolve(xmlPath) + if (!Files.exists(xmlFile)) return@mapNotNull null + + DiscoveredProduct( + name = name, + config = ProductConfiguration( + className = "test-product", + modules = emptyList(), + pluginXmlPath = xmlPath + ), + properties = null, + spec = spec, + pluginXmlPath = xmlPath + ) + } + val products = regularProducts + testProducts // Detect if this is an Ultimate build (projectRoot != communityRoot) @@ -193,7 +165,7 @@ suspend fun generateAllProductXmlFiles(projectRoot: Path): ProductGenerationResu .loadProject(projectRoot.toString(), mapOf("MAVEN_REPOSITORY" to JpsMavenSettings.getMavenRepositoryPath()), false) .modules ) - + generateProductXml( pluginXmlPath = pluginXmlPath, spec = spec, diff --git a/platform/core-impl/src/com/intellij/ide/plugins/ModulesWithDependencies.kt b/platform/core-impl/src/com/intellij/ide/plugins/ModulesWithDependencies.kt index bce5788314d2..db6890f1674c 100644 --- a/platform/core-impl/src/com/intellij/ide/plugins/ModulesWithDependencies.kt +++ b/platform/core-impl/src/com/intellij/ide/plugins/ModulesWithDependencies.kt @@ -15,8 +15,10 @@ private val RIDER_MODULE_ID = PluginModuleId("intellij.rider", PluginModuleId.JE private val JSON_ALIAS_ID = PluginId.getId("com.intellij.modules.json") private val JSON_BACKEND_MODULE_ID = PluginModuleId("intellij.json.backend", PluginModuleId.JETBRAINS_NAMESPACE) -internal class ModulesWithDependencies(val modules: List, - val directDependencies: Map>) { +internal class ModulesWithDependencies( + @JvmField val modules: List, + @JvmField val directDependencies: Map>, +) { internal fun sorted(topologicalComparator: Comparator): ModulesWithDependencies { return ModulesWithDependencies( modules = modules.sortedWith(topologicalComparator), @@ -59,7 +61,10 @@ internal fun createModulesWithDependenciesAndAdditionalEdges(plugins: Collection // and is loaded only in IntelliJ IDEA, so it may use classes from Java plugin. val implicitDep = if (hasAllModules && PluginCompatibilityUtils.isLegacyPluginWithoutPlatformAliasDependencies(module)) { pluginIdToDescriptor.get(PluginManagerCore.JAVA_PLUGIN_ALIAS_ID) - } else null + } + else { + null + } if (implicitDep != null) { if (module === implicitDep) { PluginManagerCore.logger.error("Plugin $module depends on self") @@ -108,7 +113,7 @@ internal fun createModulesWithDependenciesAndAdditionalEdges(plugins: Collection dependenciesCollector.add(main) } - /* if the plugin containing the module is incompatible with some other plugins, make sure that the module is processed after that plugins (and all their required modules) + /* if the plugin containing the module is incompatible with some other plugins, make sure that the module is processed after these plugins (and all their required modules) to ensure that the proper module is disabled in case of package conflict */ for (incompatibility in main.incompatiblePlugins) { val incompatibleDescriptor = pluginIdToDescriptor.get(incompatibility) @@ -137,7 +142,7 @@ internal fun createModulesWithDependenciesAndAdditionalEdges(plugins: Collection ) to additionalEdges } -// alias in most cases points to Core plugin, so, we cannot use computed dependencies to check +// alias in most cases points to Core plugin, so we cannot use computed dependencies to check private fun doesDependOnPluginAlias(plugin: IdeaPluginDescriptorImpl, @Suppress("SameParameterValue") aliasId: PluginId): Boolean { return plugin.dependencies.any { it.pluginId == aliasId } || plugin.moduleDependencies.plugins.any { it == aliasId } } @@ -164,7 +169,7 @@ internal fun toCoreAwareComparator(comparator: Comparatorcom.intellij.modules.platform` or `com.intellij.modules.lang` tags. * See [this article](https://youtrack.jetbrains.com/articles/IJPL-A-956#keep-compatibility-with-external-plugins) for more details. */ -private val contentModulesExtractedInCorePluginWhichCanBeUsedFromExternalPlugins = listOf( +private val contentModulesExtractedInCorePluginWhichCanBeUsedFromExternalPlugins = arrayOf( "intellij.platform.collaborationTools.auth", "intellij.platform.collaborationTools.auth.base", "intellij.platform.tasks", @@ -175,13 +180,14 @@ private val contentModulesExtractedInCorePluginWhichCanBeUsedFromExternalPlugins "intellij.spellchecker.xml", "intellij.relaxng", "intellij.spellchecker", + "intellij.platform.structuralSearch", ).map { PluginModuleId(it, PluginModuleId.JETBRAINS_NAMESPACE) } /** * List of content modules from the core plugin which should be automatically added as dependencies third-party plugins and plugins with dependency on `com.intellij.modules.vcs` * plugin alias for compatibility. */ -private val vcsApiContentModules = listOf( +private val vcsApiContentModules = arrayOf( "intellij.platform.vcs.impl", "intellij.platform.vcs.dvcs", "intellij.platform.vcs.dvcs.impl", @@ -196,7 +202,7 @@ private val COLLABORATION_TOOLS_MODULE_ID = PluginModuleId("intellij.platform.co * List of content modules from the core plugin which should be automatically added as dependencies to all plugins with dependency on `org.jetbrains.completion.full.line` plugin * alias for compatibility. */ -private val fullLineApiContentModules = listOf( +private val fullLineApiContentModules = arrayOf( "intellij.fullLine.core", "intellij.fullLine.local", "intellij.fullLine.core.impl", @@ -222,8 +228,10 @@ private fun collectDirectDependenciesInOldFormat( } } else { - // e.g. `.env` plugin in an old format and doesn't explicitly specify dependency on a new extracted modules - dependenciesCollector.addAll(dep.contentModules) + // e.g. `.env` plugin in an old format and doesn't explicitly specify dependency on new extracted modules + if (dep is PluginMainDescriptor) { + dependenciesCollector.addAll(dep.contentModules) + } dependenciesCollector.add(dep) } @@ -290,7 +298,7 @@ private fun collectDirectDependenciesInNewFormat( } } - if (module.pluginId != PluginManagerCore.CORE_ID) { + if (module.pluginId != PluginManagerCore.CORE_ID && module is PluginMainDescriptor) { /* Add edges to all required content modules. This is needed to ensure that the main plugin module is processed after them, and at that point we can determine whether the plugin can be loaded or not. */ 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 3b6a73221d86..002507069223 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 @@ -280,6 +280,7 @@ + \ No newline at end of file diff --git a/platform/structuralsearch/module-content.yaml b/platform/structuralsearch/module-content.yaml new file mode 100644 index 000000000000..d8e86d6f6766 --- /dev/null +++ b/platform/structuralsearch/module-content.yaml @@ -0,0 +1,3 @@ +- name: dist.all/lib/intellij.platform.structuralSearch.jar + modules: + - name: intellij.platform.structuralSearch \ No newline at end of file diff --git a/platform/structuralsearch/resources/META-INF/structuralsearch.xml b/platform/structuralsearch/resources/intellij.platform.structuralSearch.xml similarity index 98% rename from platform/structuralsearch/resources/META-INF/structuralsearch.xml rename to platform/structuralsearch/resources/intellij.platform.structuralSearch.xml index 9efdbb6ff1b9..daac30b824f4 100644 --- a/platform/structuralsearch/resources/META-INF/structuralsearch.xml +++ b/platform/structuralsearch/resources/intellij.platform.structuralSearch.xml @@ -1,4 +1,4 @@ - + diff --git a/plugins/kotlin/code-insight/structural-search-k2/resources/intellij.kotlin.codeInsight.structuralSearch.xml b/plugins/kotlin/code-insight/structural-search-k2/resources/intellij.kotlin.codeInsight.structuralSearch.xml index 4f9212711e51..5f19f00127fa 100644 --- a/plugins/kotlin/code-insight/structural-search-k2/resources/intellij.kotlin.codeInsight.structuralSearch.xml +++ b/plugins/kotlin/code-insight/structural-search-k2/resources/intellij.kotlin.codeInsight.structuralSearch.xml @@ -1,6 +1,6 @@ - + diff --git a/plugins/properties/resources/intellij.properties.backend.xml b/plugins/properties/resources/intellij.properties.backend.xml index da82cf0ad9c7..26e276a238da 100644 --- a/plugins/properties/resources/intellij.properties.backend.xml +++ b/plugins/properties/resources/intellij.properties.backend.xml @@ -8,6 +8,7 @@ + diff --git a/python/resources/META-INF/pycharm-core.xml b/python/resources/META-INF/pycharm-core.xml index 93fc5707aac1..6c7c6c4f5488 100644 --- a/python/resources/META-INF/pycharm-core.xml +++ b/python/resources/META-INF/pycharm-core.xml @@ -5,9 +5,7 @@ - - - +