From bc38cfec45a640836a4d8bea0868dede02ff80aa Mon Sep 17 00:00:00 2001 From: Vladimir Krivosheev Date: Wed, 12 Nov 2025 08:41:52 +0100 Subject: [PATCH] IJPL-217839 introduce javaIdeBaseFragment as a replacement for deprecated xi-include GitOrigin-RevId: 7e02eec1ae4b27cfa318b780ce9e00c5dadee350 --- .../intellij/build/IdeaCommunityProperties.kt | 2 +- .../resources/META-INF/IdeaPlugin.xml | 8 + .../resources/META-INF/JavaIdePlugin.xml | 27 +--- .../src/DuplicateIncludeDetector.kt | 29 +--- .../product-dsl/src/ModuleSetJsonExport.kt | 4 +- .../product-dsl/src/ProductXmlRenderer.kt | 8 +- .../src/json/ModuleSetJsonWriter.kt | 24 ++- .../build/DuplicateIncludeAnalyzer.kt | 142 ------------------ .../CommunityProductFragments.kt | 24 +++ 9 files changed, 64 insertions(+), 204 deletions(-) delete mode 100644 platform/build-scripts/src/org/jetbrains/intellij/build/DuplicateIncludeAnalyzer.kt diff --git a/build/src/org/jetbrains/intellij/build/IdeaCommunityProperties.kt b/build/src/org/jetbrains/intellij/build/IdeaCommunityProperties.kt index b8ebd9eecbaa..6b8bc0ae8c4b 100644 --- a/build/src/org/jetbrains/intellij/build/IdeaCommunityProperties.kt +++ b/build/src/org/jetbrains/intellij/build/IdeaCommunityProperties.kt @@ -230,7 +230,7 @@ fun intellijCommunityBaseFragment(): ProductModulesContentSpec = productModules alias("com.intellij.platform.ide.provisioner") include(CommunityProductFragments.platformLangBaseFragment()) - deprecatedInclude("intellij.java.ide.resources", "META-INF/JavaIdePlugin.xml") + include(CommunityProductFragments.javaIdeBaseFragment()) deprecatedInclude("intellij.idea.community.customization", "META-INF/tips-intellij-idea-community.xml") moduleSet(CommunityModuleSets.debuggerStreams()) diff --git a/community-resources/resources/META-INF/IdeaPlugin.xml b/community-resources/resources/META-INF/IdeaPlugin.xml index 7c4c67be2469..fdf58a2c8c86 100644 --- a/community-resources/resources/META-INF/IdeaPlugin.xml +++ b/community-resources/resources/META-INF/IdeaPlugin.xml @@ -2,14 +2,22 @@ + + + + + + + + diff --git a/java/ide-resources/resources/META-INF/JavaIdePlugin.xml b/java/ide-resources/resources/META-INF/JavaIdePlugin.xml index 3e14d630728d..ff720084f616 100644 --- a/java/ide-resources/resources/META-INF/JavaIdePlugin.xml +++ b/java/ide-resources/resources/META-INF/JavaIdePlugin.xml @@ -1,29 +1,4 @@ - - - - - - - - - - - - + , projectRoot: Path) { - // Convert ProductSpec plugin paths to actual file paths - val productFiles = products - .mapNotNull { it.pluginXmlPath } - .map { projectRoot.resolve(it) } - .filter { it.exists() && it.isRegularFile() } - - // Run detection - val report = DuplicateIncludeDetector.detectDuplicates(productFiles, projectRoot) - - // Output JSON - val json = Json { prettyPrint = true } - println(json.encodeToString(report)) -} - /** * Report containing all detected duplicate includes. */ diff --git a/platform/build-scripts/product-dsl/src/ModuleSetJsonExport.kt b/platform/build-scripts/product-dsl/src/ModuleSetJsonExport.kt index 23186d4470fa..9d4966d15147 100644 --- a/platform/build-scripts/product-dsl/src/ModuleSetJsonExport.kt +++ b/platform/build-scripts/product-dsl/src/ModuleSetJsonExport.kt @@ -113,7 +113,7 @@ fun streamModuleAnalysisJson( } filter.filter == "duplicates" -> { gen.writeObjectFieldStart("duplicateAnalysis") - writeDuplicateAnalysis(gen, allModuleSets) + writeDuplicateAnalysis(gen, allModuleSets, enrichedProducts, projectRoot) gen.writeEndObject() } filter.filter == "product" && filter.value != null -> { @@ -219,7 +219,7 @@ private fun writeAllSections( // Write duplicate analysis gen.writeObjectFieldStart("duplicateAnalysis") - writeDuplicateAnalysis(gen, allModuleSets) + writeDuplicateAnalysis(gen, allModuleSets, products, projectRoot) gen.writeEndObject() // Write product composition analysis diff --git a/platform/build-scripts/product-dsl/src/ProductXmlRenderer.kt b/platform/build-scripts/product-dsl/src/ProductXmlRenderer.kt index f26813716326..4d815c51eb54 100644 --- a/platform/build-scripts/product-dsl/src/ProductXmlRenderer.kt +++ b/platform/build-scripts/product-dsl/src/ProductXmlRenderer.kt @@ -29,8 +29,8 @@ internal fun StringBuilder.appendOpeningTag( // Determine if xi:include namespace is needed val hasXmlIncludes = !inlineXmlIncludes && spec.deprecatedXmlIncludes.isNotEmpty() val hasModuleSetIncludes = !inlineModuleSets && spec.moduleSets.isNotEmpty() - val needsXiNamespace = hasXmlIncludes || hasModuleSetIncludes - + // when we inline another some xi-include file, it can in turn have own xi-includes + val needsXiNamespace = inlineXmlIncludes || hasXmlIncludes || hasModuleSetIncludes if (needsXiNamespace) { append("\n") } @@ -62,7 +62,7 @@ internal fun generateXIncludes( isUltimateBuild: Boolean, ) { for (include in spec.deprecatedXmlIncludes) { - // When inlining: skip ultimate-only xi-includes in Community builds + // When inlining: skip ultimate-only `xi-includes` in Community builds if (inlineXmlIncludes && include.ultimateOnly && !isUltimateBuild) { continue } @@ -111,7 +111,7 @@ internal fun generateXIncludes( } /** - * Converts a resource path to an xi:include href path. + * Converts a resource path to an `xi:include` href path. */ internal fun resourcePathToXIncludePath(resourcePath: String): String { return if (isModuleNameLikeFilename(resourcePath)) resourcePath else "/$resourcePath" diff --git a/platform/build-scripts/product-dsl/src/json/ModuleSetJsonWriter.kt b/platform/build-scripts/product-dsl/src/json/ModuleSetJsonWriter.kt index 13ca9d546198..92f7c96a18d4 100644 --- a/platform/build-scripts/product-dsl/src/json/ModuleSetJsonWriter.kt +++ b/platform/build-scripts/product-dsl/src/json/ModuleSetJsonWriter.kt @@ -2,10 +2,15 @@ package org.jetbrains.intellij.build.productLayout.json import com.fasterxml.jackson.core.JsonGenerator +import org.jetbrains.intellij.build.productLayout.DuplicateIncludeDetector import org.jetbrains.intellij.build.productLayout.ModuleSet import org.jetbrains.intellij.build.productLayout.analysis.ModuleSetMetadata +import org.jetbrains.intellij.build.productLayout.analysis.ProductSpec import org.jetbrains.intellij.build.productLayout.collectAllModuleNames import org.jetbrains.intellij.build.productLayout.collectAllModuleNamesFromSet +import java.nio.file.Path +import kotlin.io.path.exists +import kotlin.io.path.isRegularFile /** * Writes a single module set to JSON. @@ -43,11 +48,13 @@ fun writeModuleSet( } /** - * Writes duplicate analysis section. + * Writes duplicate analysis section including both module duplicates and xi:include duplicates. */ fun writeDuplicateAnalysis( gen: JsonGenerator, - allModuleSets: List + allModuleSets: List, + products: List, + projectRoot: Path ) { // Find modules that appear in multiple module sets val moduleToSets = mutableMapOf>() @@ -121,4 +128,17 @@ fun writeDuplicateAnalysis( } } gen.writeEndArray() + + // xi:include duplicate detection + val productFiles = products + .mapNotNull { it.pluginXmlPath } + .map { projectRoot.resolve(it) } + .filter { it.exists() && it.isRegularFile() } + + val report = DuplicateIncludeDetector.detectDuplicates(productFiles, projectRoot) + + // Serialize using kotlinx.serialization and write raw JSON (consistent with ModuleSet pattern) + val reportJson = kotlinxJson.encodeToString(report) + gen.writeFieldName("xiIncludeDuplicates") + gen.writeRawValue(reportJson) } diff --git a/platform/build-scripts/src/org/jetbrains/intellij/build/DuplicateIncludeAnalyzer.kt b/platform/build-scripts/src/org/jetbrains/intellij/build/DuplicateIncludeAnalyzer.kt deleted file mode 100644 index a7494c3b2315..000000000000 --- a/platform/build-scripts/src/org/jetbrains/intellij/build/DuplicateIncludeAnalyzer.kt +++ /dev/null @@ -1,142 +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 - -import com.intellij.openapi.application.PathManager -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.runBlocking -import kotlinx.serialization.json.Json -import org.jetbrains.intellij.build.productLayout.DuplicateIncludeDetector -import java.nio.file.Path -import kotlin.io.path.exists -import kotlin.io.path.isDirectory -import kotlin.io.path.isRegularFile -import kotlin.io.path.listDirectoryEntries -import kotlin.io.path.readText -import kotlin.io.path.walk - -/** - * Command-line tool to detect duplicate xi:include elements in product plugin.xml files. - * - * Usage: - * bazel run //community/platform/build-scripts:DuplicateIncludeAnalyzer - * - * Output: - * JSON report showing products with duplicate includes, where each duplicate comes from, - * and summary statistics. - */ -object DuplicateIncludeAnalyzer { - @JvmStatic - fun main(args: Array) { - runBlocking(Dispatchers.Default) { - val projectRoot = Path.of(PathManager.getHomePathFor(DuplicateIncludeAnalyzer::class.java)!!) - - // Discover all product plugin.xml files - val productFiles = discoverProductFiles(projectRoot) - - // Run detection - val report = DuplicateIncludeDetector.detectDuplicates(productFiles, projectRoot) - - // Output JSON - val json = Json { prettyPrint = true } - println(json.encodeToString(report)) - } - } - - /** - * Discovers all product plugin.xml files in the project. - * Searches in known product directories and looks for files ending with Plugin.xml or named plugin.xml. - */ - private fun discoverProductFiles(projectRoot: Path): List { - val productFiles = mutableListOf() - - // Known product directories to search - val productDirs = listOf( - projectRoot.resolve("community"), - projectRoot.resolve("ultimate"), - projectRoot.resolve("CIDR"), - projectRoot.resolve("goland"), - projectRoot.resolve("ruby"), - projectRoot.resolve("WebStorm"), - projectRoot.resolve("dbe"), - projectRoot.resolve("aqua"), - projectRoot.resolve("rider"), - projectRoot.resolve("python"), - projectRoot.resolve("plugins"), - ) - - for (dir in productDirs) { - if (!dir.exists() || !dir.isDirectory()) { - continue - } - - // Search for Plugin.xml files in resources/META-INF directories - dir.walk() - .filter { it.isRegularFile() } - .filter { it.parent?.fileName?.toString() == "META-INF" } - .filter { - val name = it.fileName.toString() - name.endsWith("Plugin.xml") || name == "plugin.xml" - } - .filter { isProductFile(it) } - .forEach { productFiles.add(it) } - } - - return productFiles.distinct() - } - - /** - * Checks if an XML file is a product descriptor (not a plugin descriptor). - * Products don't have their own tag or have com.intellij. - * Also filters out test files and non-product descriptors. - */ - private fun isProductFile(file: Path): Boolean { - // Skip test resources - if (file.toString().contains("/testResources/") || - file.toString().contains("/testSrc/") || - file.toString().contains("/test/")) { - return false - } - - // Skip toolbox - if (file.toString().contains("/toolbox/")) { - return false - } - - val fileName = file.fileName.toString() - - // Skip module descriptor files (have dots in the name like intellij.platform.jewel.detektPlugin.xml) - if (fileName != "plugin.xml" && fileName.contains(".") && !fileName.matches(Regex("^[A-Z][a-zA-Z]*Plugin\\.xml$"))) { - return false - } - - try { - val content = file.readText() - - // Check for tag that's NOT com.intellij - val idMatch = Regex("""([^<]+)""").find(content) - if (idMatch != null && idMatch.groupValues[1] != "com.intellij") { - // This is a plugin with its own ID, not a product - return false - } - - // Check if it has ApplicationInfo.xml nearby (strong indicator of a product) - val resourceRoot = file.parent?.parent // Go up from META-INF to resources - if (resourceRoot != null) { - val ideaDir = resourceRoot.resolve("idea") - if (ideaDir.exists() && ideaDir.isDirectory()) { - val hasAppInfo = ideaDir.listDirectoryEntries() - .any { it.fileName.toString().endsWith("ApplicationInfo.xml") } - if (hasAppInfo) { - return true - } - } - } - - // If no tag or has com.intellij, likely a product - return true - } - catch (e: Exception) { - return false - } - } -} diff --git a/platform/build-scripts/src/org/jetbrains/intellij/build/productLayout/CommunityProductFragments.kt b/platform/build-scripts/src/org/jetbrains/intellij/build/productLayout/CommunityProductFragments.kt index e84e65531df3..fd32a21788f5 100644 --- a/platform/build-scripts/src/org/jetbrains/intellij/build/productLayout/CommunityProductFragments.kt +++ b/platform/build-scripts/src/org/jetbrains/intellij/build/productLayout/CommunityProductFragments.kt @@ -18,4 +18,28 @@ object CommunityProductFragments { fun platformLangBaseFragment(): ProductModulesContentSpec = productModules { deprecatedInclude("intellij.platform.resources", "META-INF/PlatformLangPlugin.xml") } + + /** + * Java IDE base fragment: provides Java IDE module aliases and optional plugin includes. + * + * Includes: + * - Module aliases for Java IDE capability detection + * - Optional remote servers support + * - Optional UI Designer support + * - Extensions for IDEA-specific customization (UTM tracking, new UI onboarding) + * + * Use this fragment for products that include Java IDE functionality. + */ + fun javaIdeBaseFragment(): ProductModulesContentSpec = productModules { + // Module capability aliases + alias("com.intellij.modules.all") + alias("com.intellij.modules.jsp.base") + + // Optional plugin support (with fallback) + deprecatedInclude("intellij.platform.remoteServers.impl", "intellij.platform.remoteServers.impl.xml", optional = true) + deprecatedInclude("intellij.uiDesigner", "META-INF/DesignerCorePlugin.xml", optional = true) + + // Extensions block (UTM tracking, new UI onboarding) + deprecatedInclude("intellij.java.ide.resources", "META-INF/JavaIdePlugin.xml") + } }