IDEA-273306 building enabled plugin and module maps — unify implementation

GitOrigin-RevId: fd68078ffa6f48c859c5f4787cbf496012a59d29
This commit is contained in:
Vladimir Krivosheev
2021-07-07 19:56:48 +02:00
committed by intellij-monorepo-bot
parent 1e6d1496f7
commit 4d37e309bf
29 changed files with 526 additions and 571 deletions

View File

@@ -1,4 +1,4 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
// 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.framework.detection;
import com.intellij.facet.Facet;
@@ -54,7 +54,7 @@ public class FrameworkDetectionTest extends FrameworkDetectionTestCase {
Disposer.register(getTestRootDisposable(), DynamicPluginsTestUtil.loadExtensionWithText(
"<framework.detector implementation=\"" + MockFacetDetector.class.getName() + "\"/>",
MockFacetDetector.class.getClassLoader()));
"com.intellij"));
assertFrameworkDetectedIn(file);
}

View File

@@ -1,4 +1,4 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
// 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.framework.detection;
import com.intellij.facet.FacetTestCase;
@@ -21,11 +21,11 @@ public abstract class FrameworkDetectionTestCase extends FacetTestCase {
//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(
"<framework.detector implementation=\"" + MockFacetDetector.class.getName() + "\"/>",
MockFacetDetector.class.getClassLoader()));
"com.intellij"));
Disposer.register(getTestRootDisposable(), DynamicPluginsTestUtil.loadExtensionWithText(
"<framework.detector implementation=\"" + MockSubFacetDetector.class.getName() + "\"/>",
MockSubFacetDetector.class.getClassLoader()));
"com.intellij"));
}
FrameworkDetectionManager.getInstance(myProject).doInitialize();
}

View File

