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