diff --git a/java/idea-ui/testSrc/com/intellij/framework/detection/FrameworkDetectionTest.java b/java/idea-ui/testSrc/com/intellij/framework/detection/FrameworkDetectionTest.java
index 3289f2db34b1..80e04f3511c0 100644
--- a/java/idea-ui/testSrc/com/intellij/framework/detection/FrameworkDetectionTest.java
+++ b/java/idea-ui/testSrc/com/intellij/framework/detection/FrameworkDetectionTest.java
@@ -6,10 +6,10 @@ import com.intellij.facet.FacetManager;
import com.intellij.facet.mock.*;
import com.intellij.framework.detection.impl.FacetBasedDetectedFrameworkDescription;
import com.intellij.framework.detection.impl.FrameworkDetectionManager;
-import com.intellij.ide.plugins.DynamicPluginsTestUtil;
import com.intellij.openapi.application.WriteAction;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.platform.testFramework.DynamicPluginTestUtilsKt;
import com.intellij.testFramework.PsiTestUtil;
import com.intellij.testFramework.VfsTestUtil;
@@ -52,7 +52,7 @@ public class FrameworkDetectionTest extends FrameworkDetectionTestCase {
VirtualFile file = createFrameworkConfig("my-config.xml");
assertNoFrameworksDetected();
- Disposer.register(getTestRootDisposable(), DynamicPluginsTestUtil.loadExtensionWithText(
+ Disposer.register(getTestRootDisposable(), DynamicPluginTestUtilsKt.loadExtensionWithText(
"",
"com.intellij"));
diff --git a/java/idea-ui/testSrc/com/intellij/framework/detection/FrameworkDetectionTestCase.java b/java/idea-ui/testSrc/com/intellij/framework/detection/FrameworkDetectionTestCase.java
index 79cb49605f26..bc10ab0ad988 100644
--- a/java/idea-ui/testSrc/com/intellij/framework/detection/FrameworkDetectionTestCase.java
+++ b/java/idea-ui/testSrc/com/intellij/framework/detection/FrameworkDetectionTestCase.java
@@ -6,10 +6,10 @@ import com.intellij.facet.mock.MockFacetDetector;
import com.intellij.facet.mock.MockSubFacetDetector;
import com.intellij.framework.detection.impl.FrameworkDetectionManager;
import com.intellij.framework.detection.impl.FrameworkDetectionUtil;
-import com.intellij.ide.plugins.DynamicPluginsTestUtil;
import com.intellij.openapi.roots.PlatformModifiableModelsProvider;
import com.intellij.openapi.roots.ui.configuration.DefaultModulesProvider;
import com.intellij.openapi.util.Disposer;
+import com.intellij.platform.testFramework.DynamicPluginTestUtilsKt;
import java.util.List;
@@ -19,11 +19,11 @@ public abstract class FrameworkDetectionTestCase extends FacetTestCase {
super.setUp();
if (!"dynamicDetector".equals(getTestName(true))) {
//todo we can get rid of this ugly check by converting facet tests to JUnit4 and using test rules to enable facet detection
- Disposer.register(getTestRootDisposable(), DynamicPluginsTestUtil.loadExtensionWithText(
+ Disposer.register(getTestRootDisposable(), DynamicPluginTestUtilsKt.loadExtensionWithText(
"",
"com.intellij"));
- Disposer.register(getTestRootDisposable(), DynamicPluginsTestUtil.loadExtensionWithText(
+ Disposer.register(getTestRootDisposable(), DynamicPluginTestUtilsKt.loadExtensionWithText(
"",
"com.intellij"));
}
diff --git a/java/java-tests/testSrc/com/intellij/java/index/propertyBased/FileTypeIndexConsistencyTest.kt b/java/java-tests/testSrc/com/intellij/java/index/propertyBased/FileTypeIndexConsistencyTest.kt
index f349f2b6276f..c1a00f658d0d 100644
--- a/java/java-tests/testSrc/com/intellij/java/index/propertyBased/FileTypeIndexConsistencyTest.kt
+++ b/java/java-tests/testSrc/com/intellij/java/index/propertyBased/FileTypeIndexConsistencyTest.kt
@@ -1,7 +1,7 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.java.index.propertyBased
-import com.intellij.ide.plugins.loadExtensionWithText
+import com.intellij.platform.testFramework.loadExtensionWithText
import com.intellij.openapi.extensions.InternalIgnoreDependencyViolation
import com.intellij.openapi.fileTypes.FileType
import com.intellij.openapi.fileTypes.FileTypeManager
diff --git a/java/java-tests/testSrc/com/intellij/lang/LanguageSubstitutorLoadUnloadTest.kt b/java/java-tests/testSrc/com/intellij/lang/LanguageSubstitutorLoadUnloadTest.kt
index 3264e32126ed..6db775574322 100644
--- a/java/java-tests/testSrc/com/intellij/lang/LanguageSubstitutorLoadUnloadTest.kt
+++ b/java/java-tests/testSrc/com/intellij/lang/LanguageSubstitutorLoadUnloadTest.kt
@@ -1,7 +1,7 @@
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.lang
-import com.intellij.ide.plugins.loadExtensionWithText
+import com.intellij.platform.testFramework.loadExtensionWithText
import com.intellij.lang.java.JavaLanguage
import com.intellij.openapi.fileTypes.PlainTextLanguage
import com.intellij.openapi.project.Project
diff --git a/java/java-tests/testSrc/com/intellij/scopes/LibraryUseSearchUsingScopeEnlargerTest.java b/java/java-tests/testSrc/com/intellij/scopes/LibraryUseSearchUsingScopeEnlargerTest.java
index 1ada47b26e65..c9412c972cfc 100644
--- a/java/java-tests/testSrc/com/intellij/scopes/LibraryUseSearchUsingScopeEnlargerTest.java
+++ b/java/java-tests/testSrc/com/intellij/scopes/LibraryUseSearchUsingScopeEnlargerTest.java
@@ -5,13 +5,13 @@ import com.intellij.JavaTestUtil;
import com.intellij.codeInsight.daemon.LineMarkerInfo;
import com.intellij.codeInsight.daemon.impl.DaemonCodeAnalyzerImpl;
import com.intellij.codeInsight.daemon.impl.MarkerType;
-import com.intellij.ide.plugins.DynamicPluginsTestUtil;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.extensions.InternalIgnoreDependencyViolation;
import com.intellij.openapi.roots.OrderRootType;
import com.intellij.openapi.roots.impl.LibraryScopeCache;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.vfs.LocalFileSystem;
+import com.intellij.platform.testFramework.DynamicPluginTestUtilsKt;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiElement;
@@ -43,7 +43,7 @@ public class LibraryUseSearchUsingScopeEnlargerTest extends JavaCodeInsightFixtu
@Override
protected void setUp() throws Exception {
super.setUp();
- Disposer.register(getTestRootDisposable(), DynamicPluginsTestUtil.loadExtensionWithText(
+ Disposer.register(getTestRootDisposable(), DynamicPluginTestUtilsKt.loadExtensionWithText(
"",
"com.intellij"));
//bug? Test seems to use stale data.
diff --git a/java/java-tests/testSrc/com/intellij/util/indexing/BrokenPluginIndexingTest.kt b/java/java-tests/testSrc/com/intellij/util/indexing/BrokenPluginIndexingTest.kt
index 8cbaede3d342..96e9b95156ea 100644
--- a/java/java-tests/testSrc/com/intellij/util/indexing/BrokenPluginIndexingTest.kt
+++ b/java/java-tests/testSrc/com/intellij/util/indexing/BrokenPluginIndexingTest.kt
@@ -1,7 +1,7 @@
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.util.indexing
-import com.intellij.ide.plugins.loadExtensionWithText
+import com.intellij.platform.testFramework.loadExtensionWithText
import com.intellij.openapi.extensions.InternalIgnoreDependencyViolation
import com.intellij.openapi.fileTypes.FileType
import com.intellij.openapi.fileTypes.FileTypeRegistry
diff --git a/java/java-tests/testSrc/com/intellij/util/indexing/CountingFileBasedIndexExtension.kt b/java/java-tests/testSrc/com/intellij/util/indexing/CountingFileBasedIndexExtension.kt
index e32c6662d7c5..2827491912fc 100644
--- a/java/java-tests/testSrc/com/intellij/util/indexing/CountingFileBasedIndexExtension.kt
+++ b/java/java-tests/testSrc/com/intellij/util/indexing/CountingFileBasedIndexExtension.kt
@@ -1,7 +1,7 @@
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.util.indexing
-import com.intellij.ide.plugins.loadExtensionWithText
+import com.intellij.platform.testFramework.loadExtensionWithText
import com.intellij.openapi.Disposable
import com.intellij.openapi.extensions.InternalIgnoreDependencyViolation
import com.intellij.openapi.util.Disposer
diff --git a/java/java-tests/testSrc/com/intellij/util/indexing/IndexInfrastructureExtensionTest.kt b/java/java-tests/testSrc/com/intellij/util/indexing/IndexInfrastructureExtensionTest.kt
index 8d1a834106f7..8c1318277a60 100644
--- a/java/java-tests/testSrc/com/intellij/util/indexing/IndexInfrastructureExtensionTest.kt
+++ b/java/java-tests/testSrc/com/intellij/util/indexing/IndexInfrastructureExtensionTest.kt
@@ -1,7 +1,7 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.util.indexing
-import com.intellij.ide.plugins.loadExtensionWithText
+import com.intellij.platform.testFramework.loadExtensionWithText
import com.intellij.openapi.application.PathManager
import com.intellij.openapi.extensions.InternalIgnoreDependencyViolation
import com.intellij.openapi.project.Project
diff --git a/java/java-tests/testSrc/com/intellij/util/indexing/MultiProjectIndexTest.kt b/java/java-tests/testSrc/com/intellij/util/indexing/MultiProjectIndexTest.kt
index c4c1c1fdf968..4d137ed66ff8 100644
--- a/java/java-tests/testSrc/com/intellij/util/indexing/MultiProjectIndexTest.kt
+++ b/java/java-tests/testSrc/com/intellij/util/indexing/MultiProjectIndexTest.kt
@@ -2,7 +2,7 @@
package com.intellij.util.indexing
import com.intellij.find.ngrams.TrigramIndex
-import com.intellij.ide.plugins.loadExtensionWithText
+import com.intellij.platform.testFramework.loadExtensionWithText
import com.intellij.openapi.extensions.InternalIgnoreDependencyViolation
import com.intellij.openapi.module.JavaModuleType
import com.intellij.openapi.project.DumbService
diff --git a/java/java-tests/testSrc/com/intellij/util/indexing/TracingFileBasedIndexExtension.kt b/java/java-tests/testSrc/com/intellij/util/indexing/TracingFileBasedIndexExtension.kt
index 0e945a03bc41..f4915dcbd11a 100644
--- a/java/java-tests/testSrc/com/intellij/util/indexing/TracingFileBasedIndexExtension.kt
+++ b/java/java-tests/testSrc/com/intellij/util/indexing/TracingFileBasedIndexExtension.kt
@@ -1,10 +1,10 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.util.indexing
-import com.intellij.ide.plugins.loadExtensionWithText
import com.intellij.openapi.Disposable
import com.intellij.openapi.extensions.InternalIgnoreDependencyViolation
import com.intellij.openapi.util.Disposer
+import com.intellij.platform.testFramework.loadExtensionWithText
import com.intellij.util.io.EnumeratorStringDescriptor
import com.intellij.util.io.KeyDescriptor
import com.intellij.util.io.VoidDataExternalizer
diff --git a/platform/lang-impl/intellij.platform.lang.tests.iml b/platform/lang-impl/intellij.platform.lang.tests.iml
index f16297c2278b..646280575e3e 100644
--- a/platform/lang-impl/intellij.platform.lang.tests.iml
+++ b/platform/lang-impl/intellij.platform.lang.tests.iml
@@ -62,6 +62,5 @@
-
\ No newline at end of file
diff --git a/platform/lang-impl/testSources/com/intellij/util/indexing/DirtyFilesQueueTest.kt b/platform/lang-impl/testSources/com/intellij/util/indexing/DirtyFilesQueueTest.kt
index 62dc42691061..de879cde44de 100644
--- a/platform/lang-impl/testSources/com/intellij/util/indexing/DirtyFilesQueueTest.kt
+++ b/platform/lang-impl/testSources/com/intellij/util/indexing/DirtyFilesQueueTest.kt
@@ -4,7 +4,7 @@ package com.intellij.util.indexing
import com.intellij.find.TextSearchService
import com.intellij.ide.impl.ProjectUtil
import com.intellij.ide.plugins.PluginManagerCore
-import com.intellij.ide.plugins.loadExtensionWithText
+import com.intellij.platform.testFramework.loadExtensionWithText
import com.intellij.openapi.application.*
import com.intellij.openapi.components.service
import com.intellij.openapi.fileTypes.ExtensionFileNameMatcher
diff --git a/platform/platform-tests/testSrc/com/intellij/ide/plugins/ClassLoaderConfiguratorTest.kt b/platform/platform-tests/testSrc/com/intellij/ide/plugins/ClassLoaderConfiguratorTest.kt
index 811581f3acb3..a164632e63af 100644
--- a/platform/platform-tests/testSrc/com/intellij/ide/plugins/ClassLoaderConfiguratorTest.kt
+++ b/platform/platform-tests/testSrc/com/intellij/ide/plugins/ClassLoaderConfiguratorTest.kt
@@ -7,6 +7,7 @@ import com.intellij.ide.plugins.cl.PluginClassLoader
import com.intellij.openapi.util.BuildNumber
import com.intellij.platform.ide.bootstrap.ZipFilePoolImpl
import com.intellij.platform.plugins.parser.impl.PluginDescriptorBuilder
+import com.intellij.platform.testFramework.PluginBuilder
import com.intellij.testFramework.assertions.Assertions.assertThat
import com.intellij.testFramework.rules.InMemoryFsExtension
import com.intellij.util.io.directoryStreamIfExists
diff --git a/platform/platform-tests/testSrc/com/intellij/ide/plugins/DynamicPluginsTest.kt b/platform/platform-tests/testSrc/com/intellij/ide/plugins/DynamicPluginsTest.kt
index 0b924fe10d53..bfe0108d11c0 100644
--- a/platform/platform-tests/testSrc/com/intellij/ide/plugins/DynamicPluginsTest.kt
+++ b/platform/platform-tests/testSrc/com/intellij/ide/plugins/DynamicPluginsTest.kt
@@ -40,6 +40,11 @@ import com.intellij.openapi.startup.StartupActivity
import com.intellij.openapi.util.Disposer
import com.intellij.openapi.util.registry.Registry
import com.intellij.openapi.util.use
+import com.intellij.platform.testFramework.PluginBuilder
+import com.intellij.platform.testFramework.loadAndInitDescriptorInTest
+import com.intellij.platform.testFramework.loadExtensionWithText
+import com.intellij.platform.testFramework.setPluginClassLoaderForMainAndSubPlugins
+import com.intellij.platform.testFramework.unloadAndUninstallPlugin
import com.intellij.psi.PsiFile
import com.intellij.testFramework.EdtRule
import com.intellij.testFramework.ProjectRule
@@ -86,7 +91,7 @@ class DynamicPluginsTest {
pluginBuilder: PluginBuilder,
disabledPlugins: Set = emptySet(),
): Disposable {
- return loadPluginWithText(
+ return com.intellij.platform.testFramework.loadPluginWithText(
pluginBuilder = pluginBuilder,
path = rootPath.resolve(Ksuid.generate()),
disabledPlugins = disabledPlugins,
@@ -811,7 +816,8 @@ class DynamicPluginsTest {
private fun loadPluginWithOptionalDependency(pluginDescriptor: PluginBuilder,
optionalDependencyDescriptor: PluginBuilder,
- dependsOn: PluginBuilder): Disposable {
+ dependsOn: PluginBuilder
+ ): Disposable {
pluginDescriptor.depends(dependsOn.id, optionalDependencyDescriptor)
return loadPluginWithText(pluginDescriptor)
}
diff --git a/platform/platform-tests/testSrc/com/intellij/ide/plugins/PluginBuilder.kt b/platform/platform-tests/testSrc/com/intellij/ide/plugins/PluginBuilder.kt
index cee9a653d5a0..d099cf4efe8b 100644
--- a/platform/platform-tests/testSrc/com/intellij/ide/plugins/PluginBuilder.kt
+++ b/platform/platform-tests/testSrc/com/intellij/ide/plugins/PluginBuilder.kt
@@ -1,27 +1,16 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.ide.plugins
-import com.intellij.openapi.extensions.PluginId
import com.intellij.platform.plugins.parser.impl.PluginDescriptorBuilder
import com.intellij.platform.plugins.parser.impl.PluginDescriptorFromXmlStreamConsumer
-import com.intellij.platform.plugins.parser.impl.PluginXmlConst
import com.intellij.platform.plugins.parser.impl.ReadModuleContext
import com.intellij.platform.plugins.parser.impl.consume
import com.intellij.platform.plugins.parser.impl.elements.OS
-import com.intellij.util.io.Compressor
-import com.intellij.util.io.createParentDirectories
import com.intellij.util.io.write
import com.intellij.util.xml.dom.NoOpXmlInterner
import org.intellij.lang.annotations.Language
import org.jetbrains.annotations.TestOnly
-import java.io.ByteArrayOutputStream
-import java.io.OutputStream
-import java.nio.file.Files
import java.nio.file.Path
-import java.util.concurrent.atomic.AtomicInteger
-import kotlin.io.path.createDirectories
-
-private val pluginIdCounter = AtomicInteger()
fun plugin(outDir: Path, @Language("XML") descriptor: String) {
val rawDescriptor = try {
@@ -53,306 +42,6 @@ fun module(outDir: Path, ownerId: String, moduleId: String, @Language("XML") des
outDir.resolve("$ownerId/$moduleId.xml").write(descriptor.trimIndent())
}
-class PluginBuilder private constructor() {
- private data class ExtensionBlock(val ns: String, val text: String)
- private data class DependsTag(val pluginId: String, val configFile: String?)
-
- // counter is used to reduce plugin id length
- var id: String = "p_${pluginIdCounter.incrementAndGet()}"
- private set
-
- private var implementationDetail = false
- private var separateJar = false
- private var name: String? = null
- private var description: String? = null
- private var packagePrefix: String? = null
- private val dependsTags = mutableListOf()
- private var applicationListeners: String? = null
- private var resourceBundleBaseName: String? = null
- private var actions: String? = null
- private val extensions = mutableListOf()
- private var extensionPoints: String? = null
- private var untilBuild: String? = null
- private var sinceBuild: String? = null
- private var version: String? = null
- private val pluginAliases = mutableListOf()
-
- private val content = mutableListOf()
- private val dependencies = mutableListOf()
- private val pluginDependencies = mutableListOf()
- private val incompatibleWith = mutableListOf()
-
- private data class SubDescriptor(val filename: String, val builder: PluginBuilder)
- private val subDescriptors = ArrayList()
-
- fun dependsIntellijModulesLang(): PluginBuilder {
- depends("com.intellij.modules.lang")
- return this
- }
-
- fun id(id: String): PluginBuilder {
- this.id = id
- return this
- }
-
- fun randomId(idPrefix: String): PluginBuilder {
- this.id = "${idPrefix}_${pluginIdCounter.incrementAndGet()}"
- return this
- }
-
- fun name(name: String): PluginBuilder {
- this.name = name
- return this
- }
-
- fun description(description: String): PluginBuilder {
- this.description = description
- return this
- }
-
- fun packagePrefix(value: String?): PluginBuilder {
- packagePrefix = value
- return this
- }
-
- fun separateJar(value: Boolean): PluginBuilder {
- separateJar = value
- return this
- }
-
- fun depends(pluginId: String, configFile: String? = null): PluginBuilder {
- dependsTags.add(DependsTag(pluginId, configFile))
- return this
- }
-
- fun depends(pluginId: String, subDescriptor: PluginBuilder, filename: String? = null): PluginBuilder {
- val fileName = filename ?: "dep_${pluginIdCounter.incrementAndGet()}.xml"
- subDescriptors.add(SubDescriptor(PluginManagerCore.META_INF + fileName, subDescriptor))
- depends(pluginId, fileName)
- return this
- }
-
- fun module(moduleName: String, moduleDescriptor: PluginBuilder, loadingRule: ModuleLoadingRule = ModuleLoadingRule.OPTIONAL,
- moduleFile: String = "$moduleName.xml"): PluginBuilder {
- subDescriptors.add(SubDescriptor(moduleFile, moduleDescriptor))
- content.add(PluginContentDescriptor.ModuleItem(name = moduleName, configFile = null, descriptorContent = null, loadingRule = loadingRule))
- return this
- }
-
- fun pluginAlias(alias: String): PluginBuilder {
- pluginAliases.add(alias)
- return this
- }
-
- fun dependency(moduleName: String): PluginBuilder {
- dependencies.add(ModuleDependencies.ModuleReference(moduleName))
- return this
- }
-
- fun pluginDependency(pluginId: String): PluginBuilder {
- pluginDependencies.add(ModuleDependencies.PluginReference(PluginId.getId(pluginId)))
- return this
- }
-
- fun incompatibleWith(pluginId: String): PluginBuilder {
- incompatibleWith.add(ModuleDependencies.PluginReference(PluginId.getId(pluginId)))
- return this
- }
-
- fun resourceBundle(resourceBundle: String?): PluginBuilder {
- resourceBundleBaseName = resourceBundle
- return this
- }
-
- fun untilBuild(buildNumber: String): PluginBuilder {
- untilBuild = buildNumber
- return this
- }
-
- fun sinceBuild(buildNumber: String): PluginBuilder {
- sinceBuild = buildNumber
- return this
- }
-
- fun version(version: String): PluginBuilder {
- this.version = version
- return this
- }
-
- fun applicationListeners(text: String): PluginBuilder {
- applicationListeners = text
- return this
- }
-
- fun actions(text: String): PluginBuilder {
- actions = text
- return this
- }
-
- fun extensions(text: String, ns: String = "com.intellij"): PluginBuilder {
- extensions.add(ExtensionBlock(ns, text))
- return this
- }
-
- fun extensionPoints(@Language("XML") text: String): PluginBuilder {
- extensionPoints = text
- return this
- }
-
- fun implementationDetail(): PluginBuilder {
- implementationDetail = true
- return this
- }
-
- fun text(requireId: Boolean = true): String {
- return buildString {
- append("")
- if (requireId) {
- append("$id")
- }
- name?.let { append("$it") }
- description?.let { append("$it") }
- for (dependsTag in dependsTags) {
- val configFile = dependsTag.configFile
- if (configFile != null) {
- append("""${dependsTag.pluginId}""")
- }
- else {
- append("${dependsTag.pluginId}")
- }
- }
- version?.let { append("$it") }
-
- if (sinceBuild != null && untilBuild != null) {
- append("""""")
- }
- else if (sinceBuild != null) {
- append("""""")
- }
- else if (untilBuild != null) {
- append("""""")
- }
-
- for (extensionBlock in extensions) {
- append("""${extensionBlock.text}""")
- }
- extensionPoints?.let { append("$it") }
- applicationListeners?.let { append("$it") }
- resourceBundleBaseName?.let { append("""$it""") }
- actions?.let { append("$it") }
-
- if (content.isNotEmpty()) {
- append("\n\n ")
- content.joinTo(this, separator = "\n ") { moduleItem ->
- val loadingAttribute = when (moduleItem.loadingRule) {
- ModuleLoadingRule.OPTIONAL -> ""
- ModuleLoadingRule.REQUIRED -> "loading=\"required\" "
- ModuleLoadingRule.EMBEDDED -> "loading=\"embedded\" "
- ModuleLoadingRule.ON_DEMAND -> "loading=\"on-demand\" "
- }
- """"""
- }
- append("\n")
- }
-
- if (incompatibleWith.isNotEmpty()) {
- incompatibleWith.joinTo(this, separator = "\n ") {
- """${it.id}"""
- }
- }
-
- if (dependencies.isNotEmpty() || pluginDependencies.isNotEmpty()) {
- append("\n\n ")
- if (dependencies.isNotEmpty()) {
- dependencies.joinTo(this, separator = "\n ") { """""" }
- }
- if (pluginDependencies.isNotEmpty()) {
- pluginDependencies.joinTo(this, separator = "\n ") { """""" }
- }
- append("\n")
- }
-
- for (alias in pluginAliases) {
- append("\n")
- append("""""")
- }
-
- append("")
- }
- }
-
- fun build(path: Path): PluginBuilder {
- val allDescriptors = collectAllSubDescriptors(subDescriptors).toList()
- if (allDescriptors.any { it.builder.separateJar }) {
- val modulesDir = path.resolve("lib/modules")
- modulesDir.createDirectories()
- buildJar(path.resolve("lib/$id.jar"))
- for ((fileName, subDescriptor) in allDescriptors) {
- if (subDescriptor.separateJar) {
- val jarPath = modulesDir.resolve("${fileName.removeSuffix(".xml")}.jar")
- subDescriptor.buildJarToStream(Files.newOutputStream(jarPath), mainDescriptorRelativePath = fileName)
- }
- }
- }
- else {
- path.resolve(PluginManagerCore.PLUGIN_XML_PATH).write(text())
- for (subDescriptor in allDescriptors) {
- path.resolve(subDescriptor.filename).createParentDirectories().write(subDescriptor.builder.text(requireId = false))
- }
- }
- return this
- }
-
- private fun collectAllSubDescriptors(descriptors: List): Sequence {
- return descriptors.asSequence().flatMap { sequenceOf(it) + collectAllSubDescriptors(it.builder.subDescriptors) }
- }
-
- fun buildJar(path: Path): PluginBuilder {
- buildJarToStream(Files.newOutputStream(path), PluginManagerCore.PLUGIN_XML_PATH)
- return this
- }
-
- private fun buildJarToStream(outputStream: OutputStream, mainDescriptorRelativePath: String) {
- Compressor.Zip(outputStream).use {
- it.addFile(mainDescriptorRelativePath, text(requireId = mainDescriptorRelativePath == PluginManagerCore.PLUGIN_XML_PATH).toByteArray())
- for ((fileName, subDescriptor) in subDescriptors) {
- if (!subDescriptor.separateJar) {
- it.addFile(fileName, subDescriptor.text(requireId = false).toByteArray())
- }
- }
- }
- }
-
- fun buildZip(path: Path): PluginBuilder {
- val jarStream = ByteArrayOutputStream()
- buildJarToStream(jarStream, PluginManagerCore.PLUGIN_XML_PATH)
-
- val pluginName = name ?: id
- Compressor.Zip(Files.newOutputStream(path)).use {
- it.addDirectory(pluginName)
- it.addDirectory("$pluginName/lib")
- it.addFile("$pluginName/lib/$pluginName.jar", jarStream.toByteArray())
- }
-
- return this
- }
-
- companion object {
- fun withModulesLang(): PluginBuilder = PluginBuilder().dependsIntellijModulesLang()
- fun empty(): PluginBuilder = PluginBuilder()
- }
-}
-
@TestOnly
fun readModuleDescriptorForTest(input: ByteArray): PluginDescriptorBuilder {
return PluginDescriptorFromXmlStreamConsumer(readContext = object : ReadModuleContext {
diff --git a/platform/platform-tests/testSrc/com/intellij/ide/plugins/PluginDependenciesTest.kt b/platform/platform-tests/testSrc/com/intellij/ide/plugins/PluginDependenciesTest.kt
index a37924e5abc7..8f2862817283 100644
--- a/platform/platform-tests/testSrc/com/intellij/ide/plugins/PluginDependenciesTest.kt
+++ b/platform/platform-tests/testSrc/com/intellij/ide/plugins/PluginDependenciesTest.kt
@@ -2,6 +2,7 @@
package com.intellij.ide.plugins
import com.intellij.ide.plugins.cl.PluginClassLoader
+import com.intellij.platform.testFramework.PluginBuilder
import com.intellij.platform.plugins.testFramework.PluginSetTestBuilder
import com.intellij.testFramework.LoggedErrorProcessor
import com.intellij.testFramework.assertions.Assertions.assertThat
diff --git a/platform/platform-tests/testSrc/com/intellij/ide/plugins/PluginDescriptorTest.kt b/platform/platform-tests/testSrc/com/intellij/ide/plugins/PluginDescriptorTest.kt
index 36bf52b3e018..b1630ef10c39 100644
--- a/platform/platform-tests/testSrc/com/intellij/ide/plugins/PluginDescriptorTest.kt
+++ b/platform/platform-tests/testSrc/com/intellij/ide/plugins/PluginDescriptorTest.kt
@@ -3,6 +3,8 @@
package com.intellij.ide.plugins
import com.intellij.openapi.diagnostic.logger
+import com.intellij.platform.testFramework.PluginBuilder
+import com.intellij.platform.testFramework.loadAndInitDescriptorInTest
import com.intellij.openapi.util.BuildNumber
import com.intellij.testFramework.PlatformTestUtil
import com.intellij.testFramework.TestDataPath
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 6becb923d0a5..825c5216eaef 100644
--- a/platform/platform-tests/testSrc/com/intellij/ide/plugins/PluginManagerTest.kt
+++ b/platform/platform-tests/testSrc/com/intellij/ide/plugins/PluginManagerTest.kt
@@ -13,6 +13,7 @@ import com.intellij.platform.plugins.parser.impl.PluginDescriptorFromXmlStreamCo
import com.intellij.platform.plugins.parser.impl.ReadModuleContext
import com.intellij.platform.plugins.parser.impl.XIncludeLoader.LoadedXIncludeReference
import com.intellij.platform.plugins.parser.impl.consume
+import com.intellij.platform.testFramework.loadAndInitDescriptorInTest
import com.intellij.testFramework.PlatformTestUtil
import com.intellij.testFramework.TestDataPath
import com.intellij.testFramework.UsefulTestCase
diff --git a/platform/platform-tests/testSrc/com/intellij/ide/plugins/PluginSetLoadingTest.kt b/platform/platform-tests/testSrc/com/intellij/ide/plugins/PluginSetLoadingTest.kt
index 7037986296b4..89004838e147 100644
--- a/platform/platform-tests/testSrc/com/intellij/ide/plugins/PluginSetLoadingTest.kt
+++ b/platform/platform-tests/testSrc/com/intellij/ide/plugins/PluginSetLoadingTest.kt
@@ -3,6 +3,7 @@ package com.intellij.ide.plugins
import com.intellij.openapi.util.BuildNumber
import com.intellij.platform.plugins.testFramework.PluginSetTestBuilder
+import com.intellij.platform.testFramework.PluginBuilder
import com.intellij.testFramework.rules.InMemoryFsRule
import com.intellij.util.io.write
import org.assertj.core.api.Assertions.assertThat
diff --git a/platform/platform-tests/testSrc/com/intellij/ide/plugins/RegistryKeyBeanPluginTest.kt b/platform/platform-tests/testSrc/com/intellij/ide/plugins/RegistryKeyBeanPluginTest.kt
index f1d1bf69ca4f..08496bb1a6c8 100644
--- a/platform/platform-tests/testSrc/com/intellij/ide/plugins/RegistryKeyBeanPluginTest.kt
+++ b/platform/platform-tests/testSrc/com/intellij/ide/plugins/RegistryKeyBeanPluginTest.kt
@@ -9,6 +9,8 @@ import com.intellij.openapi.util.Disposer
import com.intellij.openapi.util.registry.Registry
import com.intellij.openapi.util.registry.RegistryKeyBean
import com.intellij.openapi.util.registry.RegistryKeyDescriptor
+import com.intellij.platform.testFramework.PluginBuilder
+import com.intellij.platform.testFramework.loadPluginWithText
import com.intellij.testFramework.*
import com.intellij.testFramework.rules.InMemoryFsRule
import com.intellij.util.io.Ksuid
diff --git a/platform/platform-tests/testSrc/com/intellij/openapi/application/ConfigImportHelperTest.kt b/platform/platform-tests/testSrc/com/intellij/openapi/application/ConfigImportHelperTest.kt
index 07afd242160c..05f781827911 100644
--- a/platform/platform-tests/testSrc/com/intellij/openapi/application/ConfigImportHelperTest.kt
+++ b/platform/platform-tests/testSrc/com/intellij/openapi/application/ConfigImportHelperTest.kt
@@ -5,7 +5,7 @@ import com.intellij.configurationStore.getPerOsSettingsStorageFolderName
import com.intellij.diagnostic.VMOptions
import com.intellij.ide.SpecialConfigFiles
import com.intellij.ide.plugins.DisabledPluginsState.Companion.saveDisabledPluginsAndInvalidate
-import com.intellij.ide.plugins.PluginBuilder
+import com.intellij.platform.testFramework.PluginBuilder
import com.intellij.ide.plugins.PluginNode
import com.intellij.ide.plugins.marketplace.MarketplacePluginDownloadService
import com.intellij.ide.plugins.marketplace.utils.MarketplaceCustomizationService
diff --git a/platform/platform-tests/testSrc/com/intellij/openapi/vfs/local/JarFileSystemTest.java b/platform/platform-tests/testSrc/com/intellij/openapi/vfs/local/JarFileSystemTest.java
index ff5798492c5a..547300f07ec1 100644
--- a/platform/platform-tests/testSrc/com/intellij/openapi/vfs/local/JarFileSystemTest.java
+++ b/platform/platform-tests/testSrc/com/intellij/openapi/vfs/local/JarFileSystemTest.java
@@ -1,7 +1,6 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.openapi.vfs.local;
-import com.intellij.ide.plugins.DynamicPluginsTestUtil;
import com.intellij.openapi.actionSystem.ActionManager;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.application.ApplicationManager;
@@ -22,6 +21,7 @@ import com.intellij.openapi.vfs.newvfs.BulkFileListener;
import com.intellij.openapi.vfs.newvfs.VfsImplUtil;
import com.intellij.openapi.vfs.newvfs.events.VFileContentChangeEvent;
import com.intellij.openapi.vfs.newvfs.events.VFileEvent;
+import com.intellij.platform.testFramework.DynamicPluginTestUtilsKt;
import com.intellij.testFramework.EdtTestUtil;
import com.intellij.testFramework.PlatformTestUtil;
import com.intellij.testFramework.TestActionEvent;
@@ -370,7 +370,7 @@ public class JarFileSystemTest extends BareTestFixtureTestCase {
EdtTestUtil.runInEdtAndWait(() -> {
assertNotNull(LocalFileSystem.getInstance().refreshAndFindFileByNioFile(copiedJar));
- var fsRegistration = DynamicPluginsTestUtil.loadExtensionWithText(fsExtText, "com.intellij");
+ var fsRegistration = DynamicPluginTestUtilsKt.loadExtensionWithText(fsExtText, "com.intellij");
try {
Disposer.register(fsRegistration, JarFileSystemImpl::cleanupForNextTest);
diff --git a/platform/platform-tests/testSrc/com/intellij/openapi/vfs/newvfs/persistent/PersistentFsTest.java b/platform/platform-tests/testSrc/com/intellij/openapi/vfs/newvfs/persistent/PersistentFsTest.java
index cce2f2e8f70f..9d9e74713abe 100644
--- a/platform/platform-tests/testSrc/com/intellij/openapi/vfs/newvfs/persistent/PersistentFsTest.java
+++ b/platform/platform-tests/testSrc/com/intellij/openapi/vfs/newvfs/persistent/PersistentFsTest.java
@@ -2,7 +2,6 @@
package com.intellij.openapi.vfs.newvfs.persistent;
import com.intellij.CacheSwitcher;
-import com.intellij.ide.plugins.DynamicPluginsTestUtil;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.Application;
import com.intellij.openapi.application.ApplicationManager;
@@ -31,6 +30,7 @@ import com.intellij.openapi.vfs.newvfs.*;
import com.intellij.openapi.vfs.newvfs.events.*;
import com.intellij.openapi.vfs.newvfs.impl.VirtualDirectoryImpl;
import com.intellij.openapi.vfs.newvfs.impl.VirtualFileSystemEntry;
+import com.intellij.platform.testFramework.DynamicPluginTestUtilsKt;
import com.intellij.testFramework.*;
import com.intellij.testFramework.fixtures.BareTestFixtureTestCase;
import com.intellij.testFramework.rules.TempDirectory;
@@ -748,7 +748,7 @@ public class PersistentFsTest extends BareTestFixtureTestCase {
String text = "";
- Disposable disposable = runInEdtAndGet(() -> DynamicPluginsTestUtil.loadExtensionWithText(text, "com.intellij"));
+ Disposable disposable = runInEdtAndGet(() -> DynamicPluginTestUtilsKt.loadExtensionWithText(text, "com.intellij"));
try {
File generationDir = tempDirectory.newDirectory("gen");
@@ -795,7 +795,7 @@ public class PersistentFsTest extends BareTestFixtureTestCase {
String text = "";
- Disposable disposable = runInEdtAndGet(() -> DynamicPluginsTestUtil.loadExtensionWithText(text, "com.intellij"));
+ Disposable disposable = runInEdtAndGet(() -> DynamicPluginTestUtilsKt.loadExtensionWithText(text, "com.intellij"));
try {
File generationDir = tempDirectory.newDirectory("gen");
diff --git a/platform/platform-tests/testSrc/com/intellij/ide/plugins/DynamicPluginsTestUtil.kt b/platform/testFramework/src/com/intellij/platform/testFramework/DynamicPluginTestUtils.kt
similarity index 81%
rename from platform/platform-tests/testSrc/com/intellij/ide/plugins/DynamicPluginsTestUtil.kt
rename to platform/testFramework/src/com/intellij/platform/testFramework/DynamicPluginTestUtils.kt
index 9153dd80bbf9..4cd3919d70be 100644
--- a/platform/platform-tests/testSrc/com/intellij/ide/plugins/DynamicPluginsTestUtil.kt
+++ b/platform/testFramework/src/com/intellij/platform/testFramework/DynamicPluginTestUtils.kt
@@ -1,19 +1,23 @@
-// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
-@file:JvmName("DynamicPluginsTestUtil")
-@file:Suppress("UsePropertyAccessSyntax")
-package com.intellij.ide.plugins
+// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+package com.intellij.platform.testFramework
+import com.intellij.ide.plugins.DynamicPlugins
+import com.intellij.ide.plugins.IdeaPluginDescriptorImpl
+import com.intellij.ide.plugins.PluginDescriptorLoadingContext
+import com.intellij.ide.plugins.PluginInitializationContext
+import com.intellij.ide.plugins.PluginManagerCore
+import com.intellij.ide.plugins.loadDescriptorFromFileOrDirInTests
import com.intellij.openapi.Disposable
import com.intellij.openapi.extensions.PluginId
import com.intellij.openapi.util.BuildNumber
import com.intellij.openapi.util.io.FileUtil
import com.intellij.testFramework.IndexingTestUtil
-import com.intellij.testFramework.assertions.Assertions.assertThat
+import org.assertj.core.api.Assertions.assertThat
import java.nio.file.Files
import java.nio.file.Path
@JvmOverloads
-internal fun loadAndInitDescriptorInTest(
+fun loadAndInitDescriptorInTest(
dir: Path,
isBundled: Boolean = false,
disabledPlugins: Set = emptySet(),
@@ -59,7 +63,7 @@ fun loadExtensionWithText(extensionTag: String, ns: String = "com.intellij"): Di
}
}
-internal fun loadPluginWithText(
+fun loadPluginWithText(
pluginBuilder: PluginBuilder,
path: Path,
disabledPlugins: Set = emptySet(),
@@ -86,7 +90,7 @@ internal fun loadPluginWithText(
}
}
-internal fun loadAndInitDescriptorInTest(
+fun loadAndInitDescriptorInTest(
pluginBuilder: PluginBuilder,
rootPath: Path,
disabledPlugins: Set = emptySet(),
@@ -106,7 +110,7 @@ internal fun loadAndInitDescriptorInTest(
)
}
-internal fun setPluginClassLoaderForMainAndSubPlugins(rootDescriptor: IdeaPluginDescriptorImpl, classLoader: ClassLoader?) {
+fun setPluginClassLoaderForMainAndSubPlugins(rootDescriptor: IdeaPluginDescriptorImpl, classLoader: ClassLoader?) {
rootDescriptor.pluginClassLoader = classLoader
for (dependency in rootDescriptor.dependencies) {
dependency.subDescriptor?.let {
@@ -115,7 +119,7 @@ internal fun setPluginClassLoaderForMainAndSubPlugins(rootDescriptor: IdeaPlugin
}
}
-internal fun unloadAndUninstallPlugin(descriptor: IdeaPluginDescriptorImpl): Boolean {
+fun unloadAndUninstallPlugin(descriptor: IdeaPluginDescriptorImpl): Boolean {
return DynamicPlugins.unloadPlugin(
descriptor,
DynamicPlugins.UnloadPluginOptions(disable = false),
diff --git a/platform/testFramework/src/com/intellij/platform/testFramework/PluginBuilder.kt b/platform/testFramework/src/com/intellij/platform/testFramework/PluginBuilder.kt
new file mode 100644
index 000000000000..7dcc36a7d923
--- /dev/null
+++ b/platform/testFramework/src/com/intellij/platform/testFramework/PluginBuilder.kt
@@ -0,0 +1,332 @@
+// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+package com.intellij.platform.testFramework
+
+import com.intellij.ide.plugins.ModuleDependencies
+import com.intellij.ide.plugins.ModuleLoadingRule
+import com.intellij.ide.plugins.PluginContentDescriptor
+import com.intellij.ide.plugins.PluginManagerCore
+import com.intellij.openapi.extensions.PluginId
+import com.intellij.util.io.Compressor
+import com.intellij.util.io.createParentDirectories
+import com.intellij.util.io.write
+import org.intellij.lang.annotations.Language
+import java.io.ByteArrayOutputStream
+import java.io.OutputStream
+import java.nio.file.Files
+import java.nio.file.Path
+import java.util.concurrent.atomic.AtomicInteger
+import kotlin.io.path.createDirectories
+
+
+/** Constants from [com.intellij.platform.plugins.parser.impl.PluginXmlConst]
+ * We can't access PluginXmlConst directly because it's in an implementation module */
+private object PluginBuilderConsts {
+ const val PLUGIN_IMPLEMENTATION_DETAIL_ATTR: String = "implementation-detail"
+ const val PLUGIN_PACKAGE_ATTR: String = "package"
+}
+
+
+private val pluginIdCounter = AtomicInteger()
+
+class PluginBuilder private constructor() {
+ private data class ExtensionBlock(val ns: String, val text: String)
+ private data class DependsTag(val pluginId: String, val configFile: String?)
+
+ // counter is used to reduce plugin id length
+ var id: String = "p_${pluginIdCounter.incrementAndGet()}"
+ private set
+
+ private var implementationDetail = false
+ private var separateJar = false
+ private var name: String? = null
+ private var description: String? = null
+ private var packagePrefix: String? = null
+ private val dependsTags = mutableListOf()
+ private var applicationListeners: String? = null
+ private var resourceBundleBaseName: String? = null
+ private var actions: String? = null
+ private val extensions = mutableListOf()
+ private var extensionPoints: String? = null
+ private var untilBuild: String? = null
+ private var sinceBuild: String? = null
+ private var version: String? = null
+ private val pluginAliases = mutableListOf()
+
+ private val content = mutableListOf()
+ private val dependencies = mutableListOf()
+ private val pluginDependencies = mutableListOf()
+ private val incompatibleWith = mutableListOf()
+
+ private data class SubDescriptor(val filename: String, val builder: PluginBuilder)
+
+ private val subDescriptors = ArrayList()
+
+ fun dependsIntellijModulesLang(): PluginBuilder {
+ depends("com.intellij.modules.lang")
+ return this
+ }
+
+ fun id(id: String): PluginBuilder {
+ this.id = id
+ return this
+ }
+
+ fun randomId(idPrefix: String): PluginBuilder {
+ this.id = "${idPrefix}_${pluginIdCounter.incrementAndGet()}"
+ return this
+ }
+
+ fun name(name: String): PluginBuilder {
+ this.name = name
+ return this
+ }
+
+ fun description(description: String): PluginBuilder {
+ this.description = description
+ return this
+ }
+
+ fun packagePrefix(value: String?): PluginBuilder {
+ packagePrefix = value
+ return this
+ }
+
+ fun separateJar(value: Boolean): PluginBuilder {
+ separateJar = value
+ return this
+ }
+
+ fun depends(pluginId: String, configFile: String? = null): PluginBuilder {
+ dependsTags.add(DependsTag(pluginId, configFile))
+ return this
+ }
+
+ fun depends(pluginId: String, subDescriptor: PluginBuilder, filename: String? = null): PluginBuilder {
+ val fileName = filename ?: "dep_${pluginIdCounter.incrementAndGet()}.xml"
+ subDescriptors.add(SubDescriptor(PluginManagerCore.META_INF + fileName, subDescriptor))
+ depends(pluginId, fileName)
+ return this
+ }
+
+ fun module(
+ moduleName: String, moduleDescriptor: PluginBuilder, loadingRule: ModuleLoadingRule = ModuleLoadingRule.OPTIONAL,
+ moduleFile: String = "$moduleName.xml",
+ ): PluginBuilder {
+ subDescriptors.add(SubDescriptor(moduleFile, moduleDescriptor))
+ content.add(PluginContentDescriptor.ModuleItem(name = moduleName, configFile = null, descriptorContent = null, loadingRule = loadingRule))
+ return this
+ }
+
+ fun pluginAlias(alias: String): PluginBuilder {
+ pluginAliases.add(alias)
+ return this
+ }
+
+ fun dependency(moduleName: String): PluginBuilder {
+ dependencies.add(ModuleDependencies.ModuleReference(moduleName))
+ return this
+ }
+
+ fun pluginDependency(pluginId: String): PluginBuilder {
+ pluginDependencies.add(ModuleDependencies.PluginReference(PluginId.getId(pluginId)))
+ return this
+ }
+
+ fun incompatibleWith(pluginId: String): PluginBuilder {
+ incompatibleWith.add(ModuleDependencies.PluginReference(PluginId.getId(pluginId)))
+ return this
+ }
+
+ fun resourceBundle(resourceBundle: String?): PluginBuilder {
+ resourceBundleBaseName = resourceBundle
+ return this
+ }
+
+ fun untilBuild(buildNumber: String): PluginBuilder {
+ untilBuild = buildNumber
+ return this
+ }
+
+ fun sinceBuild(buildNumber: String): PluginBuilder {
+ sinceBuild = buildNumber
+ return this
+ }
+
+ fun version(version: String): PluginBuilder {
+ this.version = version
+ return this
+ }
+
+ fun applicationListeners(text: String): PluginBuilder {
+ applicationListeners = text
+ return this
+ }
+
+ fun actions(text: String): PluginBuilder {
+ actions = text
+ return this
+ }
+
+ fun extensions(text: String, ns: String = "com.intellij"): PluginBuilder {
+ extensions.add(ExtensionBlock(ns, text))
+ return this
+ }
+
+ fun extensionPoints(@Language("XML") text: String): PluginBuilder {
+ extensionPoints = text
+ return this
+ }
+
+ fun implementationDetail(): PluginBuilder {
+ implementationDetail = true
+ return this
+ }
+
+ fun text(requireId: Boolean = true): String {
+ return buildString {
+ append("")
+ if (requireId) {
+ append("$id")
+ }
+ name?.let { append("$it") }
+ description?.let { append("$it") }
+ for (dependsTag in dependsTags) {
+ val configFile = dependsTag.configFile
+ if (configFile != null) {
+ append("""${dependsTag.pluginId}""")
+ }
+ else {
+ append("${dependsTag.pluginId}")
+ }
+ }
+ version?.let { append("$it") }
+
+ if (sinceBuild != null && untilBuild != null) {
+ append("""""")
+ }
+ else if (sinceBuild != null) {
+ append("""""")
+ }
+ else if (untilBuild != null) {
+ append("""""")
+ }
+
+ for (extensionBlock in extensions) {
+ append("""${extensionBlock.text}""")
+ }
+ extensionPoints?.let { append("$it") }
+ applicationListeners?.let { append("$it") }
+ resourceBundleBaseName?.let { append("""$it""") }
+ actions?.let { append("$it") }
+
+ if (content.isNotEmpty()) {
+ append("\n\n ")
+ content.joinTo(this, separator = "\n ") { moduleItem ->
+ val loadingAttribute = when (moduleItem.loadingRule) {
+ ModuleLoadingRule.OPTIONAL -> ""
+ ModuleLoadingRule.REQUIRED -> "loading=\"required\" "
+ ModuleLoadingRule.EMBEDDED -> "loading=\"embedded\" "
+ ModuleLoadingRule.ON_DEMAND -> "loading=\"on-demand\" "
+ }
+ """"""
+ }
+ append("\n")
+ }
+
+ if (incompatibleWith.isNotEmpty()) {
+ incompatibleWith.joinTo(this, separator = "\n ") {
+ """${it.id}"""
+ }
+ }
+
+ if (dependencies.isNotEmpty() || pluginDependencies.isNotEmpty()) {
+ append("\n\n ")
+ if (dependencies.isNotEmpty()) {
+ dependencies.joinTo(this, separator = "\n ") { """""" }
+ }
+ if (pluginDependencies.isNotEmpty()) {
+ pluginDependencies.joinTo(this, separator = "\n ") { """""" }
+ }
+ append("\n")
+ }
+
+ for (alias in pluginAliases) {
+ append("\n")
+ append("""""")
+ }
+
+ append("")
+ }
+ }
+
+ fun build(path: Path): PluginBuilder {
+ val allDescriptors = collectAllSubDescriptors(subDescriptors).toList()
+ if (allDescriptors.any { it.builder.separateJar }) {
+ val modulesDir = path.resolve("lib/modules")
+ modulesDir.createDirectories()
+ buildJar(path.resolve("lib/$id.jar"))
+ for ((fileName, subDescriptor) in allDescriptors) {
+ if (subDescriptor.separateJar) {
+ val jarPath = modulesDir.resolve("${fileName.removeSuffix(".xml")}.jar")
+ subDescriptor.buildJarToStream(Files.newOutputStream(jarPath), mainDescriptorRelativePath = fileName)
+ }
+ }
+ }
+ else {
+ path.resolve(PluginManagerCore.PLUGIN_XML_PATH).write(text())
+ for (subDescriptor in allDescriptors) {
+ path.resolve(subDescriptor.filename).createParentDirectories().write(subDescriptor.builder.text(requireId = false))
+ }
+ }
+ return this
+ }
+
+ private fun collectAllSubDescriptors(descriptors: List): Sequence {
+ return descriptors.asSequence().flatMap { sequenceOf(it) + collectAllSubDescriptors(it.builder.subDescriptors) }
+ }
+
+ fun buildJar(path: Path): PluginBuilder {
+ buildJarToStream(Files.newOutputStream(path), PluginManagerCore.PLUGIN_XML_PATH)
+ return this
+ }
+
+ private fun buildJarToStream(outputStream: OutputStream, mainDescriptorRelativePath: String) {
+ Compressor.Zip(outputStream).use {
+ it.addFile(mainDescriptorRelativePath, text(requireId = mainDescriptorRelativePath == PluginManagerCore.PLUGIN_XML_PATH).toByteArray())
+ for ((fileName, subDescriptor) in subDescriptors) {
+ if (!subDescriptor.separateJar) {
+ it.addFile(fileName, subDescriptor.text(requireId = false).toByteArray())
+ }
+ }
+ }
+ }
+
+ fun buildZip(path: Path): PluginBuilder {
+ val jarStream = ByteArrayOutputStream()
+ buildJarToStream(jarStream, PluginManagerCore.PLUGIN_XML_PATH)
+
+ val pluginName = name ?: id
+ Compressor.Zip(Files.newOutputStream(path)).use {
+ it.addDirectory(pluginName)
+ it.addDirectory("$pluginName/lib")
+ it.addFile("$pluginName/lib/$pluginName.jar", jarStream.toByteArray())
+ }
+
+ return this
+ }
+
+ companion object {
+ fun withModulesLang(): PluginBuilder = PluginBuilder().dependsIntellijModulesLang()
+ fun empty(): PluginBuilder = PluginBuilder()
+ }
+}
\ No newline at end of file