@@ -24,8 +24,8 @@ private const val FILE_NAME = "FileIdentifiableByText"
@SkipSlowTestLocally
class FileTypeIndexConsistencyTest : LightJavaCodeInsightFixtureTestCase() {
fun testFuzzActions() {
Disposer.register(testRootDisposable, loadExtensionWithText("<fileTypeDetector implementation=\"${MyFileTypeDetector::class.java.name}\"/>",
MyFileTypeDetector::class.java.classLoader))
Disposer.register(testRootDisposable, loadExtensionWithText(
"<fileTypeDetector implementation=\"${MyFileTypeDetector::class.java.name}\"/>"))
val genAction: Generator<PsiIndexConsistencyTester.Action> = Generator.from { data ->
MyTextChange(

View File

@@ -1,4 +1,4 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
// 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
@@ -10,7 +10,6 @@ import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.LanguageSubstitutor
import com.intellij.psi.PsiManager
import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase
import com.intellij.util.SystemProperties
import org.assertj.core.api.Assertions.assertThat
class LanguageSubstitutorLoadUnloadTest : LightJavaCodeInsightFixtureTestCase() {
@@ -24,7 +23,7 @@ class LanguageSubstitutorLoadUnloadTest : LightJavaCodeInsightFixtureTestCase()
val virtualFile = beforeLoading.virtualFile
val text = "<lang.substitutor language=\"TEXT\" implementationClass=\"${TextToJavaSubstitutor::class.java.name}\"/>"
loadExtensionWithText(text, javaClass.classLoader).use {
loadExtensionWithText(text).use {
val afterLoading = PsiManager.getInstance(myFixture.project).findFile(virtualFile)
assertThat(afterLoading!!.language).isInstanceOf(JavaLanguage::class.java)
}

View File

@@ -1,4 +1,4 @@
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
// 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.scopes;
import com.intellij.JavaTestUtil;
@@ -45,7 +45,7 @@ public class LibraryUseSearchUsingScopeEnlargerTest extends JavaCodeInsightFixtu
super.setUp();
Disposer.register(getTestRootDisposable(), DynamicPluginsTestUtil.loadExtensionWithText(
"<useScopeEnlarger implementation=\"com.intellij.scopes.LibraryUseSearchUsingScopeEnlargerTest$LibraryUseScopeEnlarger\"/>",
getClass().getClassLoader()));
"com.intellij"));
//bug? Test seems to use stale data.
LocalFileSystem.getInstance().refreshIoFiles(Collections.singleton(new File(getTestDataPath())), false, true, null);
}

View File

@@ -1,4 +1,4 @@
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
// 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
@@ -38,7 +38,7 @@ class BrokenPluginIndexingTest : JavaCodeInsightFixtureTestCase() {
}
val text = "<fileBasedIndex implementation=\"" + BrokenFileBasedIndexExtension::class.qualifiedName + "\"/>"
Disposer.register(testRootDisposable, loadExtensionWithText(text, BrokenFileBasedIndexExtension::class.java.classLoader))
Disposer.register(testRootDisposable, loadExtensionWithText(text))
val file = myFixture.addClass("class Some {}").containingFile.virtualFile
FileBasedIndex.getInstance().getFileData(BrokenFileBasedIndexExtension.INDEX_ID, file, project)

View File

@@ -1,4 +1,4 @@
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
// 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
@@ -16,7 +16,7 @@ import kotlin.streams.toList
class IndexInfrastructureExtensionTest : LightJavaCodeInsightFixtureTestCase() {
fun `test infrastructure extension drops all indexes when it requires invalidation`() {
val text = "<fileBasedIndexInfrastructureExtension implementation=\"" + TestIndexInfrastructureExtension::class.java.name + "\"/>"
Disposer.register(testRootDisposable, loadExtensionWithText(text, TestIndexInfrastructureExtension::class.java.classLoader))
Disposer.register(testRootDisposable, loadExtensionWithText(text))
val before = Files.list(PathManager.getIndexRoot()).use {
it.toList().associate { p -> p.fileName.toString() to p.lastModified().toMillis() }.toSortedMap()

View File

@@ -47,7 +47,7 @@ class MultiProjectIndexTest {
@Test
fun `test index extension process files intersection`() {
val text = "<fileBasedIndexInfrastructureExtension implementation=\"" + CountingTestExtension::class.java.name + "\"/>"
Disposer.register(disposable.disposable, loadExtensionWithText(text, CountingTestExtension::class.java.classLoader))
Disposer.register(disposable.disposable, loadExtensionWithText(text))
val ext = FileBasedIndexInfrastructureExtension.EP_NAME.findExtension(CountingTestExtension::class.java)!!
val projectPath1 = tempDir.newDirectory("project1").toPath()

View File

@@ -1,70 +1,93 @@
// 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.ide.plugins
import com.intellij.openapi.extensions.PluginId
import com.intellij.util.graph.DFSTBuilder
import com.intellij.util.graph.GraphGenerator
import com.intellij.util.graph.InboundSemiGraph
import com.intellij.util.graph.Graph
import com.intellij.util.lang.Java11Shim
import org.jetbrains.annotations.ApiStatus
import java.util.*
import java.util.function.Supplier
import java.util.stream.Stream
internal class CachingSemiGraph<Node>(private val nodes: Collection<Node>,
private val pluginToDirectDependencies: Map<Node, List<Node>>) : InboundSemiGraph<Node> {
@ApiStatus.Internal
class CachingSemiGraph<Node>(private val nodes: Collection<Node>,
private val pluginToDirectDependencies: Map<Node, List<Node>>) : Graph<Node> {
private val outs = IdentityHashMap<Node, MutableList<Node>>()
init {
buildOuts()
}
private fun buildOuts() {
val edges = Collections.newSetFromMap<Map.Entry<Node, Node>>(HashMap())
for (node in nodes) {
for (inNode in (pluginToDirectDependencies.get(node) ?: continue)) {
if (edges.add(AbstractMap.SimpleImmutableEntry(inNode, node))) {
// not a duplicate edge
outs.computeIfAbsent(inNode) { ArrayList() }.add(node)
}
}
}
}
override fun getOut(n: Node): Iterator<Node> = outs.get(n)?.iterator() ?: Collections.emptyIterator()
override fun getNodes() = nodes
override fun getIn(node: Node): Iterator<Node> {
return pluginToDirectDependencies[node]?.iterator()
?: Collections.emptyIterator()
return pluginToDirectDependencies.get(node)?.iterator() ?: Collections.emptyIterator()
}
fun getInStream(node: Node): Stream<Node> {
return pluginToDirectDependencies[node]?.stream()
?: Stream.empty()
return pluginToDirectDependencies.get(node)?.stream() ?: Stream.empty()
}
}
fun getTopologicallySorted(descriptors: Collection<IdeaPluginDescriptorImpl>,
pluginSet: PluginSet,
withOptional: Boolean): List<IdeaPluginDescriptorImpl> {
val graph = createPluginIdGraph(descriptors = descriptors,
pluginSet = pluginSet,
withOptional = withOptional)
val requiredOnlyGraph = DFSTBuilder(GraphGenerator.generate(graph))
val sortedRequired = ArrayList(graph.nodes)
val graph = createPluginIdGraph(descriptors = descriptors, pluginSet = pluginSet, withOptional = withOptional)
val requiredOnlyGraph = DFSTBuilder(graph)
val comparator = requiredOnlyGraph.comparator()
// there is circular reference between core and implementation-detail plugin, as not all such plugins extracted from core,
// so, ensure that core plugin is always first (otherwise not possible to register actions - parent group not defined)
// don't use sortWith here - avoid loading kotlin stdlib
Collections.sort(sortedRequired, Comparator { o1, o2 ->
val sortedRequired = descriptors.toTypedArray()
Arrays.sort(sortedRequired, Comparator { o1, o2 ->
when (PluginManagerCore.CORE_ID) {
o1.pluginId -> -1
o2.pluginId -> 1
else -> comparator.compare(o1, o2)
o1.id -> -1
o2.id -> 1
else -> comparator.compare(o1.id, o2.id)
}
})
return sortedRequired
@Suppress("ReplaceJavaStaticMethodWithKotlinAnalog", "UNCHECKED_CAST")
return Java11Shim.INSTANCE.listOf(sortedRequired)
}
internal fun createPluginIdGraph(descriptors: Collection<IdeaPluginDescriptorImpl>,
pluginSet: PluginSet,
withOptional: Boolean): CachingSemiGraph<IdeaPluginDescriptorImpl> {
@ApiStatus.Internal
fun createPluginIdGraph(descriptors: Collection<IdeaPluginDescriptorImpl>,
pluginSet: PluginSet,
withOptional: Boolean): CachingSemiGraph<PluginId> {
val hasAllModules = pluginSet.isPluginEnabled(PluginManagerCore.ALL_MODULES_MARKER)
val javaDep = Supplier {
pluginSet.findEnabledPlugin(PluginManagerCore.JAVA_MODULE_ID)
}
val uniqueCheck = HashSet<IdeaPluginDescriptorImpl>()
val pluginToDirectDependencies = HashMap<IdeaPluginDescriptorImpl, List<IdeaPluginDescriptorImpl>>(descriptors.size)
val list = ArrayList<IdeaPluginDescriptorImpl>(32)
val uniqueCheck = Collections.newSetFromMap<PluginId>(IdentityHashMap())
val pluginToDirectDependencies = IdentityHashMap<PluginId, List<PluginId>>(descriptors.size)
val list = ArrayList<PluginId>(32)
val ids = arrayOfNulls<PluginId>(descriptors.size)
var index = 0
for (descriptor in descriptors) {
ids[index++] = descriptor.id
collectDirectDependencies(descriptor, pluginSet, withOptional, hasAllModules, javaDep, uniqueCheck, list)
if (!list.isEmpty()) {
pluginToDirectDependencies.put(descriptor, Java11Shim.INSTANCE.copyOf(list))
pluginToDirectDependencies.put(descriptor.id, Java11Shim.INSTANCE.copyOf(list))
list.clear()
}
}
return CachingSemiGraph(descriptors, pluginToDirectDependencies)
return CachingSemiGraph(Java11Shim.INSTANCE.listOf<PluginId>(ids), pluginToDirectDependencies)
}
private fun collectDirectDependencies(rootDescriptor: IdeaPluginDescriptorImpl,
@@ -72,8 +95,8 @@ private fun collectDirectDependencies(rootDescriptor: IdeaPluginDescriptorImpl,
withOptional: Boolean,
hasAllModules: Boolean,
javaDep: Supplier<IdeaPluginDescriptorImpl?>,
uniqueCheck: MutableSet<IdeaPluginDescriptorImpl>,
result: MutableList<IdeaPluginDescriptorImpl>) {
uniqueCheck: MutableSet<PluginId>,
result: MutableList<PluginId>) {
val implicitDep = if (hasAllModules) PluginManagerCore.getImplicitDependency(rootDescriptor, javaDep) else null
uniqueCheck.clear()
if (implicitDep != null) {
@@ -81,10 +104,11 @@ private fun collectDirectDependencies(rootDescriptor: IdeaPluginDescriptorImpl,
PluginManagerCore.getLogger().error("Plugin $rootDescriptor depends on self")
}
else {
uniqueCheck.add(implicitDep)
result.add(implicitDep)
uniqueCheck.add(implicitDep.id)
result.add(implicitDep.id)
}
}
for (dependency in rootDescriptor.pluginDependencies) {
if (!withOptional && dependency.isOptional) {
continue
@@ -103,8 +127,8 @@ private fun collectDirectDependencies(rootDescriptor: IdeaPluginDescriptorImpl,
PluginManagerCore.getLogger().error("Plugin $rootDescriptor depends on self")
}
}
else if (uniqueCheck.add(dep)) {
result.add(dep)
else if (uniqueCheck.add(dep.id)) {
result.add(dep.id)
}
}
@@ -118,19 +142,19 @@ private fun collectDirectDependencies(rootDescriptor: IdeaPluginDescriptorImpl,
}
for (moduleId in rootDescriptor.incompatibilities) {
val dep = pluginSet.findEnabledPlugin(moduleId)
if (dep != null && uniqueCheck.add(dep)) {
result.add(dep)
if (dep != null && uniqueCheck.add(dep.id)) {
result.add(dep.id)
}
}
}
private fun directDependenciesOfModule(module: IdeaPluginDescriptorImpl,
pluginSet: PluginSet,
uniqueCheck: MutableSet<IdeaPluginDescriptorImpl>,
result: MutableList<IdeaPluginDescriptorImpl>) {
uniqueCheck: MutableSet<PluginId>,
result: MutableList<PluginId>) {
processDirectDependencies(module, pluginSet) {
if (uniqueCheck.add(it)) {
result.add(it)
if (uniqueCheck.add(it.id)) {
result.add(it.id)
}
}
}

View File

@@ -19,7 +19,7 @@ import java.util.function.BiPredicate
import java.util.function.Function
private val DEFAULT_CLASSLOADER_CONFIGURATION = UrlClassLoader.build().useCache()
private val EMPTY_DESCRIPTOR_ARRAY = arrayOfNulls<IdeaPluginDescriptorImpl>(0)
private val EMPTY_DESCRIPTOR_ARRAY = emptyArray<IdeaPluginDescriptorImpl>()
@ApiStatus.Internal
class ClassLoaderConfigurator(
@@ -31,8 +31,7 @@ class ClassLoaderConfigurator(
// temporary set to produce arrays (avoid allocation for each plugin)
// set to remove duplicated classloaders
private val loaders = LinkedHashSet<ClassLoader>()
private val dependencies = ArrayList<IdeaPluginDescriptorImpl>()
private val dependencies = LinkedHashSet<IdeaPluginDescriptorImpl>()
private val hasAllModules = pluginSet.isPluginEnabled(PluginManagerCore.ALL_MODULES_MARKER)
@@ -71,17 +70,17 @@ class ClassLoaderConfigurator(
}
}
else {
mainDependentClassLoader.attachParent(dependencyPlugin.classLoader!!)
mainDependentClassLoader.attachParent(dependencyPlugin)
for (module in modules) {
module.classLoader = mainDependentClassLoader
}
}
}
loaders.clear()
dependencies.clear()
}
fun configureAll() {
val postTasks = mutableListOf<() -> Unit>()
val postTasks = ArrayList<() -> Unit>()
for (plugin in pluginSet.enabledPlugins) {
// not only for core plugin, but also for a plugin from classpath (run TraverseUi)
if (plugin.pluginId == PluginManagerCore.CORE_ID || (plugin.isUseCoreClassLoader && !plugin.content.modules.isEmpty())) {
@@ -109,7 +108,7 @@ class ClassLoaderConfigurator(
return
}
loaders.clear()
dependencies.clear()
// first, set class loader for main descriptor
if (hasAllModules) {
@@ -120,7 +119,12 @@ class ClassLoaderConfigurator(
}
javaDep!!.orElse(null)
}
implicitDependency?.let { addLoaderOrLogError(plugin, it, loaders) }
implicitDependency?.let {
if (it.classLoader !== coreLoader) {
dependencies.add(it)
}
Unit
}
}
var files = plugin.jarFiles
@@ -142,7 +146,7 @@ class ClassLoaderConfigurator(
addContentModulesIfNeeded(dependency)
}
// must be after adding implicit module class loaders
loaders.add(loader)
dependencies.add(p)
}
dependency.subDescriptor?.let {
@@ -155,9 +159,8 @@ class ClassLoaderConfigurator(
// new format
processDirectDependencies(plugin, pluginSet) {
val classLoader = it.classLoader!!
if (classLoader !== coreLoader) {
loaders.add(classLoader)
if (it.classLoader !== coreLoader) {
dependencies.add(it)
}
}
@@ -193,28 +196,28 @@ class ClassLoaderConfigurator(
}
// reset to ensure that stalled data will be not reused somehow later
loaders.clear()
dependencies.clear()
}
private fun addContentModulesIfNeeded(dependency: PluginDependency) {
when (dependency.pluginId.idString) {
"Docker" -> {
pluginSet.findEnabledModule("intellij.clouds.docker.file")?.classLoader?.let {
loaders.add(it)
pluginSet.findEnabledModule("intellij.clouds.docker.file")?.let {
dependencies.add(it)
}
pluginSet.findEnabledModule("intellij.clouds.docker.remoteRun")?.classLoader?.let {
loaders.add(it)
pluginSet.findEnabledModule("intellij.clouds.docker.remoteRun")?.let {
dependencies.add(it)
}
}
"com.intellij.diagram" -> {
// https://youtrack.jetbrains.com/issue/IDEA-266323
pluginSet.findEnabledModule("intellij.diagram.java")?.classLoader?.let {
loaders.add(it)
pluginSet.findEnabledModule("intellij.diagram.java")?.let {
dependencies.add(it)
}
}
"com.intellij.modules.clion" -> {
pluginSet.findEnabledModule("intellij.profiler.clion")?.classLoader?.let {
loaders.add(it)
pluginSet.findEnabledModule("intellij.profiler.clion")?.let {
dependencies.add(it)
}
}
}
@@ -290,14 +293,14 @@ class ClassLoaderConfigurator(
files: List<Path>,
libDirectories: MutableList<String>,
classPath: ClassPath): PluginClassLoader {
val parentLoaders = if (loaders.isEmpty()) {
PluginClassLoader.EMPTY_CLASS_LOADER_ARRAY
val parents: Array<IdeaPluginDescriptorImpl> = if (dependencies.isEmpty()) {
EMPTY_DESCRIPTOR_ARRAY
}
else {
loaders.toArray(arrayOfNulls(loaders.size))
dependencies.toArray(arrayOfNulls(dependencies.size))
}
return createPluginClassLoader(parentLoaders = parentLoaders,
return createPluginClassLoader(parents = parents,
descriptor = descriptor,
files = files,
coreLoader = coreLoader,
@@ -322,11 +325,7 @@ class ClassLoaderConfigurator(
for (item in module.dependencies.modules) {
// Module dependency is always optional. If the module depends on an unavailable plugin, it will not be loaded.
val descriptor = (pluginSet.findEnabledModule(item.name) ?: return)
val classLoader = descriptor.classLoader
if (classLoader !== coreLoader) {
dependencies.add(descriptor)
}
dependencies.add(pluginSet.findEnabledModule(item.name) ?: return)
}
for (item in module.dependencies.plugins) {
val descriptor = pluginSet.findEnabledPlugin(item.id) ?: return
@@ -352,7 +351,6 @@ class ClassLoaderConfigurator(
module.classLoader = PluginClassLoader(
files,
classPath,
null,
array,
module,
coreLoader,
@@ -361,18 +359,6 @@ class ClassLoaderConfigurator(
)
}
private fun addLoaderOrLogError(dependent: IdeaPluginDescriptorImpl,
dependency: IdeaPluginDescriptorImpl,
loaders: MutableCollection<ClassLoader>) {
val loader = dependency.classLoader
if (loader == null) {
log.error(PluginLoadingError.formatErrorMessage(dependent, "requires missing class loader for '${dependency.name}'"))
}
else if (loader !== coreLoader) {
loaders.add(loader)
}
}
private fun setPluginClassLoaderForMainAndSubPlugins(rootDescriptor: IdeaPluginDescriptorImpl, classLoader: ClassLoader?) {
rootDescriptor.classLoader = classLoader
for (dependency in rootDescriptor.pluginDependencies) {
@@ -400,7 +386,7 @@ private val log: Logger
get() = Logger.getInstance("#com.intellij.ide.plugins.PluginManager")
// static to ensure that anonymous classes will not hold ClassLoaderConfigurator
private fun createPluginClassLoader(parentLoaders: Array<ClassLoader>,
private fun createPluginClassLoader(parents: Array<IdeaPluginDescriptorImpl>,
descriptor: IdeaPluginDescriptorImpl,
files: List<Path>,
libDirectories: MutableList<String>,
@@ -412,7 +398,7 @@ private fun createPluginClassLoader(parentLoaders: Array<ClassLoader>,
when (descriptor.id.idString) {
"com.intellij.diagram" -> {
// multiple packages - intellij.diagram and intellij.diagram.impl modules
return createPluginClassLoaderWithExtraPackage(parentLoaders = parentLoaders,
return createPluginClassLoaderWithExtraPackage(parents = parents,
descriptor = descriptor,
files = files,
coreLoader = coreLoader,
@@ -421,7 +407,7 @@ private fun createPluginClassLoader(parentLoaders: Array<ClassLoader>,
customPackage = "com.intellij.diagram.")
}
"com.intellij.struts2" -> {
return createPluginClassLoaderWithExtraPackage(parentLoaders = parentLoaders,
return createPluginClassLoaderWithExtraPackage(parents = parents,
descriptor = descriptor,
files = files,
coreLoader = coreLoader,
@@ -431,7 +417,7 @@ private fun createPluginClassLoader(parentLoaders: Array<ClassLoader>,
}
"com.intellij.properties" -> {
// todo ability to customize (cannot move due to backward compatibility)
return createPluginClassloader(parentLoaders = parentLoaders,
return createPluginClassloader(parents = parents,
descriptor = descriptor,
files = files,
coreLoader = coreLoader,
@@ -455,7 +441,7 @@ private fun createPluginClassLoader(parentLoaders: Array<ClassLoader>,
else {
if (!descriptor.content.modules.isEmpty()) {
// see "The `content.module` element" section about content handling for a module
return createPluginClassloader(parentLoaders = parentLoaders,
return createPluginClassloader(parents = parents,
descriptor = descriptor,
files = files,
coreLoader = coreLoader,
@@ -464,7 +450,7 @@ private fun createPluginClassLoader(parentLoaders: Array<ClassLoader>,
resolveScopeManager = createModuleContentBasedScope(descriptor))
}
else if (descriptor.packagePrefix != null) {
return createPluginClassloader(parentLoaders = parentLoaders,
return createPluginClassloader(parents = parents,
descriptor = descriptor,
files = files,
coreLoader = coreLoader,
@@ -474,7 +460,7 @@ private fun createPluginClassLoader(parentLoaders: Array<ClassLoader>,
}
return createPluginClassloader(
parentLoaders = parentLoaders,
parents = parents,
descriptor = descriptor,
files = files,
coreLoader = coreLoader,
@@ -492,25 +478,25 @@ private fun createModuleResolveScopeManager(): PluginClassLoader.ResolveScopeMan
}
}
private fun createPluginClassloader(parentLoaders: Array<ClassLoader>,
private fun createPluginClassloader(parents: Array<IdeaPluginDescriptorImpl>,
descriptor: IdeaPluginDescriptorImpl,
files: List<Path>,
libDirectories: MutableList<String>,
coreLoader: ClassLoader,
classPath: ClassPath,
resolveScopeManager: PluginClassLoader.ResolveScopeManager?): PluginClassLoader {
return PluginClassLoader(files, classPath, parentLoaders, null, descriptor, coreLoader, resolveScopeManager, descriptor.packagePrefix,
return PluginClassLoader(files, classPath, parents, descriptor, coreLoader, resolveScopeManager, descriptor.packagePrefix,
libDirectories)
}
private fun createPluginClassLoaderWithExtraPackage(parentLoaders: Array<ClassLoader>,
private fun createPluginClassLoaderWithExtraPackage(parents: Array<IdeaPluginDescriptorImpl>,
descriptor: IdeaPluginDescriptorImpl,
files: List<Path>,
libDirectories: MutableList<String>,
coreLoader: ClassLoader,
classPath: ClassPath,
customPackage: String): PluginClassLoader {
return createPluginClassloader(parentLoaders = parentLoaders,
return createPluginClassloader(parents = parents,
descriptor = descriptor,
files = files,
coreLoader = coreLoader,

View File

@@ -11,20 +11,14 @@ import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.extensions.PluginId;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.util.graph.Graph;
import com.intellij.util.graph.GraphAlgorithms;
import com.intellij.util.graph.GraphGenerator;
import com.intellij.util.lang.Java11Shim;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Method;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -183,32 +177,6 @@ public final class PluginManager {
return !Registry.is("plugins.show.implementation.details");
}
@ApiStatus.Internal
public void setPlugins(@NotNull List<IdeaPluginDescriptorImpl> descriptors) {
PluginManagerCore.doSetPlugins(Java11Shim.INSTANCE.copyOf(descriptors));
}
@ApiStatus.Internal
public boolean processAllBackwardDependencies(@NotNull IdeaPluginDescriptorImpl rootDescriptor,
boolean withOptionalDeps,
@NotNull Function<IdeaPluginDescriptorImpl, FileVisitResult> consumer) {
@NotNull PluginSet pluginSet = PluginManagerCore.getPluginSet();
CachingSemiGraph<IdeaPluginDescriptorImpl> semiGraph = CachingSemiGraphKt.createPluginIdGraph(pluginSet.enabledPlugins, pluginSet,
withOptionalDeps);
Graph<IdeaPluginDescriptorImpl> graph = GraphGenerator.generate(semiGraph);
Set<IdeaPluginDescriptorImpl> dependencies = new LinkedHashSet<>();
GraphAlgorithms.getInstance().collectOutsRecursively(graph, rootDescriptor, dependencies);
for (IdeaPluginDescriptorImpl dependency : dependencies) {
if (dependency == rootDescriptor) {
continue;
}
if (consumer.apply(dependency) == FileVisitResult.TERMINATE) {
return false;
}
}
return true;
}
public @NotNull Disposable createDisposable(@NotNull Class<?> requestor) {
ClassLoader classLoader = requestor.getClassLoader();
if (!(classLoader instanceof PluginAwareClassLoader)) {

View File

@@ -21,7 +21,6 @@ import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.PlatformUtils;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.graph.DFSTBuilder;
import com.intellij.util.graph.GraphGenerator;
import com.intellij.util.lang.Java11Shim;
import com.intellij.util.lang.UrlClassLoader;
import org.jetbrains.annotations.*;
@@ -151,8 +150,9 @@ public final class PluginManagerCore {
return pluginSet != null;
}
static synchronized void doSetPlugins(@Nullable List<IdeaPluginDescriptorImpl> value) {
pluginSet = value == null ? null : new PluginSet(value, getOnlyEnabledPlugins(value), true);
@ApiStatus.Internal
public static void setPluginSet(@NotNull PluginSet value) {
pluginSet = value;
}
public static boolean isDisabled(@NotNull PluginId pluginId) {
@@ -434,7 +434,7 @@ public final class PluginManagerCore {
}
public static synchronized void invalidatePlugins() {
doSetPlugins(null);
pluginSet = null;
DisabledPluginsState.invalidate();
ourShadowedBundledPlugins = null;
}
@@ -551,26 +551,32 @@ public final class PluginManagerCore {
private static void checkPluginCycles(@NotNull List<IdeaPluginDescriptorImpl> descriptors,
@NotNull PluginSet pluginSet,
@NotNull List<Supplier<@Nls String>> errors) {
CachingSemiGraph<IdeaPluginDescriptorImpl> graph = CachingSemiGraphKt.createPluginIdGraph(descriptors, pluginSet, true);
DFSTBuilder<IdeaPluginDescriptorImpl> builder = new DFSTBuilder<>(GraphGenerator.generate(graph));
@NotNull List<Supplier<@Nls String>> errors,
@NotNull Map<PluginId, IdeaPluginDescriptorImpl> idMap) {
CachingSemiGraph<PluginId> graph = CachingSemiGraphKt.createPluginIdGraph(descriptors, pluginSet, true);
DFSTBuilder<PluginId> builder = new DFSTBuilder<>(graph);
if (builder.isAcyclic()) {
return;
}
for (Collection<IdeaPluginDescriptorImpl> component : builder.getComponents()) {
for (Collection<PluginId> component : builder.getComponents()) {
if (component.size() < 2) {
continue;
}
for (IdeaPluginDescriptor descriptor : component) {
descriptor.setEnabled(false);
for (PluginId id : component) {
IdeaPluginDescriptorImpl plugin = pluginSet.findEnabledPlugin(id);
if (plugin != null) {
plugin.setEnabled(false);
}
}
String pluginsString = component.stream().map(it -> "'" + it.getName() + "'").collect(Collectors.joining(", "));
String pluginsString = component.stream().map(it -> "'" + idMap.get(it).getName() + "'").collect(Collectors.joining(", "));
errors.add(message("plugin.loading.error.plugins.cannot.be.loaded.because.they.form.a.dependency.cycle", pluginsString));
StringBuilder detailedMessage = new StringBuilder();
Function<IdeaPluginDescriptorImpl, String> pluginToString = plugin -> {
return "id = " + plugin.getPluginId().getIdString() + " (" + plugin.getName() + ")";
Function<PluginId, String> pluginToString = id -> {
IdeaPluginDescriptorImpl descriptor = idMap.get(id);
return "id = " + id.getIdString() + " (" + descriptor.getName() + ")";
};
detailedMessage.append("Detected plugin dependencies cycle details (only related dependencies are included):\n");
@@ -893,51 +899,34 @@ public final class PluginManagerCore {
}
List<IdeaPluginDescriptorImpl> descriptors = loadingResult.getEnabledPlugins();
PluginSet rawPluginSet = new PluginSet(descriptors, descriptors, false);
PluginSet rawPluginSet = PluginSet.Companion.createRawPluginSet(descriptors);
disableIncompatiblePlugins(descriptors, idMap, pluginErrorsById);
checkPluginCycles(descriptors, rawPluginSet, globalErrors);
checkPluginCycles(descriptors, rawPluginSet, globalErrors, idMap);
Map<PluginId, String> disabledIds = new HashMap<>();
// topological sort based on required dependencies only
List<IdeaPluginDescriptorImpl> sortedRequired = CachingSemiGraphKt.getTopologicallySorted(descriptors, rawPluginSet, false);
Set<PluginId> enabledPluginIds = new HashSet<>();
Set<String> enabledModuleV2Ids = new HashSet<>();
Map<PluginId, IdeaPluginDescriptorImpl> enabledPluginIds = new HashMap<>();
Map<String, PluginContentDescriptor.ModuleItem> enabledModuleV2Ids = new HashMap<>();
Set<PluginId> disabledRequiredIds = new HashSet<>();
Logger logger = getLogger();
boolean isDebugLogEnabled = logger.isDebugEnabled() || !System.getProperty("plugin.classloader.debug", "").isEmpty();
boolean isDebugLogEnabled = logger.isDebugEnabled() || !System.getProperty("plugin.classloader.debug", "").isEmpty() || isUnitTestMode;
for (IdeaPluginDescriptorImpl descriptor : sortedRequired) {
boolean wasEnabled = descriptor.isEnabled();
if (wasEnabled && computePluginEnabled(descriptor,
enabledPluginIds, enabledModuleV2Ids,
idMap, disabledRequiredIds, context.disabledPlugins, pluginErrorsById)) {
enabledPluginIds.add(descriptor.getPluginId());
enabledPluginIds.addAll(descriptor.modules);
m: for (PluginContentDescriptor.ModuleItem item : descriptor.content.modules) {
for (ModuleDependenciesDescriptor.ModuleReference ref : item.requireDescriptor().dependencies.modules) {
if (!enabledModuleV2Ids.contains(ref.name)) {
if (isDebugLogEnabled) {
logger.info("Module " + item.name + " is not enabled because dependency " + ref.name + " is not available");
}
continue m;
}
}
for (ModuleDependenciesDescriptor.PluginReference ref : item.requireDescriptor().dependencies.plugins) {
if (!enabledPluginIds.contains(ref.id)) {
if (isDebugLogEnabled) {
logger.info("Module " + item.name + " is not enabled because dependency " + ref.id + " is not available");
}
continue m;
}
}
enabledModuleV2Ids.add(item.name);
PluginSet.Companion.addWithV1Modules(enabledPluginIds, descriptor);
if (!CORE_ID.equals(descriptor.getPluginId())) {
PluginSet.Companion.checkModules(descriptor, enabledPluginIds, enabledModuleV2Ids, isDebugLogEnabled, logger);
}
if (descriptor.packagePrefix != null) {
enabledModuleV2Ids.add(descriptor.getId().getIdString());
else {
for (PluginContentDescriptor.ModuleItem item : descriptor.content.modules) {
enabledModuleV2Ids.put(item.name, item);
}
}
}
else {
@@ -961,20 +950,24 @@ public final class PluginManagerCore {
// topological sort based on all (required and optional) dependencies
List<IdeaPluginDescriptorImpl> allPlugins = CachingSemiGraphKt.getTopologicallySorted(sortedRequired, rawPluginSet, true);
List<IdeaPluginDescriptorImpl> enabledPlugins = getOnlyEnabledPlugins(allPlugins);
List<IdeaPluginDescriptorImpl> enabledPlugins = PluginSet.Companion.getOnlyEnabledPlugins(allPlugins);
Java11Shim java11Shim = Java11Shim.INSTANCE;
if (!context.result.incompletePlugins.isEmpty()) {
allPlugins.addAll(context.result.incompletePlugins.values());
List<IdeaPluginDescriptorImpl> result = new ArrayList<>(allPlugins.size() + context.result.incompletePlugins.size());
result.addAll(allPlugins);
result.addAll(context.result.incompletePlugins.values());
allPlugins = java11Shim.copyOf(result);
}
PluginSet pluginSet = new PluginSet(allPlugins, enabledPlugins, false);
PluginSet pluginSet = new PluginSet(allPlugins, java11Shim.copyOf(enabledPlugins),
java11Shim.copyOf(enabledModuleV2Ids), java11Shim.copyOf(enabledPluginIds));
new ClassLoaderConfigurator(pluginSet, coreLoader).configureAll();
if (checkEssentialPlugins) {
checkEssentialPluginsAreAvailable(idMap);
}
Set<PluginId> effectiveDisabledIds = disabledIds.isEmpty() ? Collections.emptySet() : Java11Shim.INSTANCE.copyOf(disabledIds.keySet());
return new PluginManagerState(pluginSet, disabledRequiredIds, effectiveDisabledIds);
return new PluginManagerState(pluginSet, disabledRequiredIds, java11Shim.copyOf(disabledIds.keySet()));
}
@ApiStatus.Internal
@@ -1030,8 +1023,8 @@ public final class PluginManagerCore {
}
private static boolean computePluginEnabled(@NotNull IdeaPluginDescriptorImpl descriptor,
@NotNull Set<PluginId> enabledPluginIds,
@NotNull Set<String> enabledModuleV2Ids,
@NotNull Map<PluginId, IdeaPluginDescriptorImpl> enabledPluginIds,
@NotNull Map<String, PluginContentDescriptor.ModuleItem> enabledModuleV2Ids,
@NotNull Map<PluginId, IdeaPluginDescriptorImpl> idMap,
@NotNull Set<PluginId> disabledRequiredIds,
@NotNull Set<PluginId> disabledPlugins,
@@ -1043,7 +1036,7 @@ public final class PluginManagerCore {
boolean notifyUser = !descriptor.isImplementationDetail();
boolean result = true;
for (PluginId incompatibleId : descriptor.incompatibilities) {
if (!enabledPluginIds.contains(incompatibleId) || disabledPlugins.contains(incompatibleId)) {
if (!enabledPluginIds.containsKey(incompatibleId) || disabledPlugins.contains(incompatibleId)) {
continue;
}
@@ -1059,7 +1052,7 @@ public final class PluginManagerCore {
for (PluginDependency dependency : descriptor.pluginDependencies) {
PluginId depId = dependency.getPluginId();
if (dependency.isOptional() || enabledPluginIds.contains(depId)) {
if (dependency.isOptional() || enabledPluginIds.containsKey(depId)) {
continue;
}
@@ -1072,8 +1065,9 @@ public final class PluginManagerCore {
addCannotLoadError(descriptor, errors, notifyUser, depId, dep);
}
for (ModuleDependenciesDescriptor.PluginReference item : descriptor.dependencies.plugins) {
if (enabledPluginIds.contains(item.id)) {
if (enabledPluginIds.containsKey(item.id)) {
continue;
}
@@ -1086,8 +1080,9 @@ public final class PluginManagerCore {
addCannotLoadError(descriptor, errors, notifyUser, item.id, dep);
}
for (ModuleDependenciesDescriptor.ModuleReference item : descriptor.dependencies.modules) {
if (enabledModuleV2Ids.contains(item.name)) {
if (enabledModuleV2Ids.containsKey(item.name)) {
continue;
}
@@ -1301,9 +1296,8 @@ public final class PluginManagerCore {
return true;
}
@ApiStatus.Internal
public static @NotNull List<PluginId> getNonOptionalDependenciesIds(@NotNull IdeaPluginDescriptorImpl descriptor) {
ArrayList<PluginId> dependencies = new ArrayList<>();
private static @NotNull List<PluginId> getNonOptionalDependenciesIds(@NotNull IdeaPluginDescriptorImpl descriptor) {
List<PluginId> dependencies = new ArrayList<>();
for (PluginDependency dependency : descriptor.pluginDependencies) {
if (dependency.isOptional()) {
continue;
@@ -1314,17 +1308,7 @@ public final class PluginManagerCore {
for (ModuleDependenciesDescriptor.PluginReference plugin : descriptor.dependencies.plugins) {
dependencies.add(plugin.id);
}
return Collections.unmodifiableList(dependencies);
}
private static @NotNull List<IdeaPluginDescriptorImpl> getOnlyEnabledPlugins(@NotNull List<IdeaPluginDescriptorImpl> sortedAll) {
List<IdeaPluginDescriptorImpl> enabledPlugins = new ArrayList<>(sortedAll.size());
for (IdeaPluginDescriptorImpl descriptor : sortedAll) {
if (descriptor.isEnabled()) {
enabledPlugins.add(descriptor);
}
}
return enabledPlugins;
return dependencies;
}
public static synchronized boolean isUpdatedBundledPlugin(@NotNull PluginDescriptor plugin) {

View File

@@ -1,6 +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.ide.plugins
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.extensions.PluginId
import com.intellij.util.lang.Java11Shim
import org.jetbrains.annotations.ApiStatus
@@ -8,73 +9,116 @@ import org.jetbrains.annotations.TestOnly
// if otherwise not specified, `module` in terms of v2 plugin model
@ApiStatus.Internal
class PluginSet(
class PluginSet internal constructor(
@JvmField val allPlugins: List<IdeaPluginDescriptorImpl>,
enabledPlugins: List<IdeaPluginDescriptorImpl>,
checkModulesDependencies: Boolean = true,
@JvmField val enabledPlugins: List<IdeaPluginDescriptorImpl>,
private val enabledModuleMap: Map<String, PluginContentDescriptor.ModuleItem>,
private val enabledPluginAndV1ModuleMap: Map<PluginId, IdeaPluginDescriptorImpl>,
) {
@JvmField val enabledPlugins: List<IdeaPluginDescriptorImpl> = Java11Shim.INSTANCE.copyOf(enabledPlugins)
private val enabledPluginAndV1ModuleMap: Map<PluginId, IdeaPluginDescriptorImpl>
// module map in a new v2 format
private val moduleMap: Map<String, PluginContentDescriptor.ModuleItem>
init {
val enabledPluginAndV1ModuleMap = HashMap<PluginId, IdeaPluginDescriptorImpl>(enabledPlugins.size)
val moduleMap = HashMap<String, PluginContentDescriptor.ModuleItem>()
for (descriptor in enabledPlugins) {
addWithV1Modules(enabledPluginAndV1ModuleMap, descriptor)
m@ for (item in descriptor.content.modules) {
if (checkModulesDependencies) {
for (ref in item.requireDescriptor().dependencies.modules) {
if (!moduleMap.containsKey(ref.name)) {
continue@m
}
}
for (ref in item.requireDescriptor().dependencies.plugins) {
if (!enabledPluginAndV1ModuleMap.containsKey(ref.id)) {
continue@m
}
}
}
moduleMap.putIfAbsent(item.name, item)?.let {
throw RuntimeException("Duplicated module name (first=$it, second=$item)")
companion object {
// special case - raw plugin set where everything is enabled and resolved
fun createRawPluginSet(plugins: List<IdeaPluginDescriptorImpl>): PluginSet {
val java11Shim = Java11Shim.INSTANCE
val enabledModuleV2Ids = HashMap<String, PluginContentDescriptor.ModuleItem>()
val enabledPluginAndModuleV1Map = HashMap<PluginId, IdeaPluginDescriptorImpl>(plugins.size)
for (descriptor in plugins) {
addWithV1Modules(enabledPluginAndModuleV1Map, descriptor)
for (item in descriptor.content.modules) {
enabledModuleV2Ids.put(item.name, item)
}
}
return PluginSet(
allPlugins = plugins,
enabledPlugins = plugins,
enabledModuleMap = java11Shim.copyOf(enabledModuleV2Ids),
enabledPluginAndV1ModuleMap = java11Shim.copyOf(enabledPluginAndModuleV1Map),
)
}
if (descriptor.packagePrefix != null) {
val pluginAsModuleItem = PluginContentDescriptor.ModuleItem(name = descriptor.id.idString, configFile = null)
pluginAsModuleItem.descriptor = descriptor
moduleMap[pluginAsModuleItem.name] = pluginAsModuleItem
fun createPluginSet(allPlugins: List<IdeaPluginDescriptorImpl>, enabledPlugins: List<IdeaPluginDescriptorImpl>): PluginSet {
val enabledModuleV2Ids = HashMap<String, PluginContentDescriptor.ModuleItem>()
val enabledPluginAndModuleV1Map = HashMap<PluginId, IdeaPluginDescriptorImpl>(enabledPlugins.size)
val log = PluginManagerCore.getLogger()
val isDebugLogEnabled = log.isDebugEnabled || !System.getProperty("plugin.classloader.debug", "").isEmpty()
for (descriptor in enabledPlugins) {
addWithV1Modules(enabledPluginAndModuleV1Map, descriptor)
checkModules(descriptor, enabledPluginAndModuleV1Map, enabledModuleV2Ids, isDebugLogEnabled, log)
}
val java11Shim = Java11Shim.INSTANCE
return PluginSet(
allPlugins = java11Shim.copyOf(allPlugins),
enabledPlugins = java11Shim.copyOf(enabledPlugins),
enabledModuleMap = java11Shim.copyOf(enabledModuleV2Ids),
enabledPluginAndV1ModuleMap = java11Shim.copyOf(enabledPluginAndModuleV1Map),
)
}
fun addWithV1Modules(result: MutableMap<PluginId, IdeaPluginDescriptorImpl>, descriptor: IdeaPluginDescriptorImpl) {
result.put(descriptor.id, descriptor)
for (module in descriptor.modules) {
result.put(module, descriptor)
}
}
val java11Shim = Java11Shim.INSTANCE
this.enabledPluginAndV1ModuleMap = java11Shim.copyOf(enabledPluginAndV1ModuleMap)
this.moduleMap = java11Shim.copyOf(moduleMap)
fun getOnlyEnabledPlugins(sortedAll: Collection<IdeaPluginDescriptorImpl>): List<IdeaPluginDescriptorImpl> {
return sortedAll.filterTo(ArrayList(sortedAll.size)) { it.isEnabled }
}
fun checkModules(descriptor: IdeaPluginDescriptorImpl,
enabledPluginIds: Map<PluginId, IdeaPluginDescriptorImpl>,
enabledModuleV2Ids: MutableMap<String, PluginContentDescriptor.ModuleItem>,
isDebugLogEnabled: Boolean,
log: Logger) {
m@ for (item in descriptor.content.modules) {
for (ref in item.requireDescriptor().dependencies.modules) {
if (!enabledModuleV2Ids.containsKey(ref.name)) {
if (isDebugLogEnabled) {
log.info("Module ${item.name} is not enabled because dependency ${ref.name} is not available")
}
continue@m
}
}
for (ref in item.requireDescriptor().dependencies.plugins) {
if (!enabledPluginIds.containsKey(ref.id)) {
if (isDebugLogEnabled) {
log.info("Module ${item.name} is not enabled because dependency ${ref.id} is not available")
}
continue@m
}
}
enabledModuleV2Ids.put(item.name, item)
}
}
}
@TestOnly
fun getUnsortedEnabledModules(): Collection<PluginContentDescriptor.ModuleItem> = ArrayList(moduleMap.values)
fun getUnsortedEnabledModules(): Collection<PluginContentDescriptor.ModuleItem> = ArrayList(enabledModuleMap.values)
fun isPluginEnabled(id: PluginId) = enabledPluginAndV1ModuleMap.containsKey(id)
fun findEnabledPlugin(id: PluginId): IdeaPluginDescriptorImpl? = enabledPluginAndV1ModuleMap[id]
fun findEnabledPlugin(id: PluginId): IdeaPluginDescriptorImpl? = enabledPluginAndV1ModuleMap.get(id)
fun findEnabledModule(id: String): IdeaPluginDescriptorImpl? = moduleMap[id]?.requireDescriptor()
fun findEnabledModule(id: String): IdeaPluginDescriptorImpl? = enabledModuleMap.get(id)?.requireDescriptor()
fun isModuleEnabled(id: String) = moduleMap.containsKey(id)
fun isModuleEnabled(id: String) = enabledModuleMap.containsKey(id)
fun concat(descriptor: IdeaPluginDescriptorImpl): PluginSet {
return PluginSet(allPlugins = allPlugins.plus(descriptor),
enabledPlugins = Java11Shim.INSTANCE.copyOf(enabledPlugins.plus(descriptor)))
fun enablePlugin(descriptor: IdeaPluginDescriptorImpl): PluginSet {
// in tests or on install plugin is not in all plugins
// linear search is ok here - not a hot method
PluginManagerCore.getLogger().assertTrue(!enabledPlugins.contains(descriptor))
return createPluginSet(allPlugins = if (allPlugins.contains(descriptor)) allPlugins else allPlugins.plus(descriptor),
enabledPlugins = enabledPlugins.plus(descriptor))
}
private fun addWithV1Modules(result: MutableMap<PluginId, IdeaPluginDescriptorImpl>, descriptor: IdeaPluginDescriptorImpl) {
result[descriptor.id] = descriptor
for (module in descriptor.modules) {
result[module] = descriptor
}
fun updateEnabledPlugins(): PluginSet {
return createPluginSet(allPlugins = allPlugins, enabledPlugins = getOnlyEnabledPlugins(allPlugins))
}
fun removePluginAndUpdateEnabledPlugins(descriptor: IdeaPluginDescriptorImpl): PluginSet {
// not just remove from enabledPlugins - maybe another plugins in list also disabled as result of plugin unloading
val allPlugins = allPlugins.minus(descriptor)
return createPluginSet(allPlugins = allPlugins, enabledPlugins = getOnlyEnabledPlugins(allPlugins))
}
}

View File

@@ -113,8 +113,7 @@ public final class PluginClassLoader extends UrlClassLoader implements PluginAwa
logStream = logStreamCandidate;
}
private ClassLoader[] parents;
private IdeaPluginDescriptorImpl[] dependencies;
private IdeaPluginDescriptorImpl[] parents;
// cache of computed list of all parents (not only direct)
private volatile ClassLoader[] allParents;
@@ -143,7 +142,7 @@ public final class PluginClassLoader extends UrlClassLoader implements PluginAwa
}
public PluginClassLoader(@NotNull UrlClassLoader.Builder builder,
@NotNull ClassLoader @NotNull [] parents,
@NotNull IdeaPluginDescriptorImpl @NotNull [] dependencies,
@NotNull PluginDescriptor pluginDescriptor,
@Nullable Path pluginRoot,
@NotNull ClassLoader coreLoader) {
@@ -152,12 +151,11 @@ public final class PluginClassLoader extends UrlClassLoader implements PluginAwa
instanceId = instanceIdProducer.incrementAndGet();
this.resolveScopeManager = (p1, p2, p3) -> null;
this.parents = parents;
this.pluginDescriptor = pluginDescriptor;
pluginId = pluginDescriptor.getPluginId();
this.packagePrefix = null;
this.coreLoader = coreLoader;
checkNoCoreInParents(parents, coreLoader);
this.parents = dependencies;
libDirectories = new SmartList<>();
if (pluginRoot != null) {
@@ -170,8 +168,7 @@ public final class PluginClassLoader extends UrlClassLoader implements PluginAwa
public PluginClassLoader(@NotNull List<Path> files,
@NotNull ClassPath classPath,
ClassLoader[] parents,
IdeaPluginDescriptorImpl[] dependencies,
@NotNull IdeaPluginDescriptorImpl @NotNull [] dependencies,
@NotNull PluginDescriptor pluginDescriptor,
@NotNull ClassLoader coreLoader,
@Nullable ResolveScopeManager resolveScopeManager,
@@ -182,16 +179,11 @@ public final class PluginClassLoader extends UrlClassLoader implements PluginAwa
instanceId = instanceIdProducer.incrementAndGet();
this.resolveScopeManager = resolveScopeManager == null ? (p1, p2, p3) -> null : resolveScopeManager;
this.parents = parents;
this.dependencies = dependencies;
this.parents = dependencies;
this.pluginDescriptor = pluginDescriptor;
pluginId = pluginDescriptor.getPluginId();
this.packagePrefix = (packagePrefix == null || packagePrefix.endsWith(".")) ? packagePrefix : (packagePrefix + '.');
this.coreLoader = coreLoader;
if (parents != null) {
checkNoCoreInParents(parents, coreLoader);
}
this.libDirectories = libDirectories;
}
@@ -199,17 +191,6 @@ public final class PluginClassLoader extends UrlClassLoader implements PluginAwa
return libDirectories;
}
private static void checkNoCoreInParents(@NotNull ClassLoader @NotNull [] parents, @NotNull ClassLoader coreLoader) {
if (PluginClassLoader.class.desiredAssertionStatus()) {
for (ClassLoader parent : parents) {
if (parent == coreLoader) {
Logger.getInstance(PluginClassLoader.class).error("Core loader must be not specified in parents " +
"(parents=" + Arrays.toString(parents) + ", coreLoader=" + coreLoader + ")");
}
}
}
}
@Override
public @Nullable String getPackagePrefix() {
return packagePrefix;
@@ -352,9 +333,6 @@ public final class PluginClassLoader extends UrlClassLoader implements PluginAwa
return result;
}
initParents();
assert parents != null;
if (parents.length == 0) {
result = new ClassLoader[]{coreLoader};
allParents = result;
@@ -363,17 +341,15 @@ public final class PluginClassLoader extends UrlClassLoader implements PluginAwa
Set<ClassLoader> parentSet = new LinkedHashSet<>();
Deque<ClassLoader> queue = new ArrayDeque<>();
Collections.addAll(queue, parents);
collectClassLoaders(queue);
ClassLoader classLoader;
while ((classLoader = queue.pollFirst()) != null) {
if (classLoader == coreLoader || !parentSet.add(classLoader)) {
if (!parentSet.add(classLoader)) {
continue;
}
if (classLoader instanceof PluginClassLoader) {
PluginClassLoader parent = (PluginClassLoader)classLoader;
parent.initParents();
Collections.addAll(queue, parent.parents);
((PluginClassLoader)classLoader).collectClassLoaders(queue);
}
}
parentSet.add(coreLoader);
@@ -383,21 +359,13 @@ public final class PluginClassLoader extends UrlClassLoader implements PluginAwa
return result;
}
private void initParents() {
IdeaPluginDescriptorImpl[] dependencies = this.dependencies;
if (dependencies == null || parents != null) {
return;
}
List<ClassLoader> list = new ArrayList<>(dependencies.length);
for (IdeaPluginDescriptorImpl dependency : dependencies) {
ClassLoader loader = dependency.classLoader;
if (loader != null) {
list.add(loader);
private void collectClassLoaders(@NotNull Deque<ClassLoader> queue) {
for (IdeaPluginDescriptorImpl parent : parents) {
ClassLoader classLoader = parent.classLoader;
if (classLoader != null && classLoader != coreLoader) {
queue.add(classLoader);
}
}
parents = list.toArray(EMPTY_CLASS_LOADER_ARRAY);
this.dependencies = null;
}
public void clearParentListCache() {
@@ -615,22 +583,24 @@ public final class PluginClassLoader extends UrlClassLoader implements PluginAwa
@TestOnly
@ApiStatus.Internal
public @Nullable List<ClassLoader> _getParents() {
ClassLoader[] parents = this.parents;
public @NotNull List<IdeaPluginDescriptorImpl> _getParents() {
//noinspection SSBasedInspection
return parents == null ? null : Collections.unmodifiableList(Arrays.asList(parents));
return Collections.unmodifiableList(Arrays.asList(parents));
}
@ApiStatus.Internal
public void attachParent(@NotNull ClassLoader classLoader) {
if (parents == null) {
initParents();
public void attachParent(@NotNull IdeaPluginDescriptorImpl parent) {
//noinspection SSBasedInspection
if (Arrays.stream(parents).anyMatch(it -> it == parent)) {
return;
}
int length = parents.length;
ClassLoader[] result = new ClassLoader[length + 1];
IdeaPluginDescriptorImpl[] result = new IdeaPluginDescriptorImpl[length + 1];
System.arraycopy(parents, 0, result, 0, length);
result[length] = classLoader;
result[length] = parent;
parents = result;
allParents = null;
parentListCacheIdCounter.incrementAndGet();
}
@@ -638,21 +608,18 @@ public final class PluginClassLoader extends UrlClassLoader implements PluginAwa
* You must clear allParents cache for all loaded plugins.
*/
@ApiStatus.Internal
public boolean detachParent(@NotNull ClassLoader classLoader) {
if (parents == null) {
initParents();
}
public boolean detachParent(@NotNull IdeaPluginDescriptorImpl parent) {
for (int i = 0; i < parents.length; i++) {
if (classLoader != parents[i]) {
if (parent != parents[i]) {
continue;
}
int length = parents.length;
ClassLoader[] result = new ClassLoader[length - 1];
IdeaPluginDescriptorImpl[] result = new IdeaPluginDescriptorImpl[length - 1];
System.arraycopy(parents, 0, result, 0, i);
System.arraycopy(parents, i + 1, result, i, length - i - 1);
parents = result;
allParents = null;
parentListCacheIdCounter.incrementAndGet();
return true;
}

View File

@@ -45,16 +45,15 @@ internal class ClassLoaderTreeChecker(private val unloadedMainDescriptor: IdeaPl
}
@Suppress("TestOnlyProblems")
val parents = classLoader._getParents() ?: return
val parents = classLoader._getParents()
for (unloadedClassLoader in classLoaders) {
if (parents.contains(unloadedClassLoader)) {
if (parents.any { it.classLoader === unloadedClassLoader }) {
LOG.error("$classLoader references via parents $unloadedClassLoader that must be unloaded")
}
}
for (parent in parents) {
if (parent is PluginClassLoader && parent.pluginId == unloadedMainDescriptor.pluginId) {
if (parent.pluginId == unloadedMainDescriptor.pluginId) {
LOG.error("$classLoader references via parents $parent that must be unloaded")
}
}

View File

@@ -83,7 +83,6 @@ import org.jetbrains.annotations.NonNls
import java.awt.KeyboardFocusManager
import java.awt.Window
import java.nio.channels.FileChannel
import java.nio.file.FileVisitResult
import java.nio.file.Paths
import java.nio.file.StandardOpenOption
import java.text.SimpleDateFormat
@@ -94,7 +93,7 @@ import javax.swing.ToolTipManager
import kotlin.collections.component1
import kotlin.collections.component2
private val LOG = logger<DynamicPlugins>()
private val LOG = logger<DynamicPlugins>()
private val classloadersFromUnloadedPlugins = mutableMapOf<PluginId, WeakList<PluginClassLoader>>()
object DynamicPlugins {
@@ -294,9 +293,9 @@ object DynamicPlugins {
if (dependencyMessage == null && checkImplementationDetailDependencies) {
val contextWithImplementationDetails = context.toMutableList()
contextWithImplementationDetails.add(descriptor)
processImplementationDetailDependenciesOnPlugin(descriptor, contextWithImplementationDetails::add)
processImplementationDetailDependenciesOnPlugin(descriptor, pluginSet, contextWithImplementationDetails::add)
processImplementationDetailDependenciesOnPlugin(descriptor) { dependentDescriptor ->
processImplementationDetailDependenciesOnPlugin(descriptor, pluginSet) { dependentDescriptor ->
// don't check a plugin that is an implementation-detail dependency on the current plugin if it has other disabled dependencies
// and won't be loaded anyway
if (findMissingRequiredDependency(dependentDescriptor, contextWithImplementationDetails) == null) {
@@ -387,13 +386,8 @@ object DynamicPlugins {
return result
}
@JvmStatic
fun getPluginUnloadingTask(pluginDescriptor: IdeaPluginDescriptorImpl, options: UnloadPluginOptions): Runnable {
return Runnable { unloadPlugin(pluginDescriptor, options) }
}
data class UnloadPluginOptions(
var disable: Boolean = false,
var disable: Boolean = true,
var isUpdate: Boolean = false,
var save: Boolean = true,
var requireMemorySnapshot: Boolean = false,
@@ -430,17 +424,21 @@ object DynamicPlugins {
}
}
fun unloadAndUninstallPlugin(pluginDescriptor: IdeaPluginDescriptorImpl): Boolean {
return unloadPlugin(pluginDescriptor, UnloadPluginOptions(disable = false))
}
@JvmOverloads
fun unloadPlugin(pluginDescriptor: IdeaPluginDescriptorImpl, options: UnloadPluginOptions = UnloadPluginOptions()): Boolean {
fun unloadPlugin(pluginDescriptor: IdeaPluginDescriptorImpl,
options: UnloadPluginOptions = UnloadPluginOptions(disable = true)): Boolean {
val app = ApplicationManager.getApplication() as ApplicationImpl
val pluginId = pluginDescriptor.pluginId
val pluginSet = PluginManagerCore.getPluginSet()
if (options.checkImplementationDetailDependencies) {
processImplementationDetailDependenciesOnPlugin(pluginDescriptor) { dependentDescriptor ->
processImplementationDetailDependenciesOnPlugin(pluginDescriptor, pluginSet) { dependentDescriptor ->
dependentDescriptor.isEnabled = false
unloadPlugin(dependentDescriptor, UnloadPluginOptions(disable = true,
save = false,
unloadPlugin(dependentDescriptor, UnloadPluginOptions(save = false,
waitForClassloaderUnload = false,
checkImplementationDetailDependencies = false))
true
@@ -473,7 +471,7 @@ object DynamicPlugins {
unloadDependencyDescriptors(pluginDescriptor, pluginSet, classLoaders)
unloadPluginDescriptorNotRecursively(pluginDescriptor)
clearPluginClassLoaderParentListCache()
clearPluginClassLoaderParentListCache(pluginSet)
app.extensionArea.clearUserCache()
for (project in ProjectUtil.getOpenProjects()) {
@@ -506,11 +504,10 @@ object DynamicPlugins {
(ProjectManager.getInstanceIfCreated() as? ProjectManagerImpl)?.disposeDefaultProjectAndCleanupComponentsForDynamicPluginTests()
if (options.disable) {
// update list of disabled plugins
PluginManager.getInstance().setPlugins(PluginManagerCore.getPluginSet().allPlugins)
PluginManagerCore.setPluginSet(pluginSet.updateEnabledPlugins())
}
else {
PluginManager.getInstance().setPlugins(PluginManagerCore.getPluginSet().allPlugins.minus(pluginDescriptor))
PluginManagerCore.setPluginSet(pluginSet.removePluginAndUpdateEnabledPlugins(pluginDescriptor))
}
}
finally {
@@ -628,7 +625,7 @@ object DynamicPlugins {
classLoaders.add(classLoader)
classLoader.state = PluginClassLoader.UNLOAD_IN_PROGRESS
}
else if (!classLoader.detachParent(dependencyClassloader)) {
else if (!classLoader.detachParent(dependencyPlugin)) {
LOG.warn("Classloader $dependencyClassloader doesn't have $classLoader as parent")
}
}
@@ -806,18 +803,19 @@ object DynamicPlugins {
val loadStartTime = System.currentTimeMillis()
val app = ApplicationManager.getApplication() as ApplicationImpl
val pluginSet = PluginManagerCore.getPluginSet().concat(pluginDescriptor)
val pluginSet = PluginManagerCore.getPluginSet().enablePlugin(pluginDescriptor)
val classLoaderConfigurator = ClassLoaderConfigurator(pluginSet)
classLoaderConfigurator.configure(pluginDescriptor)
app.messageBus.syncPublisher(DynamicPluginListener.TOPIC).beforePluginLoaded(pluginDescriptor)
app.runWriteAction {
try {
addToLoadedPlugins(pluginDescriptor)
PluginManagerCore.setPluginSet(pluginSet)
val listenerCallbacks = mutableListOf<Runnable>()
loadPluginDescriptor(pluginDescriptor, app, listenerCallbacks)
loadOptionalDependenciesOnPlugin(pluginDescriptor, classLoaderConfigurator, pluginSet, listenerCallbacks)
clearPluginClassLoaderParentListCache()
clearPluginClassLoaderParentListCache(pluginSet)
for (openProject in ProjectUtil.getOpenProjects()) {
(CachedValuesManager.getManager(openProject) as CachedValuesManagerImpl).clearCachedValues()
@@ -837,7 +835,7 @@ object DynamicPlugins {
if (checkImplementationDetailDependencies) {
var implementationDetailsLoadedWithoutRestart = true
processImplementationDetailDependenciesOnPlugin(pluginDescriptor) { dependentDescriptor ->
processImplementationDetailDependenciesOnPlugin(pluginDescriptor, pluginSet) { dependentDescriptor ->
val dependencies = dependentDescriptor.pluginDependencies
if (dependencies.all { it.isOptional || PluginManagerCore.getPlugin(it.pluginId) != null }) {
if (!loadPlugin(dependentDescriptor, checkImplementationDetailDependencies = false)) {
@@ -851,26 +849,6 @@ object DynamicPlugins {
return true
}
private fun addToLoadedPlugins(pluginDescriptor: IdeaPluginDescriptorImpl) {
var foundExistingPlugin = false
val newPlugins = PluginManagerCore.getPluginSet().allPlugins.map {
if (it.pluginId == pluginDescriptor.pluginId) {
foundExistingPlugin = true
pluginDescriptor
}
else {
it
}
}
if (foundExistingPlugin) {
PluginManager.getInstance().setPlugins(newPlugins)
}
else {
PluginManager.getInstance().setPlugins(PluginManagerCore.getPluginSet().allPlugins.plus(pluginDescriptor))
}
}
@JvmStatic
fun onPluginUnload(parentDisposable: Disposable, callback: Runnable) {
ApplicationManager.getApplication().messageBus.connect(parentDisposable)
@@ -986,15 +964,19 @@ object DynamicPlugins {
}
private fun processImplementationDetailDependenciesOnPlugin(pluginDescriptor: IdeaPluginDescriptorImpl,
processor: (descriptor: IdeaPluginDescriptorImpl) -> Boolean) {
PluginManager.getInstance().processAllBackwardDependencies(pluginDescriptor, false) { loadedDescriptor ->
if (loadedDescriptor.isImplementationDetail) {
if (processor(loadedDescriptor)) FileVisitResult.CONTINUE else FileVisitResult.TERMINATE
}
else {
FileVisitResult.CONTINUE
}
}
pluginSet: PluginSet,
processor: (descriptor: IdeaPluginDescriptorImpl) -> Boolean) {
processDependenciesOnPlugin(dependencyPlugin = pluginDescriptor,
pluginSet = pluginSet,
loadStateFilter = LoadStateFilter.ANY,
onlyOptional = false) { _, module ->
if (module.isImplementationDetail) {
processor(module)
}
else {
true
}
}
}
/**
@@ -1027,18 +1009,10 @@ private fun loadOptionalDependenciesOnPlugin(dependencyPlugin: IdeaPluginDescrip
}
}
private fun clearPluginClassLoaderParentListCache() {
for (descriptor in PluginManagerCore.getLoadedPlugins(null)) {
clearPluginClassLoaderParentListCache(descriptor)
}
}
private fun clearPluginClassLoaderParentListCache(descriptor: IdeaPluginDescriptorImpl) {
(descriptor.classLoader as? PluginClassLoader ?: return).clearParentListCache()
for (dependency in descriptor.pluginDependencies) {
dependency.subDescriptor?.let {
clearPluginClassLoaderParentListCache(it)
}
private fun clearPluginClassLoaderParentListCache(pluginSet: PluginSet) {
// yes, clear not only enabled plugins, but all, just to be sure, it is cheap operation
for (descriptor in pluginSet.allPlugins) {
(descriptor.classLoader as? PluginClassLoader ?: continue).clearParentListCache()
}
}
@@ -1084,11 +1058,28 @@ private fun createDisposeTreePredicate(pluginDescriptor: IdeaPluginDescriptorImp
}
}
private fun processOptionalDependenciesOnPlugin(dependencyPlugin: IdeaPluginDescriptorImpl,
pluginSet: PluginSet,
isLoaded: Boolean,
processor: (pluginDescriptor: IdeaPluginDescriptorImpl,
moduleDescriptor: IdeaPluginDescriptorImpl) -> Boolean) {
private fun processOptionalDependenciesOnPlugin(
dependencyPlugin: IdeaPluginDescriptorImpl,
pluginSet: PluginSet,
isLoaded: Boolean,
processor: (pluginDescriptor: IdeaPluginDescriptorImpl, moduleDescriptor: IdeaPluginDescriptorImpl) -> Boolean,
) {
processDependenciesOnPlugin(
dependencyPlugin = dependencyPlugin,
pluginSet = pluginSet,
onlyOptional = true,
loadStateFilter = if (isLoaded) LoadStateFilter.LOADED else LoadStateFilter.NOT_LOADED,
processor = processor,
)
}
private fun processDependenciesOnPlugin(
dependencyPlugin: IdeaPluginDescriptorImpl,
pluginSet: PluginSet,
loadStateFilter: LoadStateFilter,
onlyOptional: Boolean,
processor: (pluginDescriptor: IdeaPluginDescriptorImpl, moduleDescriptor: IdeaPluginDescriptorImpl) -> Boolean,
) {
val wantedIds = HashSet<String>(1 + dependencyPlugin.content.modules.size)
wantedIds.add(dependencyPlugin.id.idString)
for (module in dependencyPlugin.content.modules) {
@@ -1100,16 +1091,22 @@ private fun processOptionalDependenciesOnPlugin(dependencyPlugin: IdeaPluginDesc
continue
}
if (!processOptionalDependenciesInOldFormatOnPlugin(dependencyPlugin.id, plugin, isLoaded, processor)) {
if (!processOptionalDependenciesInOldFormatOnPlugin(dependencyPluginId = dependencyPlugin.id,
mainDescriptor = plugin,
loadStateFilter = loadStateFilter,
onlyOptional = onlyOptional,
processor = processor)) {
return
}
for (moduleItem in plugin.content.modules) {
val module = moduleItem.requireDescriptor()
val isModuleLoaded = module.classLoader != null
if (isModuleLoaded != isLoaded) {
continue
if (loadStateFilter != LoadStateFilter.ANY) {
val isModuleLoaded = module.classLoader != null
if (isModuleLoaded != (loadStateFilter == LoadStateFilter.LOADED)) {
continue
}
}
for (item in module.dependencies.modules) {
@@ -1117,30 +1114,52 @@ private fun processOptionalDependenciesOnPlugin(dependencyPlugin: IdeaPluginDesc
return
}
}
for (item in module.dependencies.plugins) {
if (dependencyPlugin.id == item.id && !processor(plugin, module)) {
return
}
}
}
}
}
private fun processOptionalDependenciesInOldFormatOnPlugin(dependencyPluginId: PluginId,
mainDescriptor: IdeaPluginDescriptorImpl,
isLoaded: Boolean,
processor: (main: IdeaPluginDescriptorImpl, sub: IdeaPluginDescriptorImpl) -> Boolean): Boolean {
private enum class LoadStateFilter {
LOADED, NOT_LOADED, ANY
}
private fun processOptionalDependenciesInOldFormatOnPlugin(
dependencyPluginId: PluginId,
mainDescriptor: IdeaPluginDescriptorImpl,
loadStateFilter: LoadStateFilter,
onlyOptional: Boolean,
processor: (main: IdeaPluginDescriptorImpl, sub: IdeaPluginDescriptorImpl) -> Boolean
): Boolean {
for (dependency in mainDescriptor.pluginDependencies) {
if (!dependency.isOptional) {
if (!onlyOptional && dependency.pluginId == dependencyPluginId && !processor(mainDescriptor, mainDescriptor)) {
return false
}
continue
}
val subDescriptor = dependency.subDescriptor ?: continue
val isModuleLoaded = subDescriptor.classLoader != null
if (isModuleLoaded != isLoaded) {
continue
if (loadStateFilter != LoadStateFilter.ANY) {
val isModuleLoaded = subDescriptor.classLoader != null
if (isModuleLoaded != (loadStateFilter == LoadStateFilter.LOADED)) {
continue
}
}
if (dependency.pluginId == dependencyPluginId && !processor(mainDescriptor, subDescriptor)) {
return false
}
if (!processOptionalDependenciesInOldFormatOnPlugin(dependencyPluginId, subDescriptor, isLoaded, processor)) {
if (!processOptionalDependenciesInOldFormatOnPlugin(
dependencyPluginId = dependencyPluginId,
mainDescriptor = subDescriptor,
loadStateFilter = loadStateFilter,
onlyOptional = onlyOptional,
processor = processor)) {
return false
}
}

View File

@@ -96,7 +96,7 @@ public final class PluginInstaller {
boolean isUpdate) {
boolean uninstalledWithoutRestart = true;
if (pluginDescriptor.isEnabled()) {
DynamicPlugins.UnloadPluginOptions options = new DynamicPlugins.UnloadPluginOptions()
DynamicPlugins.UnloadPluginOptions options = new DynamicPlugins.UnloadPluginOptions().withDisable(false)
.withUpdate(isUpdate)
.withWaitForClassloaderUnload(true);

View File

@@ -990,13 +990,18 @@ public final class StartupUtil {
}
@Override
public <E> Set<E> copyOf(Set<? extends E> collection) {
public <E> @NotNull Set<E> copyOf(Set<? extends E> collection) {
return Set.copyOf(collection);
}
@Override
public <E> List<E> copyOf(List<? extends E> collection) {
public <E> @NotNull List<E> copyOf(List<? extends E> collection) {
return List.copyOf(collection);
}
@Override
public @NotNull <E> List<E> listOf(E[] collection) {
return List.of(collection);
}
}
}

View File

@@ -313,26 +313,24 @@ public final class PluginDownloader {
public boolean tryInstallWithoutRestart(@Nullable JComponent ownerComponent) {
assert myDescriptor instanceof IdeaPluginDescriptorImpl;
final IdeaPluginDescriptorImpl descriptorImpl = (IdeaPluginDescriptorImpl)myDescriptor;
if (!DynamicPlugins.allowLoadUnloadWithoutRestart(descriptorImpl)) {
IdeaPluginDescriptorImpl descriptor = (IdeaPluginDescriptorImpl)myDescriptor;
if (!DynamicPlugins.allowLoadUnloadWithoutRestart(descriptor)) {
return false;
}
if (myOldFile != null) {
IdeaPluginDescriptor installedPlugin = PluginManagerCore.getPlugin(myDescriptor.getPluginId());
IdeaPluginDescriptorImpl fullDescriptor = installedPlugin instanceof IdeaPluginDescriptorImpl ?
(IdeaPluginDescriptorImpl)installedPlugin :
null;
if (fullDescriptor == null ||
!DynamicPlugins.INSTANCE.unloadPlugin(fullDescriptor,
new DynamicPlugins.UnloadPluginOptions()
.withUpdate(true)
.withWaitForClassloaderUnload(true))) {
IdeaPluginDescriptorImpl installedPlugin = (IdeaPluginDescriptorImpl)PluginManagerCore.getPlugin(myDescriptor.getPluginId());
// yes, if no installed plugin by id, it means that something goes wrong, so do not try to install and load
if (installedPlugin == null || !DynamicPlugins.INSTANCE.unloadPlugin(descriptor,
new DynamicPlugins.UnloadPluginOptions()
.withDisable(false)
.withUpdate(true)
.withWaitForClassloaderUnload(true))) {
return false;
}
}
return PluginInstaller.installAndLoadDynamicPlugin(myFile.toPath(), ownerComponent, descriptorImpl);
return PluginInstaller.installAndLoadDynamicPlugin(myFile.toPath(), ownerComponent, descriptor);
}
private @Nullable File tryDownloadPlugin(@NotNull ProgressIndicator indicator, boolean showMessageOnError) {

View File

@@ -2,7 +2,6 @@
package com.intellij.ide.plugins
import com.intellij.ide.plugins.cl.PluginAwareClassLoader
import com.intellij.ide.plugins.cl.PluginClassLoader
import com.intellij.openapi.util.BuildNumber
import com.intellij.testFramework.assertions.Assertions.assertThat
import com.intellij.testFramework.assertions.Assertions.assertThatThrownBy
@@ -91,7 +90,7 @@ internal class ClassLoaderConfiguratorTest {
val plugins = loadResult.getEnabledPlugins()
assertThat(plugins).hasSize(2)
val classLoaderConfigurator = ClassLoaderConfigurator(PluginSet(plugins, plugins))
val classLoaderConfigurator = ClassLoaderConfigurator(PluginSet.createPluginSet(plugins, plugins))
plugins.forEach(classLoaderConfigurator::configure)
return loadResult
}

View File

@@ -33,6 +33,7 @@ import com.intellij.openapi.roots.ui.configuration.ModuleConfigurationEditorProv
import com.intellij.openapi.roots.ui.configuration.ModuleConfigurationState
import com.intellij.openapi.startup.StartupActivity
import com.intellij.openapi.util.Disposer
import com.intellij.openapi.util.use
import com.intellij.psi.PsiFile
import com.intellij.testFramework.EdtRule
import com.intellij.testFramework.ProjectRule
@@ -90,7 +91,7 @@ class DynamicPluginsTest {
app.messageBus.syncPublisher(UISettingsListener.TOPIC).uiSettingsChanged(UISettings())
assertThat(receivedNotifications).hasSize(1)
DynamicPlugins.unloadPlugin(descriptor)
DynamicPlugins.unloadAndUninstallPlugin(descriptor)
app.messageBus.syncPublisher(UISettingsListener.TOPIC).uiSettingsChanged(UISettings())
assertThat(receivedNotifications).hasSize(1)
}
@@ -105,18 +106,18 @@ class DynamicPluginsTest {
DynamicPlugins.loadPlugin(descriptor)
DisabledPluginsState.saveDisabledPlugins(PathManager.getConfigDir(), builder.id)
DynamicPlugins.unloadPlugin(descriptor, DynamicPlugins.UnloadPluginOptions(disable = true))
DynamicPlugins.unloadAndUninstallPlugin(descriptor)
assertThat(PluginManagerCore.getPlugin(descriptor.pluginId)?.pluginClassLoader as? PluginClassLoader).isNull()
DisabledPluginsState.saveDisabledPlugins(PathManager.getConfigDir())
val newDescriptor = loadDescriptorInTest(path)
ClassLoaderConfigurator(PluginManagerCore.getPluginSet().concat(newDescriptor)).configure(newDescriptor)
ClassLoaderConfigurator(PluginManagerCore.getPluginSet().enablePlugin(newDescriptor)).configure(newDescriptor)
DynamicPlugins.loadPlugin(newDescriptor)
try {
assertThat(PluginManagerCore.getPlugin(descriptor.pluginId)?.pluginClassLoader as? PluginClassLoader).isNotNull()
}
finally {
DynamicPlugins.unloadPlugin(newDescriptor)
DynamicPlugins.unloadAndUninstallPlugin(newDescriptor)
}
}
@@ -125,12 +126,12 @@ class DynamicPluginsTest {
val data = System.currentTimeMillis().toString()
val extensionTag = "<applicationService serviceImplementation=\"${MyPersistentComponent::class.java.name}\"/>"
val disposable = loadExtensionWithText(extensionTag, DynamicPlugins::class.java.classLoader)
val disposable = loadExtensionWithText(extensionTag)
val service = ApplicationManager.getApplication().getService(MyPersistentComponent::class.java)
service.myState.stateData = data
Disposer.dispose(disposable)
val disposable2 = loadExtensionWithText(extensionTag, DynamicPlugins::class.java.classLoader)
val disposable2 = loadExtensionWithText(extensionTag)
val service2 = ApplicationManager.getApplication().getService(MyPersistentComponent::class.java)
assertThat(service2.myState.stateData).isEqualTo(data)
Disposer.dispose(disposable2)
@@ -208,32 +209,26 @@ class DynamicPluginsTest {
// match production - on plugin load/unload ActionManager is already initialized
val actionManager = ActionManager.getInstance()
val beforeList = PluginManagerCore.getLoadedPlugins()
val plugin2Builder = PluginBuilder().randomId("bar").packagePrefix("bar")
val pluginDescriptor = PluginBuilder()
.randomId("foo")
.packagePrefix("org.foo")
.module(
"intellij.org.foo",
PluginBuilder().actions("""<group id="FooBarGroup"></group>""").packagePrefix("org.foo.bar").dependency(plugin2Builder.id)
)
val plugin1Disposable = loadPluginWithText(pluginDescriptor)
assertThat(actionManager.getAction("FooBarGroup")).isNull()
try {
val pluginToDisposable = loadPluginWithText(plugin2Builder)
try {
assertThat(actionManager.getAction("FooBarGroup")).isNotNull()
}
finally {
Disposer.dispose(pluginToDisposable)
}
assertThat(actionManager.getAction("FooBarGroup")).isNull()
}
finally {
Disposer.dispose(plugin1Disposable)
}
runAndCheckThatNoNewPlugins {
val dependency = PluginBuilder().randomId("dependency").packagePrefix("org.dependency")
val dependent = PluginBuilder()
.randomId("dependent")
.packagePrefix("org.dependent")
.module(
"org.dependent",
PluginBuilder().actions("""<group id="FooBarGroup"></group>""").packagePrefix("org.dependent.sub").pluginDependency(dependency.id)
)
loadPluginWithText(dependent).use {
assertThat(actionManager.getAction("FooBarGroup")).isNull()
assertThat(PluginManagerCore.getLoadedPlugins()).isEqualTo(beforeList)
runAndCheckThatNoNewPlugins {
loadPluginWithText(dependency).use {
assertThat(actionManager.getAction("FooBarGroup")).isNotNull()
}
}
assertThat(actionManager.getAction("FooBarGroup")).isNull()
}
}
}
@Test
@@ -305,54 +300,38 @@ class DynamicPluginsTest {
PluginBuilder()
.extensions("""<barExtension key="foo" implementationClass="y"/>""", "foo")
.packagePrefix("foo1")
.dependency(barBuilder.id)
.pluginDependency(barBuilder.id)
)
val plugin1Disposable = loadPluginWithText(fooBuilder)
try {
loadPluginWithText(fooBuilder).use {
val ep = ApplicationManager.getApplication().extensionArea.getExtensionPointIfRegistered<KeyedLazyInstanceEP<*>>("foo.barExtension")
assertThat(ep).isNotNull()
val plugin2Disposable = loadPluginWithText(barBuilder)
try {
loadPluginWithText(barBuilder).use {
val extension = ep!!.extensionList.single()
assertThat(extension.key).isEqualTo("foo")
assertThat(extension.pluginDescriptor)
.isEqualTo(PluginManagerCore.getPluginSet().findEnabledModule(fooBuilder.id)!!)
}
finally {
Disposer.dispose(plugin2Disposable)
.isEqualTo(PluginManagerCore.getPluginSet().findEnabledPlugin(PluginId.findId(fooBuilder.id)!!)!!)
}
assertThat(ep!!.extensionList).isEmpty()
}
finally {
Disposer.dispose(plugin1Disposable)
}
}
@Test
fun loadOptionalDependencyDescriptor() {
val pluginOneBuilder = PluginBuilder().randomId("optionalDependencyDescriptor-one")
val pluginOneDisposable = loadPluginWithText(pluginOneBuilder)
val app = ApplicationManager.getApplication()
try {
loadPluginWithText(pluginOneBuilder).use {
assertThat(app.getService(MyPersistentComponent::class.java)).isNull()
val pluginTwoId = "optionalDependencyDescriptor-two_${Ksuid.generate()}"
val pluginTwoDisposable = loadPluginWithOptionalDependency(
loadPluginWithOptionalDependency(
PluginBuilder().id(pluginTwoId),
PluginBuilder().extensions("""<applicationService serviceImplementation="${MyPersistentComponent::class.java.name}"/>"""),
pluginOneBuilder
)
try {
).use {
assertThat(app.getService(MyPersistentComponent::class.java)).isNotNull()
}
finally {
Disposer.dispose(pluginTwoDisposable)
}
assertThat(PluginManagerCore.getPlugin(PluginId.getId(pluginTwoId))).isNull()
assertThat(app.getService(MyPersistentComponent::class.java)).isNull()
}
finally {
Disposer.dispose(pluginOneDisposable)
}
}
@Test
@@ -371,31 +350,23 @@ class DynamicPluginsTest {
.applicationListeners(
"""<listener class="${MyUISettingsListener2::class.java.name}" topic="com.intellij.ide.ui.UISettingsListener"/>""")
.packagePrefix("org.foo")
.dependency(pluginTwoBuilder.id),
.pluginDependency(pluginTwoBuilder.id),
)
val pluginOneDisposable = loadPluginWithText(pluginDescriptor)
try {
loadPluginWithText(pluginDescriptor).use {
val app = ApplicationManager.getApplication()
app.messageBus.syncPublisher(UISettingsListener.TOPIC).uiSettingsChanged(UISettings())
assertThat(receivedNotifications).hasSize(1)
val pluginToDisposable = loadPluginWithText(pluginTwoBuilder)
try {
loadPluginWithText(pluginTwoBuilder).use {
app.messageBus.syncPublisher(UISettingsListener.TOPIC).uiSettingsChanged(UISettings())
assertThat(receivedNotifications).hasSize(2)
assertThat(receivedNotifications2).hasSize(1)
}
finally {
Disposer.dispose(pluginToDisposable)
}
app.messageBus.syncPublisher(UISettingsListener.TOPIC).uiSettingsChanged(UISettings())
assertThat(receivedNotifications).hasSize(3)
assertThat(receivedNotifications2).hasSize(1)
}
finally {
Disposer.dispose(pluginOneDisposable)
}
}
@Test
@@ -444,44 +415,31 @@ class DynamicPluginsTest {
@Test
fun testProjectService() {
val project = projectRule.project
val disposable = loadExtensionWithText("""
loadExtensionWithText("""
<projectService serviceImplementation="${MyProjectService::class.java.name}"/>
""".trimIndent(), DynamicPluginsTest::class.java.classLoader)
try {
""".trimIndent()).use {
assertThat(project.getService(MyProjectService::class.java)).isNotNull()
}
finally {
Disposer.dispose(disposable)
}
}
@Test
fun extensionOnServiceDependency() {
val project = projectRule.project
StartupManagerImpl.addActivityEpListener(project)
val disposable = loadExtensionWithText("""
loadExtensionWithText("""
<postStartupActivity implementation="${MyStartupActivity::class.java.name}"/>
<projectService serviceImplementation="${MyProjectService::class.java.name}"/>
""", DynamicPluginsTest::class.java.classLoader)
try {
""").use {
assertThat(project.service<MyProjectService>().executed).isTrue()
}
finally {
Disposer.dispose(disposable)
}
}
@Test
fun unloadEPWithDefaultAttributes() {
val disposable = loadExtensionWithText(
"<globalInspection implementationClass=\"${MyInspectionTool::class.java.name}\" cleanupTool=\"false\"/>",
DynamicPlugins::class.java.classLoader)
try {
loadExtensionWithText(
"<globalInspection implementationClass=\"${MyInspectionTool::class.java.name}\" cleanupTool=\"false\"/>").use {
assertThat(InspectionEP.GLOBAL_INSPECTION.extensionList.any { it.implementationClass == MyInspectionTool::class.java.name }).isTrue()
}
finally {
Disposer.dispose(disposable)
}
assertThat(InspectionEP.GLOBAL_INSPECTION.extensionList.any { it.implementationClass == MyInspectionTool::class.java.name }).isFalse()
}
@@ -493,8 +451,7 @@ class DynamicPluginsTest {
<bundleName>messages.CommonBundle</bundleName>
<categoryKey>button.add</categoryKey>
<className>${MyIntentionAction::class.java.name}</className>
</intentionAction>""",
DynamicPlugins::class.java.classLoader)
</intentionAction>""")
try {
val intention = IntentionManagerImpl.EP_INTENTION_ACTIONS.extensionList.find { it.className == MyIntentionAction::class.java.name }
assertThat(intention).isNotNull
@@ -517,8 +474,7 @@ class DynamicPluginsTest {
checked.incrementAndGet()
}, listenerDisposable)
val disposable = loadExtensionWithText("<projectConfigurable instance=\"${MyConfigurable::class.java.name}\" displayName=\"foo\"/>",
DynamicPlugins::class.java.classLoader)
val disposable = loadExtensionWithText("<projectConfigurable instance=\"${MyConfigurable::class.java.name}\" displayName=\"foo\"/>")
try {
assertThat(checked.get()).isEqualTo(1)
assertThat(
@@ -544,11 +500,11 @@ class DynamicPluginsTest {
fun loadExistingFileTypeModification() {
@Suppress("SpellCheckingInspection")
val textToLoad = "<fileType name=\"PLAIN_TEXT\" language=\"PLAIN_TEXT\" fileNames=\".texttest\"/>"
var disposable = loadExtensionWithText(textToLoad, DynamicPlugins::class.java.classLoader)
var disposable = loadExtensionWithText(textToLoad)
Disposer.dispose(disposable)
UIUtil.dispatchAllInvocationEvents()
disposable = loadExtensionWithText(textToLoad, DynamicPlugins::class.java.classLoader)
disposable = loadExtensionWithText(textToLoad)
Disposer.dispose(disposable)
}
@@ -595,10 +551,8 @@ class DynamicPluginsTest {
val barDependencyDescriptor = PluginBuilder().depends(quuxBuilder.id, quuxDependencyDescriptor)
val mainDescriptor = PluginBuilder().randomId("main").depends(barBuilder.id, barDependencyDescriptor)
val barDisposable = loadPluginWithText(barBuilder)
try {
val quuxDisposable = loadPluginWithText(quuxBuilder)
try {
loadPluginWithText(barBuilder).use {
loadPluginWithText(quuxBuilder).use {
val descriptor = loadDescriptorInTest(
mainDescriptor,
Files.createTempDirectory(inMemoryFs.fs.getPath("/"), null),
@@ -607,12 +561,6 @@ class DynamicPluginsTest {
assertThat(DynamicPlugins.checkCanUnloadWithoutRestart(descriptor)).isEqualTo(
"Plugin ${mainDescriptor.id} is not unload-safe because of extension to non-dynamic EP foo.barExtension in optional dependency on ${quuxBuilder.id} in optional dependency on ${barBuilder.id}")
}
finally {
Disposer.dispose(quuxDisposable)
}
}
finally {
Disposer.dispose(barDisposable)
}
}
@@ -692,6 +640,7 @@ private class MyAction2 : AnAction() {
}
}
@Suppress("DialogTitleCapitalization")
private class MyIntentionAction : IntentionAction {
override fun startInWriteAction() = false
override fun getFamilyName() = "foo"
@@ -709,3 +658,9 @@ private class MyRunnable : Runnable {
private class MyModuleConfigurationEditorProvider : ModuleConfigurationEditorProvider {
override fun createEditors(state: ModuleConfigurationState?): Array<ModuleConfigurationEditor> = arrayOf()
}
private inline fun runAndCheckThatNoNewPlugins(block: () -> Unit) {
val beforeList = PluginManagerCore.getLoadedPlugins()
block()
assertThat(PluginManagerCore.getLoadedPlugins()).isEqualTo(beforeList)
}

View File

@@ -39,19 +39,12 @@ internal fun loadDescriptorInTest(dir: Path, disabledPlugins: Set<PluginId> = em
}
@JvmOverloads
fun loadExtensionWithText(
extensionTag: String,
loader: ClassLoader = DynamicPlugins::class.java.classLoader,
ns: String = "com.intellij"
): Disposable {
fun loadExtensionWithText(extensionTag: String, ns: String = "com.intellij"): Disposable {
val builder = PluginBuilder().extensions(extensionTag, ns)
return loadPluginWithText(builder, FileSystems.getDefault())
}
internal fun loadPluginWithText(
pluginBuilder: PluginBuilder,
fs: FileSystem,
): Disposable {
internal fun loadPluginWithText(pluginBuilder: PluginBuilder, fs: FileSystem): Disposable {
val directory = if (fs == FileSystems.getDefault()) {
FileUtil.createTempDirectory("test", "test", true).toPath()
}
@@ -68,13 +61,13 @@ internal fun loadPluginWithText(
DynamicPlugins.loadPlugin(pluginDescriptor = descriptor)
}
catch (e: Exception) {
DynamicPlugins.unloadPlugin(descriptor)
DynamicPlugins.unloadAndUninstallPlugin(descriptor)
throw e
}
return Disposable {
val reason = DynamicPlugins.checkCanUnloadWithoutRestart(descriptor)
DynamicPlugins.unloadPlugin(descriptor)
DynamicPlugins.unloadAndUninstallPlugin(descriptor)
assertThat(reason).isNull()
}
}

View File

@@ -1,6 +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.ide.plugins
import com.intellij.openapi.extensions.PluginId
import com.intellij.util.io.Compressor
import com.intellij.util.io.write
import org.intellij.lang.annotations.Language
@@ -32,19 +33,6 @@ fun module(outDir: Path, ownerId: String, moduleId: String, @Language("XML") des
outDir.resolve("$ownerId/$moduleId.xml").write(descriptor.trimIndent())
}
//class PluginV2Builder(val id: String, private var b: PluginBuilder) {
// var packagePrefix: String
// get() = throw IllegalStateException("set only")
// set(value) {
// b.packagePrefix(value)
// }
//
// @Language("XML")
// val extensionPoints = mutableListOf<@Language("XML") String>()
//
// val pluginDependencies
//}
class PluginBuilder {
private data class ExtensionBlock(val ns: String, val text: String)
private data class DependsTag(val pluginId: String, val configFile: String?)
@@ -67,6 +55,7 @@ class PluginBuilder {
private val content = mutableListOf<PluginContentDescriptor.ModuleItem>()
private val dependencies = mutableListOf<ModuleDependenciesDescriptor.ModuleReference>()
private val pluginDependencies = mutableListOf<ModuleDependenciesDescriptor.PluginReference>()
private val subDescriptors = HashMap<String, PluginBuilder>()
@@ -126,6 +115,11 @@ class PluginBuilder {
return this
}
fun pluginDependency(pluginId: String): PluginBuilder {
pluginDependencies.add(ModuleDependenciesDescriptor.PluginReference(PluginId.getId(pluginId)))
return this
}
fun noDepends(): PluginBuilder {
dependsTags.clear()
return this
@@ -206,9 +200,15 @@ class PluginBuilder {
content.joinTo(this, separator = "\n ") { """<module name="${it.name}" />""" }
append("\n</content>")
}
if (dependencies.isNotEmpty()) {
if (dependencies.isNotEmpty() || pluginDependencies.isNotEmpty()) {
append("\n<dependencies>\n ")
dependencies.joinTo(this, separator = "\n ") { """<module name="${it.name}" />""" }
if (dependencies.isNotEmpty()) {
dependencies.joinTo(this, separator = "\n ") { """<module name="${it.name}" />""" }
}
if (pluginDependencies.isNotEmpty()) {
pluginDependencies.joinTo(this, separator = "\n ") { """<plugin id="${it.id}" />""" }
}
append("\n</dependencies>")
}

View File

@@ -364,7 +364,7 @@ class PluginDescriptorTest {
assertThat(foo.pluginId.idString).isEqualTo("foo")
val fooClassLoader = foo.pluginClassLoader as PluginClassLoader
assertThat(fooClassLoader._getParents()!!).containsExactly(bar.pluginClassLoader)
assertThat(fooClassLoader._getParents()).containsExactly(bar)
}
@Test

View File

@@ -1,4 +1,4 @@
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
// 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.openapi.vfs.newvfs.persistent;
import com.intellij.ide.plugins.DynamicPluginsTestUtil;
@@ -751,7 +751,7 @@ public class PersistentFsTest extends BareTestFixtureTestCase {
@Test
public void testReadOnlyFsCachesLength() throws IOException {
String text = "<virtualFileSystem implementationClass=\"" + TracingJarFileSystemTestWrapper.class.getName() + "\" key=\"jar-wrapper\" physical=\"true\"/>";
Disposable disposable = runInEdtAndGet(() -> DynamicPluginsTestUtil.loadExtensionWithText(text, TracingJarFileSystemTestWrapper.class.getClassLoader()));
Disposable disposable = runInEdtAndGet(() -> DynamicPluginsTestUtil.loadExtensionWithText(text, "com.intellij"));
try {
File generationDir = tempDirectory.newDirectory("gen");
@@ -796,7 +796,7 @@ public class PersistentFsTest extends BareTestFixtureTestCase {
@Test
public void testDoNotRecalculateLengthIfEndOfInputStreamIsNotReached() throws IOException {
String text = "<virtualFileSystem implementationClass=\"" + TracingJarFileSystemTestWrapper.class.getName() + "\" key=\"jar-wrapper\" physical=\"true\"/>";
Disposable disposable = runInEdtAndGet(() -> DynamicPluginsTestUtil.loadExtensionWithText(text, TracingJarFileSystemTestWrapper.class.getClassLoader()));
Disposable disposable = runInEdtAndGet(() -> DynamicPluginsTestUtil.loadExtensionWithText(text, "com.intellij"));
try {
File generationDir = tempDirectory.newDirectory("gen");

View File

@@ -1,12 +1,15 @@
// 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.testGuiFramework.recorder.compile
import com.intellij.ide.plugins.IdeaPluginDescriptorImpl
import com.intellij.ide.plugins.PluginManagerCore
import com.intellij.ide.plugins.RawPluginDescriptor
import com.intellij.ide.plugins.cl.PluginClassLoader
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.PathManager
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.extensions.DefaultPluginDescriptor
import com.intellij.openapi.extensions.PluginId
import com.intellij.openapi.util.SystemInfo
import com.intellij.openapi.util.io.FileUtil
import com.intellij.testGuiFramework.recorder.GuiRecorderManager
@@ -64,21 +67,32 @@ internal class LocalCompiler {
}
//alternative way to run compiled code with pluginClassloader built especially for this file
// alternative way to run compiled code with pluginClassloader built especially for this file
private fun run() {
Notifier.updateStatus("${Notifier.LONG_OPERATION_PREFIX}Script running...")
var classLoadersArray: Array<ClassLoader>
try {
//run testGuiTest gradle configuration
// run testGuiTest gradle configuration
ApplicationManager::class.java.classLoader.loadClass("com.intellij.testGuiFramework.impl.GuiTestCase")
classLoadersArray = arrayOf(ApplicationManager::class.java.classLoader)
}
catch (cfe: ClassNotFoundException) {
classLoadersArray = arrayOf(ApplicationManager::class.java.classLoader, this.javaClass.classLoader)
}
val coreLoader = PluginManagerCore::class.java.classLoader
val pluginClassLoader = PluginClassLoader(UrlClassLoader.build().files(listOf(tempDir.toPath())).useCache(),
classLoadersArray, DefaultPluginDescriptor("SubGuiScriptRecorder"), null as Path?,
PluginManagerCore::class.java.classLoader)
classLoadersArray
.asSequence()
.filter { it !== coreLoader }
.map {
val descriptor = IdeaPluginDescriptorImpl(raw = RawPluginDescriptor(),
path = Path.of(""),
isBundled = false,
id = PluginId.getId(it.toString()))
descriptor.classLoader = it
descriptor
}.toList().toTypedArray(), DefaultPluginDescriptor("SubGuiScriptRecorder"), null as Path?,
coreLoader)
val currentTest = pluginClassLoader.loadClass(TEST_CLASS_NAME)
?: throw Exception("Unable to load by pluginClassLoader $TEST_CLASS_NAME.class file")
val testCase = currentTest.getDeclaredConstructor().newInstance()

View File

@@ -1,4 +1,4 @@
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
// 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.lang;
import com.intellij.ReviseWhenPortedToJDK;
@@ -19,18 +19,26 @@ public abstract class Java11Shim {
}
@Override
public <E> Set<E> copyOf(Set<? extends E> collection) {
public <E> @NotNull Set<E> copyOf(Set<? extends E> collection) {
return Collections.unmodifiableSet(collection);
}
@Override
public <E> List<E> copyOf(List<? extends E> collection) {
public <E> @NotNull List<E> copyOf(List<? extends E> collection) {
return Collections.unmodifiableList(new ArrayList<>(collection));
}
@Override
public @NotNull <E> List<E> listOf(E[] collection) {
return Arrays.asList(collection);
}
};
public abstract <@NotNull K, @NotNull V> Map<K, V> copyOf(Map<? extends K, ? extends V> map);
public abstract <@NotNull E> Set<E> copyOf(Set<? extends E> collection);
public abstract <@NotNull E> List<E> copyOf(List<? extends E> collection);
public abstract <@NotNull E> @NotNull Set<E> copyOf(Set<? extends E> collection);
public abstract <@NotNull E> @NotNull List<E> copyOf(List<? extends E> collection);
public abstract <@NotNull E> @NotNull List<E> listOf(E[] collection);
}

View File

@@ -1,4 +1,4 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
// 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.graph;
import com.intellij.openapi.util.Pair;
@@ -35,13 +35,7 @@ public final class GraphGenerator<Node> implements Graph<Node> {
// Duplicate edge
continue;
}
List<Node> edgesFromInNode = myOuts.get(inNode);
if (edgesFromInNode == null) {
edgesFromInNode = new ArrayList<>();
myOuts.put(inNode, edgesFromInNode);
}
edgesFromInNode.add(node);
myOuts.computeIfAbsent(inNode, __ -> new ArrayList<>()).add(node);
}
}
}

View File

@@ -1,4 +1,4 @@
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
// 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 org.jetbrains.java.decompiler
import com.intellij.application.options.CodeStyle
@@ -100,10 +100,9 @@ class IdeaDecompiler : ClassFileDecompilers.Light() {
PluginManagerCore.disablePlugin(id)
val plugin = PluginManagerCore.getPlugin(id)
if (plugin is IdeaPluginDescriptorImpl) {
if (DynamicPlugins.allowLoadUnloadWithoutRestart(plugin)) {
val task = DynamicPlugins.getPluginUnloadingTask(plugin, DynamicPlugins.UnloadPluginOptions(disable = true, save = false))
ApplicationManager.getApplication().invokeLater(task)
if (plugin is IdeaPluginDescriptorImpl && DynamicPlugins.allowLoadUnloadWithoutRestart(plugin)) {
ApplicationManager.getApplication().invokeLater {
DynamicPlugins.unloadPlugin(plugin, DynamicPlugins.UnloadPluginOptions(save = false))
}
}
}