diff --git a/build/groovy/org/jetbrains/intellij/build/BaseIdeaProperties.groovy b/build/groovy/org/jetbrains/intellij/build/BaseIdeaProperties.groovy index 5f3d804cd020..857653865adb 100644 --- a/build/groovy/org/jetbrains/intellij/build/BaseIdeaProperties.groovy +++ b/build/groovy/org/jetbrains/intellij/build/BaseIdeaProperties.groovy @@ -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.intellij.build import groovy.transform.CompileStatic @@ -111,10 +111,10 @@ abstract class BaseIdeaProperties extends JetBrainsProductProperties { "plugins/gradle/lib/gradle-tooling-extension-api.jar" : "1.6", "plugins/gradle/lib/gradle-tooling-extension-impl.jar" : "1.6", "plugins/maven/lib/maven-server-api.jar" : "1.6", - "plugins/maven/lib/maven2-server-impl.jar" : "1.6", + "plugins/maven/lib/maven2-server.jar" : "1.6", "plugins/maven/lib/maven3-server-common.jar" : "1.6", - "plugins/maven/lib/maven30-server-impl.jar" : "1.6", - "plugins/maven/lib/maven3-server-impl.jar" : "1.6", + "plugins/maven/lib/maven30-server.jar" : "1.6", + "plugins/maven/lib/maven3-server.jar" : "1.6", "plugins/maven/lib/artifact-resolver-m2.jar" : "1.6", "plugins/maven/lib/artifact-resolver-m3.jar" : "1.6", "plugins/maven/lib/artifact-resolver-m31.jar" : "1.6", diff --git a/platform/docs/build.main.kts b/docs/build.main.kts similarity index 63% rename from platform/docs/build.main.kts rename to docs/build.main.kts index 4206a2f304be..27163a020f5c 100644 --- a/platform/docs/build.main.kts +++ b/docs/build.main.kts @@ -1,6 +1,6 @@ -// 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. @file:Repository("https://repo1.maven.org/maven2/") -@file:DependsOn("net.sourceforge.plantuml:plantuml:1.2020.17") +@file:DependsOn("net.sourceforge.plantuml:plantuml:1.2021.6") import net.sourceforge.plantuml.FileFormat import net.sourceforge.plantuml.FileFormatOption @@ -9,44 +9,45 @@ import net.sourceforge.plantuml.error.PSystemError import org.w3c.dom.* import java.nio.file.Files import java.nio.file.Path -import java.nio.file.Paths import javax.xml.parsers.DocumentBuilderFactory import javax.xml.transform.OutputKeys +import javax.xml.transform.Transformer import javax.xml.transform.TransformerFactory import javax.xml.transform.dom.DOMSource import javax.xml.transform.stream.StreamResult import javax.xml.xpath.XPathConstants +import javax.xml.xpath.XPathExpression import javax.xml.xpath.XPathFactory val defaultFontSize = "14" -val noteBackgroundColor = "#ECECEC" +val noteBackgroundColor = "#EBEBEB" var start = System.currentTimeMillis() -val outDir = Paths.get("out").toAbsolutePath() +val outDir: Path = Path.of("out").toAbsolutePath() Files.createDirectories(outDir) val svgFileFormat = FileFormatOption(FileFormat.SVG, /* withMetadata = */ false) -Files.newDirectoryStream(Paths.get(".").toAbsolutePath(), "*.puml").use { inFiles -> +Files.newDirectoryStream(Path.of(".").toAbsolutePath(), "*.puml").use { inFiles -> for (inFile in inFiles) { if (inFile.fileName.toString().contains("-theme.")) { continue } val sourceFileReader = SourceFileReader(inFile.toFile(), outDir.toFile(), svgFileFormat) - val result = sourceFileReader.getGeneratedImages() + val result = sourceFileReader.generatedImages if (result.size == 0) { System.err.println("warning: no image in $inFile") continue } - for (s in sourceFileReader.getBlocks()) { - val diagram = s.getDiagram() + for (s in sourceFileReader.blocks) { + val diagram = s.diagram if (diagram is PSystemError) { System.err.println("status=ERROR") - System.err.println("lineNumber=" + diagram.getLineLocation().getPosition()) - for (error in diagram.getErrorsUml()) { - System.err.println("label=" + error.getError()) + System.err.println("lineNumber=" + diagram.lineLocation.position) + for (error in diagram.errorsUml) { + System.err.println("label=" + error.error) } } } @@ -56,16 +57,12 @@ Files.newDirectoryStream(Paths.get(".").toAbsolutePath(), "*.puml").use { inFile println("Generate SVG in: ${System.currentTimeMillis() - start} ms") start = System.currentTimeMillis() -// re-format SVG -val transformer = TransformerFactory.newInstance().newTransformer() -transformer.setOutputProperty(OutputKeys.INDENT, "yes") -transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2") - -val dbFactory = DocumentBuilderFactory.newDefaultInstance() -val xPathFactory = XPathFactory.newInstance() -val textFillXPath = xPathFactory.newXPath().compile("/svg/g/text") -val rectFillXPath = xPathFactory.newXPath().compile("/svg/g/rect") -val pathFillXPath = xPathFactory.newXPath().compile("/svg/g/path") +val dbFactory: DocumentBuilderFactory = DocumentBuilderFactory.newDefaultInstance() +val xPathFactory: XPathFactory = XPathFactory.newInstance() +val textFillXPath: XPathExpression = xPathFactory.newXPath().compile("/svg/g/text") +val rectFillXPath: XPathExpression = xPathFactory.newXPath().compile("/svg/g/rect") +val lineStyleXPath: XPathExpression = xPathFactory.newXPath().compile("/svg/g/line") +val pathFillXPath: XPathExpression = xPathFactory.newXPath().compile("/svg/g/path") Files.newDirectoryStream(outDir, "*.svg").use { svgFiles -> for (svgFile in svgFiles) { @@ -75,11 +72,13 @@ Files.newDirectoryStream(outDir, "*.svg").use { svgFiles -> println("Transform SVG in: ${System.currentTimeMillis() - start} ms") -fun transformSvg(svgFile: Path?) { +fun transformSvg(svgFile: Path) { val dBuilder = dbFactory.newDocumentBuilder() - val document = Files.newInputStream(svgFile).buffered().use { dBuilder.parse(it) } - - //document.documentElement.removeAttribute("xmlns:xlink") + val content = Files.readString(svgFile) + val document = dBuilder.parse(content.byteInputStream()) + if (!content.contains("xlink:")) { + document.documentElement.removeAttribute("xmlns:xlink") + } val classNameToBuilder = linkedMapOf() @@ -88,8 +87,9 @@ fun transformSvg(svgFile: Path?) { val element = textNodes.item(i) as Element val fill = element.getAttributeNode("fill") ?: continue - // not required - better to not modify glyph to ensure that font looks as expected - //element.removeAttribute("lengthAdjust") + if (element.getAttribute("lengthAdjust") == "spacing") { + element.removeAttribute("lengthAdjust") + } if (fill.value == "#000000") { element.removeAttributeNode(fill) @@ -104,9 +104,27 @@ fun transformSvg(svgFile: Path?) { val element = rectNodes.item(i) as Element val style = element.getAttributeNode("style") ?: continue val fill = element.getAttributeNode("fill") ?: continue - if (style.value == "stroke: #383838; stroke-width: 1.0;") { + if (style.value == "stroke:#383838;stroke-width:1.0;") { applyBackgroundAndBorder("process", element, style, fill, classNameToBuilder) } + else if (style.value == "stroke:#FEFECE;stroke-width:1.5;") { + applyBackgroundAndBorder("node", element, style, fill, classNameToBuilder) + } + } + + val lineNodes = lineStyleXPath.evaluate(document, XPathConstants.NODESET) as NodeList + for (i in 0 until lineNodes.length) { + val element = lineNodes.item(i) as Element + val style = element.getAttributeNode("style") ?: continue + if (style.value == "stroke:#A80036;stroke-width:1.0;") { + classNameToBuilder.computeIfAbsent("arrow") { + """ + ${style.value} + """.trimIndent() + } + element.removeAttributeNode(style) + element.setAttribute("class", "arrow") + } } val pathNodes = pathFillXPath.evaluate(document, XPathConstants.NODESET) as NodeList @@ -121,8 +139,16 @@ fun transformSvg(svgFile: Path?) { appendStyleElement(document, classNameToBuilder) + // re-format SVG + val transformer: Transformer = TransformerFactory.newDefaultInstance().newTransformer() + transformer.setOutputProperty(OutputKeys.INDENT, "yes") + @Suppress("HttpUrlsUsage") + transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2") + // so, first node of insertAdjacentHTML result will be svg and not comment + transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes") + Files.newOutputStream(svgFile).buffered().use { fileOut -> - transformer.transform(DOMSource(document), StreamResult(fileOut)) + transformer.transform(DOMSource(document.documentElement), StreamResult(fileOut)) } } @@ -139,6 +165,7 @@ fun hasOnlyAttributes(list: NamedNodeMap, prefix: String, names: List): return nameSet.isEmpty() } +@Suppress("JavaMapForEach") fun appendStyleElement(document: Document, classNameToBuilder: Map) { val styleElement = document.createElement("style") val builder = StringBuilder() @@ -146,7 +173,7 @@ fun appendStyleElement(document: Document, classNameToBuilder: Map builder.append("\n .").append(name).append(" {") - content.lineSequence().iterator().forEach { builder.append("\n ").append(it) } + content.lineSequence().iterator().forEach { builder.append("\n ").append(it.trim()) } builder.append("\n }") } builder.append('\n') @@ -177,7 +204,7 @@ fun extractFontStyle(element: Element, classNameToBuilder: MutableMap) { - classNameToBuilder.getOrPut(className) { + classNameToBuilder.computeIfAbsent(className) { """ - ${style.value} - fill: ${fill.value}; - """.trimIndent() + ${style.value.removeSuffix(";").replace(";", ";\n")}; + fill: ${fill.value}; + """.trimIndent() } element.removeAttributeNode(style) element.removeAttributeNode(fill) diff --git a/docs/build.sh b/docs/build.sh new file mode 100755 index 000000000000..ca2230bf6720 --- /dev/null +++ b/docs/build.sh @@ -0,0 +1,15 @@ +#!/bin/sh +# 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. +set -ex + +# brew install graphviz + +cd "$(dirname "$0")" + +KOTLIN="/Applications/Idea.app/Contents/plugins/KotlinPlugin" + +java -Dfile.encoding=UTF-8 -classpath $KOTLIN/kotlinc/lib/kotlin-compiler.jar:$KOTLIN/lib/kotlin-stdlib.jar:$KOTLIN/lib/kotlin-reflect.jar:$KOTLIN/kotlinc/lib/kotlin-main-kts.jar:$KOTLIN/kotlinc/lib/kotlin-stdlib.jar:$KOTLIN/kotlinc/lib/kotlin-reflect.jar:/Volumes/data/.ivy2/cache/net.sourceforge.plantuml/plantuml/jars/plantuml-1.2021.6.jar:$KOTLIN/kotlinc/lib/kotlin-script-runtime.jar org.jetbrains.kotlin.cli.jvm.K2JVMCompiler \ +-kotlin-home $KOTLIN/kotlinc -jvm-target 1.8 -\ +script ./build.main.kts + + diff --git a/platform/docs/getting-service.puml b/docs/getting-service.puml similarity index 100% rename from platform/docs/getting-service.puml rename to docs/getting-service.puml diff --git a/platform/docs/icon-loading-stat.puml b/docs/icon-loading-stat.puml similarity index 100% rename from platform/docs/icon-loading-stat.puml rename to docs/icon-loading-stat.puml diff --git a/platform/docs/jb-plantuml-theme.puml b/docs/jb-plantuml-theme.puml similarity index 80% rename from platform/docs/jb-plantuml-theme.puml rename to docs/jb-plantuml-theme.puml index 389c7ff5de48..a3aa4636d660 100644 --- a/platform/docs/jb-plantuml-theme.puml +++ b/docs/jb-plantuml-theme.puml @@ -2,6 +2,9 @@ skinparam monochrome true skinparam shadowing true +' https://plantuml.com/component-diagram "Use rectangle notation (remove UML notation)" +skinparam componentStyle rectangle + skinparam DefaultFontName Roboto skinparam DefaultMonospacedFontName "Roboto Mono" diff --git a/docs/out/getting-service.svg b/docs/out/getting-service.svg new file mode 100644 index 000000000000..cd9e4538813a --- /dev/null +++ b/docs/out/getting-service.svg @@ -0,0 +1,166 @@ + + + + + + + + + + + + + + In any thread. + Get on demand only. + Do not cache result. + Do not request in constructor unless needed. + + getService + + Return + null + + no + Is + + Service Declaration + + Found + yes + + no + Is + + Light Service + + yes + + + Is Container Active? + active + disposed or dispose in progress + + + synchronized on service class + + Is Initializing? + yes + no + + Throw + PluginException + Cyclic Service Initialization + + + non cancelable + + + Avoid getting other services to reduce initialization tree. + As less dependencies, as more faster and reliable. + + Create Instance + + Register to be Disposed on Container Dispose + if Implements + Disposable + + Load Persistent State + if Implements + PersistentStateComponent + + no + Is Created and Initialized? + yes + + + Throw + ProcessCanceledException + + no + Is Created and Initialized? + yes + + + Return Instance + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/out/icon-loading-stat.svg b/docs/out/icon-loading-stat.svg new file mode 100644 index 000000000000..defc13df9150 --- /dev/null +++ b/docs/out/icon-loading-stat.svg @@ -0,0 +1,186 @@ + + + + + + + + + + + + + + Externally called method + IconLoader.findIcon + + find-icon + + + Internally called method + CachedImageIcon.loadImage + . + Called numerous times per + CachedImageIcon + instance — to compute a new scale. + + find-icon-load + + Is SVG + yes + no + + svg-load + + png-load + + + Is resourceClass Provided + yes + no + + load-from-resource + + Is prebuiltCacheId Provided + yes + + svg-prebuilt + + Return Image + + Return + null + + no + Is Resource Exist + yes + + load-from-url + + + Is SVG + yes + no + + svg-cache-read + + svg-decode + + no + Is Cached + yes + + + png-decode + + + no + Is Cached + yes + + + Return Image + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/out/index.html b/docs/out/index.html new file mode 100644 index 000000000000..7b65ec2f6301 --- /dev/null +++ b/docs/out/index.html @@ -0,0 +1,81 @@ + + + + + IntelliJ Platform Activity Diagrams + + + + + + +
+
+ +
+ +
+
+
+ + + + diff --git a/docs/out/plugin-graph.svg b/docs/out/plugin-graph.svg new file mode 100644 index 000000000000..8d44a7ea1466 --- /dev/null +++ b/docs/out/plugin-graph.svg @@ -0,0 +1,3369 @@ + + + + + + intellij.angularJS + + + + intellij.cloudFormation + + + + intellij.clouds.docker.impl + + + + intellij.clouds.kubernetes + + + + intellij.coffeescript + + + + intellij.configurationScript + + + + intellij.diagram.impl + + + + intellij.fileWatcher + + + + intellij.freemarker + + + + intellij.go.ide + + + + intellij.go.plugin + + + + intellij.go.sharedIndexes.bundled + + + + intellij.go.template + + + + intellij.gradle.ext + + + + intellij.gradle.javaee + + + + intellij.grazie + + + + intellij.groovy + + + + intellij.grpc + + + + intellij.guice + + + + intellij.haml + + + + intellij.helidon + + + + intellij.jade + + + + intellij.java.plugin + + + + intellij.javaFX.css + + + + intellij.javaHttpClients + + + + intellij.javaee.app.servers.impl + + + + intellij.javaee.appServers.tomcat + + + + intellij.javaee.beanValidation + + + + intellij.javaee.cdi + + + + intellij.javaee.ejb.impl + + + + intellij.javaee.jax.rs + + + + intellij.javaee.jax.ws + + + + intellij.javaee.jpa.impl + + + + intellij.javaee.jsf + + + + intellij.javaee.web.impl + + + + intellij.javascript.impl + + + + intellij.jboss.arquillian + + + + intellij.jboss.jbpm + + + + intellij.less + + + + intellij.maven.ext + + + + intellij.micronaut + + + + intellij.nodeJS.remoteInterpreter + + + + intellij.php.docker + + + + intellij.phpspec + + + + intellij.play + + + + intellij.properties + + + + intellij.protoeditor + + + + intellij.puppet + + + + intellij.quarkus + + + + intellij.reactivestreams.core + + + + intellij.remoteRun + + + + intellij.ruby.plugin + + + + intellij.safe.push + + + + intellij.selenium + + + + intellij.sh + + + + intellij.slim + + + + intellij.space + + + + intellij.spring.batch + + + + intellij.spring.boot.core + + + + intellij.spring.core + + + + intellij.spring.data + + + + intellij.spring.integration.core + + + + intellij.spring.mvc.impl + + + + intellij.spring.security + + + + intellij.spring.webflow + + + + intellij.spring.websocket + + + + intellij.struts2 + + + + intellij.swagger + + + + intellij.thymeleaf + + + + intellij.vcs.git + + + + intellij.vcs.github + + + + intellij.vuejs + + + + intellij.w3validators + + + + intellij.wsl + + + + intellij.wsl.fs.helper + + + + intellij.yaml + + + + + name + intellij.angularJS + + + package + + + + descriptor + contrib/AngularJS/resources/META-INF/plugin.xml + + + content + + + + + + + + name + intellij.angularJS/diagram + + + package + org.angularjs.diagram + + + descriptor + contrib/AngularJS/resources/intellij.angularJS.diagram.xml + + + dependencies + + + + + + + + + + plugin + com.intellij.diagram + + + + module + intellij.javascript.impl/diagrams + + + + name + intellij.cloudFormation + + + package + com.intellij.aws.cloudformation + + + descriptor + contrib/CloudFormation/src/main/resources/META-INF/plugin.xml + + + + name + intellij.clouds.docker.impl + + + package + com.intellij.docker + + + descriptor + plugins/Docker/resources/META-INF/plugin.xml + + + content + + + + dependencies + + + + + + + + + + + + + + name + intellij.clouds.docker.java + + + package + com.intellij.docker.java + + + descriptor + plugins/Docker/Docker-java-impl/resources/intellij.clouds.docker.java.xml + + + dependencies + + + + + + + + plugin + com.intellij.java + + + + name + intellij.clouds.docker.compose + + + package + com.intellij.docker.composeFile + + + descriptor + plugins/Docker/Docker-compose/resources/intellij.clouds.docker.compose.xml + + + dependencies + + + + + + + + + + plugin + org.jetbrains.plugins.yaml + + + + plugin + Docker + + + + name + intellij.clouds.docker.file + + + package + com.intellij.docker.dockerFile + + + descriptor + plugins/Docker/Docker-file/resources/intellij.clouds.docker.file.xml + + + + name + intellij.clouds.docker.remoteRun + + + package + com.intellij.docker.remote + + + descriptor + plugins/Docker/Docker-remote-run/resources/intellij.clouds.docker.remoteRun.xml + + + + + + + + + + + plugin + com.intellij.modules.lang + + + + plugin + com.intellij.modules.json + + + + plugin + com.intellij.modules.remoteServers + + + + name + intellij.clouds.kubernetes + + + package + com.intellij.kubernetes + + + descriptor + plugins/kubernetes/resources/META-INF/plugin.xml + + + dependencies + + + + + + + + module + intellij.clouds.docker.compose + + + + name + intellij.coffeescript + + + package + org.coffeescript + + + descriptor + plugins/coffeescript/coffeescript-core/resources/META-INF/plugin.xml + + + + name + intellij.configurationScript + + + package + com.intellij.configurationScript + + + descriptor + community/plugins/configuration-script/resources/META-INF/plugin.xml + + + + name + intellij.diagram.impl + + + package + com.intellij.uml + + + descriptor + plugins/uml/resources/META-INF/plugin.xml + + + + name + intellij.fileWatcher + + + package + com.intellij.plugins.watcher + + + descriptor + plugins/fileWatcher/resources/META-INF/plugin.xml + + + + name + intellij.freemarker + + + package + com.intellij.freemarker + + + descriptor + plugins/freemarker/core/resources/META-INF/plugin.xml + + + + name + intellij.go.ide + + + package + com.goide.ide + + + descriptor + goland/intellij-go/ide/resources/META-INF/plugin.xml + + + + name + intellij.go.plugin + + + package + com.goide + + + descriptor + goland/intellij-go/plugin/resources/META-INF/plugin.xml + + + + name + intellij.go.sharedIndexes.bundled + + + package + com.goide.index.shared.bundled + + + descriptor + goland/intellij-go/go-shared-indexes-bundled/resources/META-INF/plugin.xml + + + + name + intellij.go.template + + + package + com.goide.template + + + descriptor + goland/intellij-go/template/resources/META-INF/plugin.xml + + + + name + intellij.gradle.ext + + + package + + + + descriptor + plugins/gradle-ext/src/main/resources/META-INF/plugin.xml + + + content + + + + + + + + name + intellij.gradle.ext/profiler + + + package + org.jetbrains.idea.gradle.ext.profiler + + + descriptor + plugins/gradle-ext/src/main/resources/intellij.gradle.ext.profiler.xml + + + dependencies + + + + + + + + module + intellij.profiler.ultimate + + + + name + intellij.gradle.javaee + + + package + com.intellij.javaee.gradle + + + descriptor + plugins/javaee/gradle-integration/resources/META-INF/plugin.xml + + + content + + + + dependencies + + + + + + + + + + name + intellij.gradle.javaee/javaee + + + package + com.intellij.javaee.gradle.javaee + + + descriptor + plugins/javaee/gradle-integration/resources/intellij.gradle.javaee.javaee.xml + + + dependencies + + + + + + + + plugin + com.intellij.javaee + + + + name + intellij.gradle.javaee/javaee.web + + + package + com.intellij.javaee.gradle.javaeeWeb + + + descriptor + plugins/javaee/gradle-integration/resources/intellij.gradle.javaee.javaee.web.xml + + + dependencies + + + + + + + + plugin + com.intellij.javaee.web + + + + + + + + + plugin + org.jetbrains.plugins.gradle + + + + plugin + com.intellij.javaee + + + + name + intellij.grazie + + + package + com.intellij.grazie + + + descriptor + community/plugins/grazie/resources/META-INF/plugin.xml + + + content + + + + + + + + + + + + + + + + + + name + intellij.grazie.java + + + package + com.intellij.grazie.ide.language.java + + + descriptor + community/plugins/grazie/java/src/main/resources/intellij.grazie.java.xml + + + dependencies + + + + + + + + plugin + com.intellij.java + + + + name + intellij.grazie.json + + + package + com.intellij.grazie.ide.language.json + + + descriptor + community/plugins/grazie/json/src/main/resources/intellij.grazie.json.xml + + + dependencies + + + + + + + + plugin + com.intellij.modules.json + + + + name + intellij.grazie.markdown + + + package + com.intellij.grazie.ide.language.markdown + + + descriptor + community/plugins/grazie/markdown/src/main/resources/intellij.grazie.markdown.xml + + + dependencies + + + + + + + + plugin + org.intellij.plugins.markdown + + + + name + intellij.grazie.properties + + + package + com.intellij.grazie.ide.language.properties + + + descriptor + community/plugins/grazie/properties/src/main/resources/intellij.grazie.properties.xml + + + dependencies + + + + + + + + plugin + com.intellij.properties + + + + name + intellij.grazie.xml + + + package + com.intellij.grazie.ide.language.xml + + + descriptor + community/plugins/grazie/xml/main/resources/intellij.grazie.xml.xml + + + dependencies + + + + + + + + plugin + com.intellij.modules.xml + + + + name + intellij.grazie.yaml + + + package + com.intellij.grazie.ide.language.yaml + + + descriptor + community/plugins/grazie/yaml/main/resources/intellij.grazie.yaml.xml + + + dependencies + + + + + + + + plugin + org.jetbrains.plugins.yaml + + + + name + intellij.groovy + + + package + org.jetbrains.plugins.groovy + + + descriptor + community/plugins/groovy/src/META-INF/plugin.xml + + + + name + intellij.grpc + + + package + com.intellij.grpc + + + descriptor + plugins/frameworks/grpc/resources/META-INF/plugin.xml + + + + name + intellij.guice + + + package + com.intellij.guice + + + descriptor + plugins/frameworks/Guice/resources/META-INF/plugin.xml + + + + name + intellij.haml + + + package + org.jetbrains.plugins.haml + + + descriptor + plugins/haml/resources/META-INF/plugin.xml + + + + name + intellij.helidon + + + package + com.intellij.helidon + + + descriptor + plugins/frameworks/helidon/resources/META-INF/plugin.xml + + + + name + intellij.jade + + + package + com.jetbrains.plugins.jade + + + descriptor + plugins/Jade/resources/META-INF/plugin.xml + + + + name + intellij.java.plugin + + + package + + + + descriptor + community/java/plugin/resources/META-INF/plugin.xml + + + content + + + + + + + + name + intellij.profiler.ultimate + + + package + com.intellij.profiler.ultimate + + + descriptor + plugins/profiler/ultimate/resources/intellij.profiler.ultimate.xml + + + dependencies + + + + + + + + plugin + com.intellij.modules.profiler + + + + name + intellij.javaFX.css + + + package + org.jetbrains.plugins.javaFX + + + descriptor + plugins/javaFX-CSS/src/META-INF/plugin.xml + + + + name + intellij.javaHttpClients + + + package + com.intellij.javahttp + + + descriptor + plugins/frameworks/java-http-clients/resources/META-INF/plugin.xml + + + + name + intellij.javaee.app.servers.impl + + + package + + + + descriptor + plugins/javaee/core/javaee-app-server-integration/app-server-integration-impl/resources/META-INF/plugin.xml + + + content + + + + + + + + name + intellij.javaee.app.servers.impl/profiler + + + package + com.intellij.javaee.appServers.run.profiler + + + descriptor + plugins/javaee/core/javaee-app-server-integration/app-server-integration-impl/resources/intellij.javaee.app.servers.impl.profiler.xml + + + dependencies + + + + + + + + module + intellij.profiler.ultimate + + + + name + intellij.javaee.appServers.tomcat + + + package + + + + descriptor + plugins/tomcat/resources/META-INF/plugin.xml + + + content + + + + + + + + name + intellij.javaee.appServers.tomcat/profiler + + + package + org.jetbrains.idea.tomcat.profiler + + + descriptor + plugins/tomcat/resources/intellij.javaee.appServers.tomcat.profiler.xml + + + dependencies + + + + + + + + module + intellij.profiler.ultimate + + + + name + intellij.javaee.beanValidation + + + package + com.intellij.beanValidation + + + descriptor + plugins/javaee/bean-validation/bv-core/resources/META-INF/plugin.xml + + + + name + intellij.javaee.cdi + + + package + com.intellij.cdi + + + descriptor + plugins/javaee/cdi/cdi-core/resources/META-INF/plugin.xml + + + + name + intellij.javaee.ejb.impl + + + package + com.intellij.javaee.ejb + + + descriptor + plugins/javaee/ejb/ejb-impl/resources/META-INF/plugin.xml + + + + name + intellij.javaee.jax.rs + + + package + com.intellij.ws.rest + + + descriptor + plugins/javaee/web-services/rs/resources/META-INF/plugin.xml + + + + name + intellij.javaee.jax.ws + + + package + com.intellij.ws.xml + + + descriptor + plugins/javaee/web-services/ws/resources/META-INF/plugin.xml + + + + name + intellij.javaee.jpa.impl + + + package + com.intellij.jpa + + + descriptor + plugins/javaee/jpa/jpa-impl/resources/META-INF/plugin.xml + + + + name + intellij.javaee.jsf + + + package + com.intellij.jsf + + + descriptor + plugins/javaee/jsf/jsf-core/src/META-INF/plugin.xml + + + + name + intellij.javaee.web.impl + + + package + com.intellij.javaee.web + + + descriptor + plugins/javaee/web/web-impl/resources/META-INF/plugin.xml + + + + name + intellij.javascript.impl + + + package + + + + descriptor + plugins/JavaScriptLanguage/src/META-INF/plugin.xml + + + content + + + + + + + + name + intellij.javascript.impl/diagrams + + + package + com.intellij.lang.javascript.modules.diagram + + + descriptor + plugins/JavaScriptLanguage/src/intellij.javascript.impl.diagrams.xml + + + dependencies + + + + + + + + plugin + com.intellij.diagram + + + + name + intellij.jboss.arquillian + + + package + com.intellij.plugins.jboss.arquillian + + + descriptor + plugins/frameworks/jboss/arquillian/arquillian-core/src/META-INF/plugin.xml + + + + name + intellij.jboss.jbpm + + + package + com.intellij.jboss.bpmn + + + descriptor + plugins/frameworks/jboss/bpmn/jbpm/resources/META-INF/plugin.xml + + + + name + intellij.less + + + package + org.jetbrains.plugins.less + + + descriptor + plugins/less/resources/META-INF/plugin.xml + + + + name + intellij.maven.ext + + + package + + + + descriptor + plugins/maven-ext/src/main/resources/META-INF/plugin.xml + + + content + + + + + + + + name + intellij.maven.ext/profiler + + + package + org.jetbrains.idea.maven.ext.profiler + + + descriptor + plugins/maven-ext/src/main/resources/intellij.maven.ext.profiler.xml + + + dependencies + + + + + + + + module + intellij.profiler.ultimate + + + + name + intellij.micronaut + + + package + com.intellij.micronaut + + + descriptor + plugins/frameworks/micronaut/micronaut-core/resources/META-INF/plugin.xml + + + + name + intellij.nodeJS.remoteInterpreter + + + package + com.jetbrains.nodejs.remote + + + descriptor + plugins/NodeJS/node-remote-interpreter/resources/META-INF/plugin.xml + + + + name + intellij.php.docker + + + package + com.jetbrains.php.remote.docker + + + descriptor + phpstorm/phpstorm-docker/resources/META-INF/plugin.xml + + + dependencies + + + + + + + + module + intellij.clouds.docker.remoteRun + + + + name + intellij.phpspec + + + package + com.jetbrains.php.phpspec + + + descriptor + phpstorm/phpspec/resources/META-INF/plugin.xml + + + + name + intellij.play + + + package + com.intellij.play + + + descriptor + plugins/frameworks/play/resources/META-INF/plugin.xml + + + + name + intellij.properties + + + package + com.intellij.lang.properties + + + descriptor + community/plugins/properties/src/META-INF/plugin.xml + + + + name + intellij.protoeditor + + + package + com.intellij.protobuf + + + descriptor + contrib/protobuf/resources/META-INF/plugin.xml + + + + name + intellij.puppet + + + package + com.intellij.lang.puppet + + + descriptor + plugins/puppet/resources/META-INF/plugin.xml + + + + name + intellij.quarkus + + + package + com.intellij.quarkus + + + descriptor + plugins/frameworks/quarkus/quarkus-core/resources/META-INF/plugin.xml + + + + name + intellij.reactivestreams.core + + + package + com.intellij.reactivestreams + + + descriptor + plugins/frameworks/reactive/reactive-streams-core/resources/META-INF/plugin.xml + + + + name + intellij.remoteRun + + + package + com.jetbrains.plugins.remotesdk + + + descriptor + plugins/remote-run/resources/META-INF/plugin.xml + + + + name + intellij.ruby.plugin + + + package + + + + descriptor + ruby/pluginResources/META-INF/plugin.xml + + + content + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + name + intellij.ruby.copyright + + + package + org.jetbrains.plugins.ruby.copyright + + + descriptor + ruby/ruby-copyright/resources/intellij.ruby.copyright.xml + + + dependencies + + + + + + + + plugin + com.intellij.copyright + + + + name + intellij.ruby.coverage + + + package + org.jetbrains.plugins.ruby.coverage + + + descriptor + ruby/coverage/ruby-coverage-common/resources/intellij.ruby.coverage.xml + + + dependencies + + + + + + + + plugin + com.intellij.modules.coverage + + + + name + intellij.ruby.database + + + package + org.jetbrains.plugins.ruby.rails.database.impl + + + descriptor + ruby/ruby-database/resources/intellij.ruby.database.xml + + + dependencies + + + + + + + + plugin + com.intellij.modules.database + + + + name + intellij.ruby.puppet.shared + + + package + com.intellij.lang.puppet.rubyShared + + + descriptor + ruby/ruby-puppet-shared/src/intellij.ruby.puppet.shared.xml + + + + name + intellij.ruby.java + + + package + org.jetbrains.plugins.ruby.java + + + descriptor + ruby/ruby-java/src/intellij.ruby.java.xml + + + dependencies + + + + + + + + plugin + com.intellij.java + + + + name + intellij.ruby.puppet.java + + + package + com.intellij.lang.puppet.ruby.java.ide + + + descriptor + ruby/ruby-puppet-java/resources/intellij.ruby.puppet.java.xml + + + dependencies + + + + + + + + + + + + plugin + com.intellij.java + + + + plugin + com.intellij.lang.puppet + + + + module + intellij.ruby.puppet.shared + + + + name + intellij.ruby.cucumber + + + package + org.jetbrains.plugins.ruby.cucumber + + + descriptor + ruby/ruby-cucumber/resources/intellij.ruby.cucumber.xml + + + dependencies + + + + + + + + plugin + gherkin + + + + name + intellij.ruby.uml + + + package + com.intellij.diagram.ruby + + + descriptor + ruby/ruby-uml/resources/intellij.ruby.uml.xml + + + dependencies + + + + + + + + plugin + com.intellij.diagram + + + + name + intellij.ruby.wsl + + + package + com.intellij.wsl.remote.ruby + + + descriptor + ruby/ruby-wsl/resources/intellij.ruby.wsl.xml + + + dependencies + + + + + + + + + + plugin + org.jetbrains.plugins.wsl + + + + module + intellij.ruby.remoteInterpreter + + + + name + intellij.ruby.remoteInterpreter + + + package + org.jetbrains.plugins.ruby.remote.impl + + + descriptor + ruby/remote-interpreter/resources/intellij.ruby.remoteInterpreter.xml + + + dependencies + + + + + + + + plugin + org.jetbrains.plugins.remote-run + + + + name + intellij.ruby.docker + + + package + com.intellij.docker.remote.ruby + + + descriptor + ruby/ruby-docker/resources/intellij.ruby.docker.xml + + + dependencies + + + + + + + + + + + + plugin + Docker + + + + module + intellij.ruby.remoteInterpreter + + + + module + intellij.ruby.coverage + + + + name + intellij.ruby.terminal + + + package + org.jetbrains.plugins.ruby.terminal + + + descriptor + ruby/ruby-terminal/resources/intellij.ruby.terminal.xml + + + dependencies + + + + + + + + plugin + org.jetbrains.plugins.terminal + + + + name + intellij.ruby.puppet + + + package + com.intellij.lang.puppet.ruby + + + descriptor + ruby/ruby-puppet/resources/intellij.ruby.puppet.xml + + + dependencies + + + + + + + + + + plugin + com.intellij.lang.puppet + + + + module + intellij.ruby.puppet.shared + + + + name + intellij.ruby.performanceTesting + + + package + org.jetbrains.ruby.performanceTesting + + + descriptor + ruby/performanceTesting/src/intellij.ruby.performanceTesting.xml + + + dependencies + + + + + + + + plugin + com.jetbrains.performancePlugin + + + + name + intellij.ruby.intelliLang + + + package + org.jetbrains.plugins.ruby.ruby.intelliLang + + + descriptor + ruby/ruby-intelliLang/resources/intellij.ruby.intelliLang.xml + + + dependencies + + + + + + + + plugin + org.intellij.intelliLang + + + + name + intellij.ruby.featuresTrainer + + + package + org.jetbrains.ruby.ift + + + descriptor + contrib/ide-features-trainer/ruby-features-trainer/resources/intellij.ruby.featuresTrainer.xml + + + dependencies + + + + + + + + plugin + training + + + + name + intellij.ruby.haml + + + package + org.jetbrains.plugins.haml.ruby + + + descriptor + ruby/ruby-haml/resources/intellij.ruby.haml.xml + + + dependencies + + + + + + + + plugin + org.jetbrains.plugins.haml + + + + name + intellij.ruby.slim + + + package + org.jetbrains.plugins.slim.ruby + + + descriptor + ruby/ruby-slim/resources/intellij.ruby.slim.xml + + + dependencies + + + + + + + + plugin + org.jetbrains.plugins.slim + + + + name + intellij.safe.push + + + package + com.intellij.safepush + + + descriptor + plugins/safe-push/resources/META-INF/plugin.xml + + + + name + intellij.selenium + + + package + com.intellij.selenium + + + descriptor + plugins/frameworks/selenium/selenium/resources/META-INF/plugin.xml + + + content + + + + + + + + + + + + name + intellij.selenium.jvm + + + package + com.intellij.selenium.jvm + + + descriptor + plugins/frameworks/selenium/selenium-jvm/resources/intellij.selenium.jvm.xml + + + dependencies + + + + + + + + + + + + + + + + plugin + com.intellij.java + + + + plugin + com.intellij.modules.ultimate + + + + plugin + com.intellij.modules.json + + + + plugin + org.intellij.intelliLang + + + + plugin + com.intellij.properties + + + + name + intellij.selenium.docker + + + package + com.intellij.selenium.docker + + + descriptor + plugins/frameworks/selenium/selenium-docker/resources/intellij.selenium.docker.xml + + + dependencies + + + + + + + + plugin + Docker + + + + name + intellij.selenium.python + + + package + com.intellij.selenium.python + + + descriptor + plugins/frameworks/selenium/selenium-python/resources/intellij.selenium.python.xml + + + + name + intellij.sh + + + package + com.intellij.sh + + + descriptor + community/plugins/sh/resources/META-INF/plugin.xml + + + + name + intellij.slim + + + package + org.jetbrains.plugins.slim + + + descriptor + plugins/slim-lang/resources/META-INF/plugin.xml + + + + name + intellij.space + + + package + com.intellij.space + + + descriptor + plugins/space/src/main/resources/META-INF/plugin.xml + + + content + + + + + + + + + + name + intellij.space.kotlin + + + package + com.intellij.space.kotlin + + + descriptor + plugins/space/kotlin/resources/intellij.space.kotlin.xml + + + dependencies + + + + + + + + plugin + org.jetbrains.kotlin + + + + name + intellij.space.index + + + package + com.intellij.space.index + + + descriptor + plugins/space/index/resources/intellij.space.index.xml + + + dependencies + + + + + + + + plugin + intellij.indexing.shared + + + + name + intellij.spring.batch + + + package + com.intellij.spring.batch + + + descriptor + plugins/spring/spring-batch/resources/META-INF/plugin.xml + + + + name + intellij.spring.boot.core + + + package + com.intellij.spring.boot + + + descriptor + plugins/spring/spring-boot/spring-boot-core/resources/META-INF/plugin.xml + + + + name + intellij.spring.core + + + package + com.intellij.spring + + + descriptor + plugins/spring/spring-framework/spring-core/resources/META-INF/plugin.xml + + + + name + intellij.spring.data + + + package + com.intellij.spring.data + + + descriptor + plugins/spring/spring-data/resources/META-INF/plugin.xml + + + + name + intellij.spring.integration.core + + + package + com.intellij.spring.integration + + + descriptor + plugins/spring/spring-integration/spring-integration-core/resources/META-INF/plugin.xml + + + + name + intellij.spring.mvc.impl + + + package + com.intellij.spring.mvc + + + descriptor + plugins/spring/spring-mvc-support/spring-mvc/resources/META-INF/plugin.xml + + + + name + intellij.spring.security + + + package + com.intellij.spring.security + + + descriptor + plugins/spring/spring-security/resources/META-INF/plugin.xml + + + + name + intellij.spring.webflow + + + package + com.intellij.spring.webflow + + + descriptor + plugins/spring/spring-webflow/resources/META-INF/plugin.xml + + + + name + intellij.spring.websocket + + + package + com.intellij.spring.websocket + + + descriptor + plugins/spring/spring-websocket/resources/META-INF/plugin.xml + + + + name + intellij.struts2 + + + package + com.intellij.struts2 + + + descriptor + contrib/struts2/plugin/resources/META-INF/plugin.xml + + + + name + intellij.swagger + + + package + com.intellij.swagger + + + descriptor + plugins/frameworks/swagger/swagger-core/resources/META-INF/plugin.xml + + + content + + + + + + + + name + intellij.swagger/endpoints + + + package + com.intellij.swagger.providers.endpoints + + + descriptor + plugins/frameworks/swagger/swagger-core/resources/intellij.swagger.endpoints.xml + + + dependencies + + + + + + + + plugin + com.intellij.microservices.ui + + + + name + intellij.thymeleaf + + + package + com.intellij.thymeleaf + + + descriptor + plugins/frameworks/thymeleaf/thymeleaf-core/resources/META-INF/plugin.xml + + + + name + intellij.vcs.git + + + package + git4idea + + + descriptor + community/plugins/git4idea/resources/META-INF/plugin.xml + + + + name + intellij.vcs.github + + + package + org.jetbrains.plugins.github + + + descriptor + community/plugins/github/resources/META-INF/plugin.xml + + + + name + intellij.vuejs + + + package + org.jetbrains.vuejs + + + descriptor + contrib/vuejs/resources/META-INF/plugin.xml + + + + name + intellij.w3validators + + + package + org.jetbrains.w3validators + + + descriptor + plugins/w3validators/src/META-INF/plugin.xml + + + + name + intellij.wsl + + + package + com.intellij.wsl + + + descriptor + plugins/WSL/resources/META-INF/plugin.xml + + + + name + intellij.wsl.fs.helper + + + package + com.intellij.wsl.fs + + + descriptor + plugins/wsl-file-system-helper/resources/META-INF/plugin.xml + + + + name + intellij.yaml + + + package + org.jetbrains.yaml + + + descriptor + community/plugins/yaml/resources/META-INF/plugin.xml + + + dependencies + + + + + + + + plugin + com.intellij.modules.lang + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/out/plugin-model.svg b/docs/out/plugin-model.svg new file mode 100644 index 000000000000..cadcc55160ad --- /dev/null +++ b/docs/out/plugin-model.svg @@ -0,0 +1,118 @@ + + + + + + + + + + + + + + + content + + + + dependencies + + + + dependencies + + + module name="…" 0…N + + + module name="…" N + + + content + is not allowed and is not supported for module. + + + Module dependency is always optional. + If the module depends on an unavailable plugin, it will not be loaded. + + module name="…" 0…N + + plugin id="…" 0…N + + + The dependency is specified in a module descriptor itself in a new model, + not where the module is referenced. + + module name="…" 0…N + + plugin id="…" 0…N + + + + plugin + + + + Every plugin is a module, but not every module is a plugin. + A plugin is a group of related modules and for now, + it is the only way to distribute modules. + + + Same as for + module + + + a sibling cannot access classes from each other + unless specified as a dependency + + + + + + + a module can access classes from a containing plugin + + + + + + + + + + + + + diff --git a/docs/out/projectClose-dispose-flow.svg b/docs/out/projectClose-dispose-flow.svg new file mode 100644 index 000000000000..64afe2bd62d3 --- /dev/null +++ b/docs/out/projectClose-dispose-flow.svg @@ -0,0 +1,175 @@ + + + + + + + + + + + + + + Project closing executed in a dispatch thread. + + closeProject + + canClose + (ep=ProjectCloseHandler) + + canClose + + (ep=ProjectCloseHandler) + yes + no + + Stop Service Preloading + + + In unit test mode in a light tests, + light project is not closed and not disposed. + + Fire + projectClosingBeforeSave + Event + (l=ProjectManagerListener) + + Save Project Files + + Save Project Settings + Successfully or Error Ignored + Error Occurred + + Fire + projectClosing + Event + (l=ProjectManagerListener) + + + write action + + + If you incorrectly specify project for + MessageBus.connect() + , + it will be disconnected on this step. + Do not specify + parentDisposable + unless needed. + + Dispose everything that uses Project as parent disposable + + Dispose Project Message Bus Connections + without explicitly specified parent disposable + + + Getting services and publishing to message bus + is prohibited from now on. + Project.isDisposed + returns + true + (not changed in a read action, + because state is set in a write action). + + Set Project State to + DISPOSE_IN_PROGRESS + + + Connecting to message bus is prohibited from now on. + + Dispose Project Message Bus Connection Disposable + + + Result of + ProjectManager.getOpenProjects() + is valid only in a read action. + + Remove Project from List of Opened + + Fire + projectClosed + Event + (l=ProjectManagerListener) + + Set Project State to + DISPOSED + + + First created, last disposed. + Children are disposed before parent. + + Dispose Services and Components + + Dispose Message Bus + + Set Project State to + DISPOSE_COMPLETED + + Close Cancelled + + Close Cancelled + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/plugin-graph.puml b/docs/plugin-graph.puml new file mode 100644 index 000000000000..7be0f6b4fbb7 --- /dev/null +++ b/docs/plugin-graph.puml @@ -0,0 +1,732 @@ +@startjson +{ + "intellij.angularJS" : { + "name" : "intellij.angularJS", + "package" : null, + "descriptor" : "contrib/AngularJS/resources/META-INF/plugin.xml", + "content" : [ { + "name" : "intellij.angularJS/diagram", + "package" : "org.angularjs.diagram", + "descriptor" : "contrib/AngularJS/resources/intellij.angularJS.diagram.xml", + "dependencies" : [ { + "plugin" : "com.intellij.diagram" + }, { + "module" : "intellij.javascript.impl/diagrams" + } ] + } ] + }, + "intellij.cloudFormation" : { + "name" : "intellij.cloudFormation", + "package" : "com.intellij.aws.cloudformation", + "descriptor" : "contrib/CloudFormation/src/main/resources/META-INF/plugin.xml" + }, + "intellij.clouds.docker.impl" : { + "name" : "intellij.clouds.docker.impl", + "package" : "com.intellij.docker", + "descriptor" : "plugins/Docker/resources/META-INF/plugin.xml", + "content" : [ { + "name" : "intellij.clouds.docker.java", + "package" : "com.intellij.docker.java", + "descriptor" : "plugins/Docker/Docker-java-impl/resources/intellij.clouds.docker.java.xml", + "dependencies" : [ { + "plugin" : "com.intellij.java" + } ] + }, { + "name" : "intellij.clouds.docker.compose", + "package" : "com.intellij.docker.composeFile", + "descriptor" : "plugins/Docker/Docker-compose/resources/intellij.clouds.docker.compose.xml", + "dependencies" : [ { + "plugin" : "org.jetbrains.plugins.yaml" + }, { + "plugin" : "Docker" + } ] + }, { + "name" : "intellij.clouds.docker.file", + "package" : "com.intellij.docker.dockerFile", + "descriptor" : "plugins/Docker/Docker-file/resources/intellij.clouds.docker.file.xml" + }, { + "name" : "intellij.clouds.docker.remoteRun", + "package" : "com.intellij.docker.remote", + "descriptor" : "plugins/Docker/Docker-remote-run/resources/intellij.clouds.docker.remoteRun.xml" + } ], + "dependencies" : [ { + "plugin" : "com.intellij.modules.lang" + }, { + "plugin" : "com.intellij.modules.json" + }, { + "plugin" : "com.intellij.modules.remoteServers" + } ] + }, + "intellij.clouds.kubernetes" : { + "name" : "intellij.clouds.kubernetes", + "package" : "com.intellij.kubernetes", + "descriptor" : "plugins/kubernetes/resources/META-INF/plugin.xml", + "dependencies" : [ { + "module" : "intellij.clouds.docker.compose" + } ] + }, + "intellij.coffeescript" : { + "name" : "intellij.coffeescript", + "package" : "org.coffeescript", + "descriptor" : "plugins/coffeescript/coffeescript-core/resources/META-INF/plugin.xml" + }, + "intellij.configurationScript" : { + "name" : "intellij.configurationScript", + "package" : "com.intellij.configurationScript", + "descriptor" : "community/plugins/configuration-script/resources/META-INF/plugin.xml" + }, + "intellij.diagram.impl" : { + "name" : "intellij.diagram.impl", + "package" : "com.intellij.uml", + "descriptor" : "plugins/uml/resources/META-INF/plugin.xml" + }, + "intellij.fileWatcher" : { + "name" : "intellij.fileWatcher", + "package" : "com.intellij.plugins.watcher", + "descriptor" : "plugins/fileWatcher/resources/META-INF/plugin.xml" + }, + "intellij.freemarker" : { + "name" : "intellij.freemarker", + "package" : "com.intellij.freemarker", + "descriptor" : "plugins/freemarker/core/resources/META-INF/plugin.xml" + }, + "intellij.go.ide" : { + "name" : "intellij.go.ide", + "package" : "com.goide.ide", + "descriptor" : "goland/intellij-go/ide/resources/META-INF/plugin.xml" + }, + "intellij.go.plugin" : { + "name" : "intellij.go.plugin", + "package" : "com.goide", + "descriptor" : "goland/intellij-go/plugin/resources/META-INF/plugin.xml" + }, + "intellij.go.sharedIndexes.bundled" : { + "name" : "intellij.go.sharedIndexes.bundled", + "package" : "com.goide.index.shared.bundled", + "descriptor" : "goland/intellij-go/go-shared-indexes-bundled/resources/META-INF/plugin.xml" + }, + "intellij.go.template" : { + "name" : "intellij.go.template", + "package" : "com.goide.template", + "descriptor" : "goland/intellij-go/template/resources/META-INF/plugin.xml" + }, + "intellij.gradle.ext" : { + "name" : "intellij.gradle.ext", + "package" : null, + "descriptor" : "plugins/gradle-ext/src/main/resources/META-INF/plugin.xml", + "content" : [ { + "name" : "intellij.gradle.ext/profiler", + "package" : "org.jetbrains.idea.gradle.ext.profiler", + "descriptor" : "plugins/gradle-ext/src/main/resources/intellij.gradle.ext.profiler.xml", + "dependencies" : [ { + "module" : "intellij.profiler.ultimate" + } ] + } ] + }, + "intellij.gradle.javaee" : { + "name" : "intellij.gradle.javaee", + "package" : "com.intellij.javaee.gradle", + "descriptor" : "plugins/javaee/gradle-integration/resources/META-INF/plugin.xml", + "content" : [ { + "name" : "intellij.gradle.javaee/javaee", + "package" : "com.intellij.javaee.gradle.javaee", + "descriptor" : "plugins/javaee/gradle-integration/resources/intellij.gradle.javaee.javaee.xml", + "dependencies" : [ { + "plugin" : "com.intellij.javaee" + } ] + }, { + "name" : "intellij.gradle.javaee/javaee.web", + "package" : "com.intellij.javaee.gradle.javaeeWeb", + "descriptor" : "plugins/javaee/gradle-integration/resources/intellij.gradle.javaee.javaee.web.xml", + "dependencies" : [ { + "plugin" : "com.intellij.javaee.web" + } ] + } ], + "dependencies" : [ { + "plugin" : "org.jetbrains.plugins.gradle" + }, { + "plugin" : "com.intellij.javaee" + } ] + }, + "intellij.grazie" : { + "name" : "intellij.grazie", + "package" : "com.intellij.grazie", + "descriptor" : "community/plugins/grazie/resources/META-INF/plugin.xml", + "content" : [ { + "name" : "intellij.grazie.java", + "package" : "com.intellij.grazie.ide.language.java", + "descriptor" : "community/plugins/grazie/java/src/main/resources/intellij.grazie.java.xml", + "dependencies" : [ { + "plugin" : "com.intellij.java" + } ] + }, { + "name" : "intellij.grazie.json", + "package" : "com.intellij.grazie.ide.language.json", + "descriptor" : "community/plugins/grazie/json/src/main/resources/intellij.grazie.json.xml", + "dependencies" : [ { + "plugin" : "com.intellij.modules.json" + } ] + }, { + "name" : "intellij.grazie.markdown", + "package" : "com.intellij.grazie.ide.language.markdown", + "descriptor" : "community/plugins/grazie/markdown/src/main/resources/intellij.grazie.markdown.xml", + "dependencies" : [ { + "plugin" : "org.intellij.plugins.markdown" + } ] + }, { + "name" : "intellij.grazie.properties", + "package" : "com.intellij.grazie.ide.language.properties", + "descriptor" : "community/plugins/grazie/properties/src/main/resources/intellij.grazie.properties.xml", + "dependencies" : [ { + "plugin" : "com.intellij.properties" + } ] + }, { + "name" : "intellij.grazie.xml", + "package" : "com.intellij.grazie.ide.language.xml", + "descriptor" : "community/plugins/grazie/xml/main/resources/intellij.grazie.xml.xml", + "dependencies" : [ { + "plugin" : "com.intellij.modules.xml" + } ] + }, { + "name" : "intellij.grazie.yaml", + "package" : "com.intellij.grazie.ide.language.yaml", + "descriptor" : "community/plugins/grazie/yaml/main/resources/intellij.grazie.yaml.xml", + "dependencies" : [ { + "plugin" : "org.jetbrains.plugins.yaml" + } ] + } ] + }, + "intellij.groovy" : { + "name" : "intellij.groovy", + "package" : "org.jetbrains.plugins.groovy", + "descriptor" : "community/plugins/groovy/src/META-INF/plugin.xml" + }, + "intellij.grpc" : { + "name" : "intellij.grpc", + "package" : "com.intellij.grpc", + "descriptor" : "plugins/frameworks/grpc/resources/META-INF/plugin.xml" + }, + "intellij.guice" : { + "name" : "intellij.guice", + "package" : "com.intellij.guice", + "descriptor" : "plugins/frameworks/Guice/resources/META-INF/plugin.xml" + }, + "intellij.haml" : { + "name" : "intellij.haml", + "package" : "org.jetbrains.plugins.haml", + "descriptor" : "plugins/haml/resources/META-INF/plugin.xml" + }, + "intellij.helidon" : { + "name" : "intellij.helidon", + "package" : "com.intellij.helidon", + "descriptor" : "plugins/frameworks/helidon/resources/META-INF/plugin.xml" + }, + "intellij.jade" : { + "name" : "intellij.jade", + "package" : "com.jetbrains.plugins.jade", + "descriptor" : "plugins/Jade/resources/META-INF/plugin.xml" + }, + "intellij.java.plugin" : { + "name" : "intellij.java.plugin", + "package" : null, + "descriptor" : "community/java/plugin/resources/META-INF/plugin.xml", + "content" : [ { + "name" : "intellij.profiler.ultimate", + "package" : "com.intellij.profiler.ultimate", + "descriptor" : "plugins/profiler/ultimate/resources/intellij.profiler.ultimate.xml", + "dependencies" : [ { + "plugin" : "com.intellij.modules.profiler" + } ] + } ] + }, + "intellij.javaFX.css" : { + "name" : "intellij.javaFX.css", + "package" : "org.jetbrains.plugins.javaFX", + "descriptor" : "plugins/javaFX-CSS/src/META-INF/plugin.xml" + }, + "intellij.javaHttpClients" : { + "name" : "intellij.javaHttpClients", + "package" : "com.intellij.javahttp", + "descriptor" : "plugins/frameworks/java-http-clients/resources/META-INF/plugin.xml" + }, + "intellij.javaee.app.servers.impl" : { + "name" : "intellij.javaee.app.servers.impl", + "package" : null, + "descriptor" : "plugins/javaee/core/javaee-app-server-integration/app-server-integration-impl/resources/META-INF/plugin.xml", + "content" : [ { + "name" : "intellij.javaee.app.servers.impl/profiler", + "package" : "com.intellij.javaee.appServers.run.profiler", + "descriptor" : "plugins/javaee/core/javaee-app-server-integration/app-server-integration-impl/resources/intellij.javaee.app.servers.impl.profiler.xml", + "dependencies" : [ { + "module" : "intellij.profiler.ultimate" + } ] + } ] + }, + "intellij.javaee.appServers.tomcat" : { + "name" : "intellij.javaee.appServers.tomcat", + "package" : null, + "descriptor" : "plugins/tomcat/resources/META-INF/plugin.xml", + "content" : [ { + "name" : "intellij.javaee.appServers.tomcat/profiler", + "package" : "org.jetbrains.idea.tomcat.profiler", + "descriptor" : "plugins/tomcat/resources/intellij.javaee.appServers.tomcat.profiler.xml", + "dependencies" : [ { + "module" : "intellij.profiler.ultimate" + } ] + } ] + }, + "intellij.javaee.beanValidation" : { + "name" : "intellij.javaee.beanValidation", + "package" : "com.intellij.beanValidation", + "descriptor" : "plugins/javaee/bean-validation/bv-core/resources/META-INF/plugin.xml" + }, + "intellij.javaee.cdi" : { + "name" : "intellij.javaee.cdi", + "package" : "com.intellij.cdi", + "descriptor" : "plugins/javaee/cdi/cdi-core/resources/META-INF/plugin.xml" + }, + "intellij.javaee.ejb.impl" : { + "name" : "intellij.javaee.ejb.impl", + "package" : "com.intellij.javaee.ejb", + "descriptor" : "plugins/javaee/ejb/ejb-impl/resources/META-INF/plugin.xml" + }, + "intellij.javaee.jax.rs" : { + "name" : "intellij.javaee.jax.rs", + "package" : "com.intellij.ws.rest", + "descriptor" : "plugins/javaee/web-services/rs/resources/META-INF/plugin.xml" + }, + "intellij.javaee.jax.ws" : { + "name" : "intellij.javaee.jax.ws", + "package" : "com.intellij.ws.xml", + "descriptor" : "plugins/javaee/web-services/ws/resources/META-INF/plugin.xml" + }, + "intellij.javaee.jpa.impl" : { + "name" : "intellij.javaee.jpa.impl", + "package" : "com.intellij.jpa", + "descriptor" : "plugins/javaee/jpa/jpa-impl/resources/META-INF/plugin.xml" + }, + "intellij.javaee.jsf" : { + "name" : "intellij.javaee.jsf", + "package" : "com.intellij.jsf", + "descriptor" : "plugins/javaee/jsf/jsf-core/src/META-INF/plugin.xml" + }, + "intellij.javaee.web.impl" : { + "name" : "intellij.javaee.web.impl", + "package" : "com.intellij.javaee.web", + "descriptor" : "plugins/javaee/web/web-impl/resources/META-INF/plugin.xml" + }, + "intellij.javascript.impl" : { + "name" : "intellij.javascript.impl", + "package" : null, + "descriptor" : "plugins/JavaScriptLanguage/src/META-INF/plugin.xml", + "content" : [ { + "name" : "intellij.javascript.impl/diagrams", + "package" : "com.intellij.lang.javascript.modules.diagram", + "descriptor" : "plugins/JavaScriptLanguage/src/intellij.javascript.impl.diagrams.xml", + "dependencies" : [ { + "plugin" : "com.intellij.diagram" + } ] + } ] + }, + "intellij.jboss.arquillian" : { + "name" : "intellij.jboss.arquillian", + "package" : "com.intellij.plugins.jboss.arquillian", + "descriptor" : "plugins/frameworks/jboss/arquillian/arquillian-core/src/META-INF/plugin.xml" + }, + "intellij.jboss.jbpm" : { + "name" : "intellij.jboss.jbpm", + "package" : "com.intellij.jboss.bpmn", + "descriptor" : "plugins/frameworks/jboss/bpmn/jbpm/resources/META-INF/plugin.xml" + }, + "intellij.less" : { + "name" : "intellij.less", + "package" : "org.jetbrains.plugins.less", + "descriptor" : "plugins/less/resources/META-INF/plugin.xml" + }, + "intellij.maven.ext" : { + "name" : "intellij.maven.ext", + "package" : null, + "descriptor" : "plugins/maven-ext/src/main/resources/META-INF/plugin.xml", + "content" : [ { + "name" : "intellij.maven.ext/profiler", + "package" : "org.jetbrains.idea.maven.ext.profiler", + "descriptor" : "plugins/maven-ext/src/main/resources/intellij.maven.ext.profiler.xml", + "dependencies" : [ { + "module" : "intellij.profiler.ultimate" + } ] + } ] + }, + "intellij.micronaut" : { + "name" : "intellij.micronaut", + "package" : "com.intellij.micronaut", + "descriptor" : "plugins/frameworks/micronaut/micronaut-core/resources/META-INF/plugin.xml" + }, + "intellij.nodeJS.remoteInterpreter" : { + "name" : "intellij.nodeJS.remoteInterpreter", + "package" : "com.jetbrains.nodejs.remote", + "descriptor" : "plugins/NodeJS/node-remote-interpreter/resources/META-INF/plugin.xml" + }, + "intellij.php.docker" : { + "name" : "intellij.php.docker", + "package" : "com.jetbrains.php.remote.docker", + "descriptor" : "phpstorm/phpstorm-docker/resources/META-INF/plugin.xml", + "dependencies" : [ { + "module" : "intellij.clouds.docker.remoteRun" + } ] + }, + "intellij.phpspec" : { + "name" : "intellij.phpspec", + "package" : "com.jetbrains.php.phpspec", + "descriptor" : "phpstorm/phpspec/resources/META-INF/plugin.xml" + }, + "intellij.play" : { + "name" : "intellij.play", + "package" : "com.intellij.play", + "descriptor" : "plugins/frameworks/play/resources/META-INF/plugin.xml" + }, + "intellij.properties" : { + "name" : "intellij.properties", + "package" : "com.intellij.lang.properties", + "descriptor" : "community/plugins/properties/src/META-INF/plugin.xml" + }, + "intellij.protoeditor" : { + "name" : "intellij.protoeditor", + "package" : "com.intellij.protobuf", + "descriptor" : "contrib/protobuf/resources/META-INF/plugin.xml" + }, + "intellij.puppet" : { + "name" : "intellij.puppet", + "package" : "com.intellij.lang.puppet", + "descriptor" : "plugins/puppet/resources/META-INF/plugin.xml" + }, + "intellij.quarkus" : { + "name" : "intellij.quarkus", + "package" : "com.intellij.quarkus", + "descriptor" : "plugins/frameworks/quarkus/quarkus-core/resources/META-INF/plugin.xml" + }, + "intellij.reactivestreams.core" : { + "name" : "intellij.reactivestreams.core", + "package" : "com.intellij.reactivestreams", + "descriptor" : "plugins/frameworks/reactive/reactive-streams-core/resources/META-INF/plugin.xml" + }, + "intellij.remoteRun" : { + "name" : "intellij.remoteRun", + "package" : "com.jetbrains.plugins.remotesdk", + "descriptor" : "plugins/remote-run/resources/META-INF/plugin.xml" + }, + "intellij.ruby.plugin" : { + "name" : "intellij.ruby.plugin", + "package" : null, + "descriptor" : "ruby/pluginResources/META-INF/plugin.xml", + "content" : [ { + "name" : "intellij.ruby.copyright", + "package" : "org.jetbrains.plugins.ruby.copyright", + "descriptor" : "ruby/ruby-copyright/resources/intellij.ruby.copyright.xml", + "dependencies" : [ { + "plugin" : "com.intellij.copyright" + } ] + }, { + "name" : "intellij.ruby.coverage", + "package" : "org.jetbrains.plugins.ruby.coverage", + "descriptor" : "ruby/coverage/ruby-coverage-common/resources/intellij.ruby.coverage.xml", + "dependencies" : [ { + "plugin" : "com.intellij.modules.coverage" + } ] + }, { + "name" : "intellij.ruby.database", + "package" : "org.jetbrains.plugins.ruby.rails.database.impl", + "descriptor" : "ruby/ruby-database/resources/intellij.ruby.database.xml", + "dependencies" : [ { + "plugin" : "com.intellij.modules.database" + } ] + }, { + "name" : "intellij.ruby.puppet.shared", + "package" : "com.intellij.lang.puppet.rubyShared", + "descriptor" : "ruby/ruby-puppet-shared/src/intellij.ruby.puppet.shared.xml" + }, { + "name" : "intellij.ruby.java", + "package" : "org.jetbrains.plugins.ruby.java", + "descriptor" : "ruby/ruby-java/src/intellij.ruby.java.xml", + "dependencies" : [ { + "plugin" : "com.intellij.java" + } ] + }, { + "name" : "intellij.ruby.puppet.java", + "package" : "com.intellij.lang.puppet.ruby.java.ide", + "descriptor" : "ruby/ruby-puppet-java/resources/intellij.ruby.puppet.java.xml", + "dependencies" : [ { + "plugin" : "com.intellij.java" + }, { + "plugin" : "com.intellij.lang.puppet" + }, { + "module" : "intellij.ruby.puppet.shared" + } ] + }, { + "name" : "intellij.ruby.cucumber", + "package" : "org.jetbrains.plugins.ruby.cucumber", + "descriptor" : "ruby/ruby-cucumber/resources/intellij.ruby.cucumber.xml", + "dependencies" : [ { + "plugin" : "gherkin" + } ] + }, { + "name" : "intellij.ruby.uml", + "package" : "com.intellij.diagram.ruby", + "descriptor" : "ruby/ruby-uml/resources/intellij.ruby.uml.xml", + "dependencies" : [ { + "plugin" : "com.intellij.diagram" + } ] + }, { + "name" : "intellij.ruby.wsl", + "package" : "com.intellij.wsl.remote.ruby", + "descriptor" : "ruby/ruby-wsl/resources/intellij.ruby.wsl.xml", + "dependencies" : [ { + "plugin" : "org.jetbrains.plugins.wsl" + }, { + "module" : "intellij.ruby.remoteInterpreter" + } ] + }, { + "name" : "intellij.ruby.remoteInterpreter", + "package" : "org.jetbrains.plugins.ruby.remote.impl", + "descriptor" : "ruby/remote-interpreter/resources/intellij.ruby.remoteInterpreter.xml", + "dependencies" : [ { + "plugin" : "org.jetbrains.plugins.remote-run" + } ] + }, { + "name" : "intellij.ruby.docker", + "package" : "com.intellij.docker.remote.ruby", + "descriptor" : "ruby/ruby-docker/resources/intellij.ruby.docker.xml", + "dependencies" : [ { + "plugin" : "Docker" + }, { + "module" : "intellij.ruby.remoteInterpreter" + }, { + "module" : "intellij.ruby.coverage" + } ] + }, { + "name" : "intellij.ruby.terminal", + "package" : "org.jetbrains.plugins.ruby.terminal", + "descriptor" : "ruby/ruby-terminal/resources/intellij.ruby.terminal.xml", + "dependencies" : [ { + "plugin" : "org.jetbrains.plugins.terminal" + } ] + }, { + "name" : "intellij.ruby.puppet", + "package" : "com.intellij.lang.puppet.ruby", + "descriptor" : "ruby/ruby-puppet/resources/intellij.ruby.puppet.xml", + "dependencies" : [ { + "plugin" : "com.intellij.lang.puppet" + }, { + "module" : "intellij.ruby.puppet.shared" + } ] + }, { + "name" : "intellij.ruby.performanceTesting", + "package" : "org.jetbrains.ruby.performanceTesting", + "descriptor" : "ruby/performanceTesting/src/intellij.ruby.performanceTesting.xml", + "dependencies" : [ { + "plugin" : "com.jetbrains.performancePlugin" + } ] + }, { + "name" : "intellij.ruby.intelliLang", + "package" : "org.jetbrains.plugins.ruby.ruby.intelliLang", + "descriptor" : "ruby/ruby-intelliLang/resources/intellij.ruby.intelliLang.xml", + "dependencies" : [ { + "plugin" : "org.intellij.intelliLang" + } ] + }, { + "name" : "intellij.ruby.featuresTrainer", + "package" : "org.jetbrains.ruby.ift", + "descriptor" : "contrib/ide-features-trainer/ruby-features-trainer/resources/intellij.ruby.featuresTrainer.xml", + "dependencies" : [ { + "plugin" : "training" + } ] + }, { + "name" : "intellij.ruby.haml", + "package" : "org.jetbrains.plugins.haml.ruby", + "descriptor" : "ruby/ruby-haml/resources/intellij.ruby.haml.xml", + "dependencies" : [ { + "plugin" : "org.jetbrains.plugins.haml" + } ] + }, { + "name" : "intellij.ruby.slim", + "package" : "org.jetbrains.plugins.slim.ruby", + "descriptor" : "ruby/ruby-slim/resources/intellij.ruby.slim.xml", + "dependencies" : [ { + "plugin" : "org.jetbrains.plugins.slim" + } ] + } ] + }, + "intellij.safe.push" : { + "name" : "intellij.safe.push", + "package" : "com.intellij.safepush", + "descriptor" : "plugins/safe-push/resources/META-INF/plugin.xml" + }, + "intellij.selenium" : { + "name" : "intellij.selenium", + "package" : "com.intellij.selenium", + "descriptor" : "plugins/frameworks/selenium/selenium/resources/META-INF/plugin.xml", + "content" : [ { + "name" : "intellij.selenium.jvm", + "package" : "com.intellij.selenium.jvm", + "descriptor" : "plugins/frameworks/selenium/selenium-jvm/resources/intellij.selenium.jvm.xml", + "dependencies" : [ { + "plugin" : "com.intellij.java" + }, { + "plugin" : "com.intellij.modules.ultimate" + }, { + "plugin" : "com.intellij.modules.json" + }, { + "plugin" : "org.intellij.intelliLang" + }, { + "plugin" : "com.intellij.properties" + } ] + }, { + "name" : "intellij.selenium.docker", + "package" : "com.intellij.selenium.docker", + "descriptor" : "plugins/frameworks/selenium/selenium-docker/resources/intellij.selenium.docker.xml", + "dependencies" : [ { + "plugin" : "Docker" + } ] + }, { + "name" : "intellij.selenium.python", + "package" : "com.intellij.selenium.python", + "descriptor" : "plugins/frameworks/selenium/selenium-python/resources/intellij.selenium.python.xml" + } ] + }, + "intellij.sh" : { + "name" : "intellij.sh", + "package" : "com.intellij.sh", + "descriptor" : "community/plugins/sh/resources/META-INF/plugin.xml" + }, + "intellij.slim" : { + "name" : "intellij.slim", + "package" : "org.jetbrains.plugins.slim", + "descriptor" : "plugins/slim-lang/resources/META-INF/plugin.xml" + }, + "intellij.space" : { + "name" : "intellij.space", + "package" : "com.intellij.space", + "descriptor" : "plugins/space/src/main/resources/META-INF/plugin.xml", + "content" : [ { + "name" : "intellij.space.kotlin", + "package" : "com.intellij.space.kotlin", + "descriptor" : "plugins/space/kotlin/resources/intellij.space.kotlin.xml", + "dependencies" : [ { + "plugin" : "org.jetbrains.kotlin" + } ] + }, { + "name" : "intellij.space.index", + "package" : "com.intellij.space.index", + "descriptor" : "plugins/space/index/resources/intellij.space.index.xml", + "dependencies" : [ { + "plugin" : "intellij.indexing.shared" + } ] + } ] + }, + "intellij.spring.batch" : { + "name" : "intellij.spring.batch", + "package" : "com.intellij.spring.batch", + "descriptor" : "plugins/spring/spring-batch/resources/META-INF/plugin.xml" + }, + "intellij.spring.boot.core" : { + "name" : "intellij.spring.boot.core", + "package" : "com.intellij.spring.boot", + "descriptor" : "plugins/spring/spring-boot/spring-boot-core/resources/META-INF/plugin.xml" + }, + "intellij.spring.core" : { + "name" : "intellij.spring.core", + "package" : "com.intellij.spring", + "descriptor" : "plugins/spring/spring-framework/spring-core/resources/META-INF/plugin.xml" + }, + "intellij.spring.data" : { + "name" : "intellij.spring.data", + "package" : "com.intellij.spring.data", + "descriptor" : "plugins/spring/spring-data/resources/META-INF/plugin.xml" + }, + "intellij.spring.integration.core" : { + "name" : "intellij.spring.integration.core", + "package" : "com.intellij.spring.integration", + "descriptor" : "plugins/spring/spring-integration/spring-integration-core/resources/META-INF/plugin.xml" + }, + "intellij.spring.mvc.impl" : { + "name" : "intellij.spring.mvc.impl", + "package" : "com.intellij.spring.mvc", + "descriptor" : "plugins/spring/spring-mvc-support/spring-mvc/resources/META-INF/plugin.xml" + }, + "intellij.spring.security" : { + "name" : "intellij.spring.security", + "package" : "com.intellij.spring.security", + "descriptor" : "plugins/spring/spring-security/resources/META-INF/plugin.xml" + }, + "intellij.spring.webflow" : { + "name" : "intellij.spring.webflow", + "package" : "com.intellij.spring.webflow", + "descriptor" : "plugins/spring/spring-webflow/resources/META-INF/plugin.xml" + }, + "intellij.spring.websocket" : { + "name" : "intellij.spring.websocket", + "package" : "com.intellij.spring.websocket", + "descriptor" : "plugins/spring/spring-websocket/resources/META-INF/plugin.xml" + }, + "intellij.struts2" : { + "name" : "intellij.struts2", + "package" : "com.intellij.struts2", + "descriptor" : "contrib/struts2/plugin/resources/META-INF/plugin.xml" + }, + "intellij.swagger" : { + "name" : "intellij.swagger", + "package" : "com.intellij.swagger", + "descriptor" : "plugins/frameworks/swagger/swagger-core/resources/META-INF/plugin.xml", + "content" : [ { + "name" : "intellij.swagger/endpoints", + "package" : "com.intellij.swagger.providers.endpoints", + "descriptor" : "plugins/frameworks/swagger/swagger-core/resources/intellij.swagger.endpoints.xml", + "dependencies" : [ { + "plugin" : "com.intellij.microservices.ui" + } ] + } ] + }, + "intellij.thymeleaf" : { + "name" : "intellij.thymeleaf", + "package" : "com.intellij.thymeleaf", + "descriptor" : "plugins/frameworks/thymeleaf/thymeleaf-core/resources/META-INF/plugin.xml" + }, + "intellij.vcs.git" : { + "name" : "intellij.vcs.git", + "package" : "git4idea", + "descriptor" : "community/plugins/git4idea/resources/META-INF/plugin.xml" + }, + "intellij.vcs.github" : { + "name" : "intellij.vcs.github", + "package" : "org.jetbrains.plugins.github", + "descriptor" : "community/plugins/github/resources/META-INF/plugin.xml" + }, + "intellij.vuejs" : { + "name" : "intellij.vuejs", + "package" : "org.jetbrains.vuejs", + "descriptor" : "contrib/vuejs/resources/META-INF/plugin.xml" + }, + "intellij.w3validators" : { + "name" : "intellij.w3validators", + "package" : "org.jetbrains.w3validators", + "descriptor" : "plugins/w3validators/src/META-INF/plugin.xml" + }, + "intellij.wsl" : { + "name" : "intellij.wsl", + "package" : "com.intellij.wsl", + "descriptor" : "plugins/WSL/resources/META-INF/plugin.xml" + }, + "intellij.wsl.fs.helper" : { + "name" : "intellij.wsl.fs.helper", + "package" : "com.intellij.wsl.fs", + "descriptor" : "plugins/wsl-file-system-helper/resources/META-INF/plugin.xml" + }, + "intellij.yaml" : { + "name" : "intellij.yaml", + "package" : "org.jetbrains.yaml", + "descriptor" : "community/plugins/yaml/resources/META-INF/plugin.xml", + "dependencies" : [ { + "plugin" : "com.intellij.modules.lang" + } ] + } +} +@endjson \ No newline at end of file diff --git a/docs/plugin-model.puml b/docs/plugin-model.puml new file mode 100644 index 000000000000..af1ac8f5887a --- /dev/null +++ b/docs/plugin-model.puml @@ -0,0 +1,58 @@ +@startuml +!include jb-plantuml-theme.puml + +' https://www.augmentedmind.de/2021/01/17/plantuml-layout-tutorial-styles/ + +component "[[https://github.com/JetBrains/intellij-community/blob/master/platform/core-impl/src/com/intellij/ide/plugins/readme.md plugin]]" as P + +folder content as PluginContent { + [module name="…" 0…N] as M + [module name="…" N] as M_N + + + M .. M_N : a sibling cannot access classes from each other\n unless specified as a dependency + + note right of M : ""content"" is not allowed and is not supported for module. + + folder dependencies as ModuleDependencies { + (module name="…" 0…N) as MDM + (plugin id="…" 0…N) as MDP + + note right of MDM + The dependency is specified in a module descriptor itself in a new model, + not where the module is referenced. + end note + + ' force PlantUML to place `module` below of `plugin` + MDM -[hidden]d- MDP + } + + note bottom of ModuleDependencies + Module dependency is always optional. + If the module depends on an unavailable plugin, it will not be loaded. + end note + + M .> P : a module can access classes from a containing plugin + + M -down- ModuleDependencies +} + +note top of PluginContent + Every plugin is a module, but not every module is a plugin. + A plugin is a group of related modules and for now, + it is the only way to distribute modules. +end note + +folder dependencies as PluginDependencies { + (module name="…" 0…N) as PDM + (plugin id="…" 0…N) as PDP + + PDM -[hidden]d- PDP +} + +note top of PluginDependencies : Same as for ""module"" + +P -down- PluginContent +P -down- PluginDependencies + +@enduml \ No newline at end of file diff --git a/platform/core-impl/src/com/intellij/ide/plugins/readme.md b/docs/plugin.md similarity index 84% rename from platform/core-impl/src/com/intellij/ide/plugins/readme.md rename to docs/plugin.md index 37b595cc6254..94e64288f97d 100644 --- a/platform/core-impl/src/com/intellij/ide/plugins/readme.md +++ b/docs/plugin.md @@ -4,7 +4,7 @@ Currently, IJ IDEA module is not reflected in any way in a plugin — only as pa It is going to be changed — IJ Module will be integral part of plugin subsystem. Plugin descriptor will reference all modules that forms its content, and plugin descriptor itself it is a specific form of module descriptor. -Every plugin it is a module, but not every module it is a plugin. +Every plugin is a module, but not every module is a plugin. Term "optional descriptor" in a new format is equivalent to module dependency of plugin. ## The `package` attribute @@ -22,7 +22,7 @@ The `content` element determines content of the plugin. Plugin consists of modul ```xml - + ``` @@ -34,7 +34,6 @@ The `content` element determines content of the plugin. Plugin consists of modul | Attribute | Type | Use | Description | |-----------|--------|----------|-------------------------------------------------------------------------------------------------------------------------------------------| | name | string | required | The name of the module. | -| package | string | required | The package of the module. Duplicates the `package` specified in the referenced module, but for now it is required for technical reasons. | There is an important difference between content specified for a plugin and for a module. * For a plugin, the referenced module _is not added_ to classpath but just forms plugin content. Still, if some another plugin depends on this plugin, it can use classloader of that module. For time being, included into plugin module, doesn’t have own classloader, but it will be changed in the future and explicit dependency on plugin’s module must be added if needed. @@ -54,39 +53,25 @@ The `dependencies` element determines dependencies of the module. | module | 0 | unbounded | A module upon which a dependency should be added. | | plugin | 0 | unbounded | A plugin upon which a dependency should be added. | -### The `dependencies.plugin` element -| Attribute | Type | Use | Description | -|-----------|--------|----------|-----------------------| -| id | string | required | The id of the plugin. | - -Not used for now and not supported. [Marketplace](https://github.com/JetBrains/intellij-plugin-verifier/tree/master/intellij-plugin-structure) is not yet ready to support a new format. - -Old format - -```xml -Docker -``` - -is still used. - ### The `dependencies.module` element | Attribute | Type | Use | Description | |-----------|--------|----------|-------------------------| | name | string | required | The name of the module. | -The module must have descriptor file with the same name as module, e.g. for module `intellij.clouds.docker.compose` must be a descriptor file `intellij.clouds.docker.compose.xml` in the module root. +The module must have descriptor file with the same name as module, e.g., for module `intellij.clouds.docker.compose` must be a descriptor file `intellij.clouds.docker.compose.xml` in the module root. -Module dependency is always optional. If module depends on an unavailable plugin, it will be not loaded. +Module dependency is always optional. If the module depends on an unavailable module, it will not be loaded. In this meaning for now module dependency makes sense only for plugins, but not for modules. It will be changed in the future, when all plugins will act as a module. The module added to classpath of dependent module but dependency itself is not able to access the dependent module (as it was earlier). Currently, it is packaged into the same JAR, but packaging it is implementation details that are hidden and maybe changed in any moment or maybe different in a different execution environment. The same classloader configuration is guaranteed, but not packaging, and independence from packaging it is a one of the goal of a new descriptor format. -In the old plugin descriptor format: - * tag `depends` with `config-file` it is `dependency.module`. - * tag `depends` without `optional` it is `dependency.plugin`. +### The `dependencies.plugin` element +| Attribute | Type | Use | Description | +|-----------|--------|----------|-----------------------| +| id | string | required | The id of the plugin. | -Note: for now you also must specify dependency in an old format. +The `dependencies.plugin` element determines dependency on a plugin. ## InternalIgnoreDependencyViolation diff --git a/platform/docs/projectClose-dispose-flow.puml b/docs/projectClose-dispose-flow.puml similarity index 100% rename from platform/docs/projectClose-dispose-flow.puml rename to docs/projectClose-dispose-flow.puml diff --git a/docs/readme.md b/docs/readme.md new file mode 100644 index 000000000000..617c08919723 --- /dev/null +++ b/docs/readme.md @@ -0,0 +1,7 @@ +IntelliJ Platform SDK [docs](https://plugins.jetbrains.com/docs/intellij/) sources located on [GitHub](https://github.com/JetBrains/intellij-sdk-docs). +This directory contains activity or model diagrams sources, design docs. +### How to write diagram in PlantUML + +Install [PlantUML integration](https://plugins.jetbrains.com/plugin/7017-plantuml-integration/) plugin to add PlantUML support to IntelliJ IDEA. + +See [PlantUML](https://plantuml.com) docs. \ No newline at end of file diff --git a/platform/util/src/com/intellij/util/xmlb/annotations/readme.md b/docs/xml-serialization.md similarity index 100% rename from platform/util/src/com/intellij/util/xmlb/annotations/readme.md rename to docs/xml-serialization.md diff --git a/java/ide-resources/resources/META-INF/JavaIdePlugin.xml b/java/ide-resources/resources/META-INF/JavaIdePlugin.xml index e7a2fece2f84..0706fc7d5e54 100644 --- a/java/ide-resources/resources/META-INF/JavaIdePlugin.xml +++ b/java/ide-resources/resources/META-INF/JavaIdePlugin.xml @@ -14,29 +14,29 @@ ~ limitations under the License. --> - + - + - + - - + + - + - + - + diff --git a/java/idea-ui/testSrc/com/intellij/facet/mock/MockFacetDetector.java b/java/idea-ui/testSrc/com/intellij/facet/mock/MockFacetDetector.java index 8252c817d6b8..9fd63667e067 100644 --- a/java/idea-ui/testSrc/com/intellij/facet/mock/MockFacetDetector.java +++ b/java/idea-ui/testSrc/com/intellij/facet/mock/MockFacetDetector.java @@ -3,6 +3,7 @@ package com.intellij.facet.mock; import com.intellij.framework.detection.FacetBasedFrameworkDetector; import com.intellij.framework.detection.FileContentPattern; +import com.intellij.openapi.extensions.InternalIgnoreDependencyViolation; import com.intellij.openapi.fileTypes.FileType; import com.intellij.openapi.fileTypes.StdFileTypes; import com.intellij.openapi.roots.ModifiableRootModel; @@ -20,6 +21,7 @@ import java.util.List; import static com.intellij.patterns.StandardPatterns.string; +@InternalIgnoreDependencyViolation public final class MockFacetDetector extends FacetBasedFrameworkDetector { public static final String ROOT_TAG_NAME = "root"; public static final String ROOT_TAG = "<" + ROOT_TAG_NAME + "/>"; diff --git a/java/idea-ui/testSrc/com/intellij/facet/mock/MockSubFacetDetector.java b/java/idea-ui/testSrc/com/intellij/facet/mock/MockSubFacetDetector.java index 552a812e1166..fbae1793545b 100644 --- a/java/idea-ui/testSrc/com/intellij/facet/mock/MockSubFacetDetector.java +++ b/java/idea-ui/testSrc/com/intellij/facet/mock/MockSubFacetDetector.java @@ -6,6 +6,7 @@ import com.intellij.facet.FacetConfiguration; import com.intellij.facet.FacetType; import com.intellij.framework.detection.FacetBasedFrameworkDetector; import com.intellij.framework.detection.FileContentPattern; +import com.intellij.openapi.extensions.InternalIgnoreDependencyViolation; import com.intellij.openapi.fileTypes.FileType; import com.intellij.openapi.fileTypes.StdFileTypes; import com.intellij.openapi.util.Pair; @@ -20,7 +21,8 @@ import java.util.Set; import static com.intellij.patterns.StandardPatterns.string; -public class MockSubFacetDetector extends FacetBasedFrameworkDetector { +@InternalIgnoreDependencyViolation +public final class MockSubFacetDetector extends FacetBasedFrameworkDetector { public MockSubFacetDetector() { super("mock-sub-facet-detector"); } diff --git a/java/java-tests/testSrc/com/intellij/scopes/LibraryUseSearchUsingScopeEnlargerTest.java b/java/java-tests/testSrc/com/intellij/scopes/LibraryUseSearchUsingScopeEnlargerTest.java index fa14f451b489..330fdfe948af 100644 --- a/java/java-tests/testSrc/com/intellij/scopes/LibraryUseSearchUsingScopeEnlargerTest.java +++ b/java/java-tests/testSrc/com/intellij/scopes/LibraryUseSearchUsingScopeEnlargerTest.java @@ -7,6 +7,7 @@ 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; @@ -39,7 +40,6 @@ import java.util.Map; * For this test, we will enlarge the use scope to include libraries. */ public class LibraryUseSearchUsingScopeEnlargerTest extends JavaCodeInsightFixtureTestCase { - @Override protected void setUp() throws Exception { super.setUp(); @@ -103,7 +103,8 @@ public class LibraryUseSearchUsingScopeEnlargerTest extends JavaCodeInsightFixtu return false; } - public static class LibraryUseScopeEnlarger extends UseScopeEnlarger { + @InternalIgnoreDependencyViolation + final static class LibraryUseScopeEnlarger extends UseScopeEnlarger { @Override public @Nullable SearchScope getAdditionalUseScope(@NotNull PsiElement element) { return LibraryScopeCache.getInstance(element.getProject()).getLibrariesOnlyScope(); 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 9455b35e416c..95a7a141ee3e 100644 --- a/java/java-tests/testSrc/com/intellij/util/indexing/BrokenPluginIndexingTest.kt +++ b/java/java-tests/testSrc/com/intellij/util/indexing/BrokenPluginIndexingTest.kt @@ -2,6 +2,7 @@ package com.intellij.util.indexing import com.intellij.ide.plugins.loadExtensionWithText +import com.intellij.openapi.extensions.InternalIgnoreDependencyViolation import com.intellij.openapi.fileTypes.FileType import com.intellij.openapi.fileTypes.FileTypeRegistry import com.intellij.openapi.util.Disposer @@ -50,9 +51,10 @@ class BrokenPluginIndexingTest : JavaCodeInsightFixtureTestCase() { } } -class BrokenPluginException : RuntimeException() +internal class BrokenPluginException : RuntimeException() -class BrokenFileBasedIndexExtension : ScalarIndexExtension() { +@InternalIgnoreDependencyViolation +internal class BrokenFileBasedIndexExtension : ScalarIndexExtension() { override fun getIndexer() = DataIndexer { throw BrokenPluginException() } override fun getName() = INDEX_ID override fun getKeyDescriptor() = EnumeratorIntegerDescriptor.INSTANCE!! 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 dc09561092ba..0e19592817e0 100644 --- a/java/java-tests/testSrc/com/intellij/util/indexing/CountingFileBasedIndexExtension.kt +++ b/java/java-tests/testSrc/com/intellij/util/indexing/CountingFileBasedIndexExtension.kt @@ -1,12 +1,14 @@ // 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. package com.intellij.util.indexing +import com.intellij.openapi.extensions.InternalIgnoreDependencyViolation import com.intellij.openapi.vfs.VirtualFile import com.intellij.util.io.EnumeratorIntegerDescriptor import com.intellij.util.io.KeyDescriptor import java.util.concurrent.atomic.AtomicInteger -class CountingFileBasedIndexExtension : ScalarIndexExtension() { +@InternalIgnoreDependencyViolation +internal class CountingFileBasedIndexExtension : ScalarIndexExtension() { override fun getIndexer(): DataIndexer { return DataIndexer { COUNTER.incrementAndGet() 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 8cca64b45274..e01e214b1dcc 100644 --- a/java/java-tests/testSrc/com/intellij/util/indexing/IndexInfrastructureExtensionTest.kt +++ b/java/java-tests/testSrc/com/intellij/util/indexing/IndexInfrastructureExtensionTest.kt @@ -3,6 +3,7 @@ package com.intellij.util.indexing import com.intellij.ide.plugins.loadExtensionWithText import com.intellij.openapi.application.PathManager +import com.intellij.openapi.extensions.InternalIgnoreDependencyViolation import com.intellij.openapi.project.Project import com.intellij.openapi.util.Disposer import com.intellij.psi.stubs.StubIndexKey @@ -43,6 +44,7 @@ class IndexInfrastructureExtensionTest : LightJavaCodeInsightFixtureTestCase() { const val testInfraExtensionFile = "_test_extension" +@InternalIgnoreDependencyViolation class TestIndexInfrastructureExtension : FileBasedIndexInfrastructureExtension { override fun createFileIndexingStatusProcessor(project: Project): FileBasedIndexInfrastructureExtension.FileIndexingStatusProcessor? = null diff --git a/java/plugin/resources/META-INF/plugin.xml b/java/plugin/resources/META-INF/plugin.xml index 98045ff654aa..d7a73448dccd 100644 --- a/java/plugin/resources/META-INF/plugin.xml +++ b/java/plugin/resources/META-INF/plugin.xml @@ -13,7 +13,7 @@ com.intellij.modules.java-capable com.intellij.copyright - + @@ -26,7 +26,10 @@ - com.intellij.modules.profiler + + + + com.intellij.modules.structuralsearch com.intellij.modules.remoteServers training diff --git a/java/testFramework/src/com/intellij/codeInsight/completion/LightFixtureCompletionTestCase.java b/java/testFramework/src/com/intellij/codeInsight/completion/LightFixtureCompletionTestCase.java index dbeab175156b..205efad6ed8a 100644 --- a/java/testFramework/src/com/intellij/codeInsight/completion/LightFixtureCompletionTestCase.java +++ b/java/testFramework/src/com/intellij/codeInsight/completion/LightFixtureCompletionTestCase.java @@ -52,7 +52,10 @@ public abstract class LightFixtureCompletionTestCase extends LightJavaCodeInsigh protected void tearDown() throws Exception { try { myItems = null; - CodeInsightSettings.getInstance().COMPLETION_CASE_SENSITIVE = CodeInsightSettings.FIRST_LETTER; + CodeInsightSettings codeInsightSettings = CodeInsightSettings.getInstance(); + if (codeInsightSettings != null) { + codeInsightSettings.setCompletionCaseSensitive(CodeInsightSettings.FIRST_LETTER); + } } catch (Throwable e) { addSuppressedException(e); diff --git a/platform/platform-resources/src/META-INF/JsonPlugin.xml b/json/resources/intellij.json.xml similarity index 99% rename from platform/platform-resources/src/META-INF/JsonPlugin.xml rename to json/resources/intellij.json.xml index fa7c09357583..8d65bebe282e 100644 --- a/platform/platform-resources/src/META-INF/JsonPlugin.xml +++ b/json/resources/intellij.json.xml @@ -1,5 +1,4 @@ - @@ -191,5 +190,4 @@ - \ No newline at end of file diff --git a/platform/build-scripts/groovy/org/jetbrains/intellij/build/CommunityRepositoryModules.groovy b/platform/build-scripts/groovy/org/jetbrains/intellij/build/CommunityRepositoryModules.groovy index bbef77f8d9d8..209929ea7b0d 100644 --- a/platform/build-scripts/groovy/org/jetbrains/intellij/build/CommunityRepositoryModules.groovy +++ b/platform/build-scripts/groovy/org/jetbrains/intellij/build/CommunityRepositoryModules.groovy @@ -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.intellij.build import com.intellij.openapi.util.io.FileUtil @@ -19,7 +19,7 @@ final class CommunityRepositoryModules { static List COMMUNITY_REPOSITORY_PLUGINS = [ plugin("intellij.ant") { mainJarName = "antIntegration.jar" - withModule("intellij.ant.jps") + withModule("intellij.ant.jps", "ant-jps.jar") }, plugin("intellij.laf.macos") { bundlingRestrictions.supportedOs = [OsFamily.MACOS] @@ -72,12 +72,12 @@ final class CommunityRepositoryModules { }, plugin("intellij.maven") { withModule("intellij.maven.jps") - withModule("intellij.maven.server") - withModule("intellij.maven.server.m2.impl") - withModule("intellij.maven.server.m3.common") - withModule("intellij.maven.server.m30.impl") - withModule("intellij.maven.server.m3.impl") - withModule("intellij.maven.server.m36.impl") + withModule("intellij.maven.server", "maven-server-api.jar") + withModule("intellij.maven.server.m2.impl", "maven2-server.jar") + withModule("intellij.maven.server.m3.common", "maven3-server-common.jar") + withModule("intellij.maven.server.m30.impl", "maven30-server.jar") + withModule("intellij.maven.server.m3.impl", "maven3-server.jar") + withModule("intellij.maven.server.m36.impl", "maven36-server.jar") withModule("intellij.maven.errorProne.compiler") withModule("intellij.maven.artifactResolver.m2", "artifact-resolver-m2.jar") withModule("intellij.maven.artifactResolver.common", "artifact-resolver-m2.jar") @@ -103,8 +103,8 @@ final class CommunityRepositoryModules { }, plugin("intellij.gradle") { withModule("intellij.gradle.common") - withModule("intellij.gradle.toolingExtension") - withModule("intellij.gradle.toolingExtension.impl") + withModule("intellij.gradle.toolingExtension", "gradle-tooling-extension-api.jar") + withModule("intellij.gradle.toolingExtension.impl", "gradle-tooling-extension-impl.jar") withModule("intellij.gradle.toolingProxy") withProjectLibrary("Gradle") }, @@ -205,10 +205,10 @@ final class CommunityRepositoryModules { withModule("intellij.errorProne.jps", "jps/errorProne-jps.jar") }, plugin("intellij.cucumber.java") { - withModule("intellij.cucumber.jvmFormatter") - withModule("intellij.cucumber.jvmFormatter3") - withModule("intellij.cucumber.jvmFormatter4") - withModule("intellij.cucumber.jvmFormatter5") + withModule("intellij.cucumber.jvmFormatter", "cucumber-jvmFormatter.jar") + withModule("intellij.cucumber.jvmFormatter3", "cucumber-jvmFormatter3.jar") + withModule("intellij.cucumber.jvmFormatter4", "cucumber-jvmFormatter4.jar") + withModule("intellij.cucumber.jvmFormatter5", "cucumber-jvmFormatter5.jar") }, plugin("intellij.cucumber.groovy") { }, @@ -407,9 +407,9 @@ final class CommunityRepositoryModules { withModule("intellij.groovy.psi", mainJarName) withModule("intellij.groovy.structuralSearch", mainJarName) excludeFromModule("intellij.groovy.psi", "standardDsls/**") - withModule("intellij.groovy.jps") - withModule("intellij.groovy.rt") - withModule("intellij.groovy.constants.rt") + withModule("intellij.groovy.jps", "groovy-jps.jar") + withModule("intellij.groovy.rt", "groovy-rt.jar") + withModule("intellij.groovy.constants.rt", "groovy-constants-rt.jar") withResource("groovy-psi/resources/standardDsls", "lib/standardDsls") withResource("hotswap/gragent.jar", "lib/agent") withResource("groovy-psi/resources/conf", "lib") diff --git a/platform/build-scripts/groovy/org/jetbrains/intellij/build/impl/BaseLayoutSpec.groovy b/platform/build-scripts/groovy/org/jetbrains/intellij/build/impl/BaseLayoutSpec.groovy index f2d744be7048..b65a687fe5f4 100644 --- a/platform/build-scripts/groovy/org/jetbrains/intellij/build/impl/BaseLayoutSpec.groovy +++ b/platform/build-scripts/groovy/org/jetbrains/intellij/build/impl/BaseLayoutSpec.groovy @@ -23,7 +23,7 @@ class BaseLayoutSpec { } /** - * Register an additional module to be included into the plugin distribution into a separate JAR file. Module-level libraries from + * Register an additional module to be included into the plugin distribution. Module-level libraries from * {@code moduleName} with scopes 'Compile' and 'Runtime' will be also copied to the 'lib' directory of the plugin. */ void withModule(String moduleName) { diff --git a/platform/build-scripts/groovy/org/jetbrains/intellij/build/impl/DistributionJARsBuilder.groovy b/platform/build-scripts/groovy/org/jetbrains/intellij/build/impl/DistributionJARsBuilder.groovy index 59a6d8a55dd9..996767efa380 100644 --- a/platform/build-scripts/groovy/org/jetbrains/intellij/build/impl/DistributionJARsBuilder.groovy +++ b/platform/build-scripts/groovy/org/jetbrains/intellij/build/impl/DistributionJARsBuilder.groovy @@ -3,6 +3,7 @@ package org.jetbrains.intellij.build.impl import com.intellij.openapi.util.Pair import com.intellij.openapi.util.io.FileUtil +import com.intellij.openapi.util.io.FileUtilRt import com.intellij.openapi.util.text.StringUtil import com.intellij.util.containers.MultiMap import com.jetbrains.plugin.blockmap.core.BlockMap @@ -882,6 +883,7 @@ final class DistributionJARsBuilder { modulePatches(List.of(moduleName)) module(moduleName) { ant.exclude(name: "**/icon-robots.txt") + ant.exclude(name: ".unmodified") for (String exclude in layout.moduleExcludes.get(moduleName)) { //noinspection GrUnresolvedAccess @@ -935,11 +937,11 @@ final class DistributionJARsBuilder { } if (layoutSpec.copyFiles) { for (ModuleResourceData resourceData in layout.resourcePaths) { - String path = FileUtil.toSystemIndependentName(new File(basePath(buildContext, resourceData.moduleName), - resourceData.resourcePath).absolutePath) + String path = FileUtilRt.toSystemIndependentName(new File(basePath(buildContext, resourceData.moduleName), + resourceData.resourcePath).absolutePath) if (resourceData.packToZip) { zip(resourceData.relativeOutputPath) { - if (Files.isRegularFile(Paths.get(path))) { + if (Files.isRegularFile(Path.of(path))) { ant.fileset(file: path) } else { @@ -949,7 +951,7 @@ final class DistributionJARsBuilder { } else { dir(resourceData.relativeOutputPath) { - if (Files.isRegularFile(Paths.get(path))) { + if (Files.isRegularFile(Path.of(path))) { ant.fileset(file: path) } else { diff --git a/platform/build-scripts/groovy/org/jetbrains/intellij/build/impl/PluginLayout.groovy b/platform/build-scripts/groovy/org/jetbrains/intellij/build/impl/PluginLayout.groovy index cef99d9b7eb2..339a72ece277 100644 --- a/platform/build-scripts/groovy/org/jetbrains/intellij/build/impl/PluginLayout.groovy +++ b/platform/build-scripts/groovy/org/jetbrains/intellij/build/impl/PluginLayout.groovy @@ -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 org.jetbrains.intellij.build.impl import com.intellij.openapi.util.Pair @@ -89,7 +89,18 @@ final class PluginLayout extends BaseLayout { mainJarName = "${convertModuleNameToFileName(layout.mainModule)}.jar" } - /** + @Override + void withModule(String moduleName) { + if (moduleName.endsWith(".jps") || moduleName.endsWith(".rt")) { + // must be in a separate JAR + super.withModule(moduleName) + } + else { + layout.moduleJars.putValue(mainJarName, moduleName) + } + } + + /** * Custom name of the directory (under 'plugins' directory) where the plugin should be placed. By default the main module name is used * (with stripped {@code intellij} prefix and dots replaced by dashes). * Don't set this property for new plugins; it is temporary added to keep layout of old plugins unchanged. diff --git a/platform/core-api/src/com/intellij/util/messages/impl/CompositeMessageBus.java b/platform/core-api/src/com/intellij/util/messages/impl/CompositeMessageBus.java index 6b3b2b4b1679..28d8d43988b5 100644 --- a/platform/core-api/src/com/intellij/util/messages/impl/CompositeMessageBus.java +++ b/platform/core-api/src/com/intellij/util/messages/impl/CompositeMessageBus.java @@ -1,8 +1,9 @@ -// 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.messages.impl; +import com.intellij.ide.plugins.IdeaPluginDescriptor; import com.intellij.openapi.extensions.ExtensionNotApplicableException; -import com.intellij.openapi.extensions.PluginId; +import com.intellij.openapi.extensions.PluginDescriptor; import com.intellij.openapi.progress.ProcessCanceledException; import com.intellij.openapi.util.Disposer; import com.intellij.util.ArrayUtilRt; @@ -74,7 +75,7 @@ class CompositeMessageBus extends MessageBusImpl implements MessageBusEx { } @Override - final @NotNull MessagePublisher createPublisher(@NotNull Topic topic, @NotNull BroadcastDirection direction) { + final @NotNull MessagePublisher createPublisher(@NotNull Topic topic, @NotNull BroadcastDirection direction) { if (direction == BroadcastDirection.TO_PARENT) { return new ToParentMessagePublisher<>(topic, this); } @@ -168,11 +169,11 @@ class CompositeMessageBus extends MessageBusImpl implements MessageBusEx { } // use linked hash map for repeatable results - Map> listenerMap = new LinkedHashMap<>(); + Map> listenerMap = new LinkedHashMap<>(); for (ListenerDescriptor listenerDescriptor : listenerDescriptors) { try { //noinspection unchecked - listenerMap.computeIfAbsent(listenerDescriptor.pluginDescriptor.getPluginId(), __ -> new ArrayList<>()) + listenerMap.computeIfAbsent(listenerDescriptor.pluginDescriptor, __ -> new ArrayList<>()) .add((L)owner.createListener(listenerDescriptor)); } catch (ExtensionNotApplicableException ignore) { @@ -252,9 +253,9 @@ class CompositeMessageBus extends MessageBusImpl implements MessageBusEx { } @Override - public final void unsubscribeLazyListeners(@NotNull PluginId pluginId, @NotNull List listenerDescriptors) { + public final void unsubscribeLazyListeners(@NotNull IdeaPluginDescriptor module, @NotNull List listenerDescriptors) { topicClassToListenerDescriptor.values().removeIf(descriptors -> { - if (descriptors.removeIf(descriptor -> descriptor.pluginDescriptor.getPluginId().equals(pluginId))) { + if (descriptors.removeIf(descriptor -> descriptor.pluginDescriptor == module)) { return descriptors.isEmpty(); } return false; @@ -279,7 +280,7 @@ class CompositeMessageBus extends MessageBusImpl implements MessageBusEx { //noinspection unchecked DescriptorBasedMessageBusConnection connection = (DescriptorBasedMessageBusConnection)holder; - if (!pluginId.equals(connection.pluginId)) { + if (module != connection.module) { continue; } @@ -299,7 +300,7 @@ class CompositeMessageBus extends MessageBusImpl implements MessageBusEx { if (newSubscribers == null) { newSubscribers = new ArrayList<>(); } - newSubscribers.add(new DescriptorBasedMessageBusConnection<>(pluginId, connection.topic, newHandlers)); + newSubscribers.add(new DescriptorBasedMessageBusConnection<>(module, connection.topic, newHandlers)); } } diff --git a/platform/core-api/src/com/intellij/util/messages/impl/DescriptorBasedMessageBusConnection.java b/platform/core-api/src/com/intellij/util/messages/impl/DescriptorBasedMessageBusConnection.java index 05394eb60b72..ba992cf3c18f 100644 --- a/platform/core-api/src/com/intellij/util/messages/impl/DescriptorBasedMessageBusConnection.java +++ b/platform/core-api/src/com/intellij/util/messages/impl/DescriptorBasedMessageBusConnection.java @@ -1,7 +1,7 @@ -// 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.messages.impl; -import com.intellij.openapi.extensions.PluginId; +import com.intellij.openapi.extensions.PluginDescriptor; import com.intellij.util.messages.Topic; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -13,14 +13,14 @@ import java.util.Set; import java.util.function.Predicate; final class DescriptorBasedMessageBusConnection implements MessageBusImpl.MessageHandlerHolder { - final @NotNull PluginId pluginId; + final @NotNull PluginDescriptor module; final @NotNull Topic topic; final @NotNull List handlers; - DescriptorBasedMessageBusConnection(@NotNull PluginId pluginId, + DescriptorBasedMessageBusConnection(@NotNull PluginDescriptor module, @NotNull Topic topic, @NotNull List handlers) { - this.pluginId = pluginId; + this.module = module; this.topic = topic; this.handlers = handlers; } diff --git a/platform/core-api/src/com/intellij/util/messages/impl/MessageBusEx.java b/platform/core-api/src/com/intellij/util/messages/impl/MessageBusEx.java index edc5d379d2d9..ef3e4ae677b0 100644 --- a/platform/core-api/src/com/intellij/util/messages/impl/MessageBusEx.java +++ b/platform/core-api/src/com/intellij/util/messages/impl/MessageBusEx.java @@ -1,7 +1,7 @@ -// 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.messages.impl; -import com.intellij.openapi.extensions.PluginId; +import com.intellij.ide.plugins.IdeaPluginDescriptor; import com.intellij.util.messages.ListenerDescriptor; import com.intellij.util.messages.MessageBus; import org.jetbrains.annotations.ApiStatus; @@ -16,7 +16,7 @@ import java.util.function.Predicate; public interface MessageBusEx extends MessageBus { void clearPublisherCache(); - void unsubscribeLazyListeners(@NotNull PluginId pluginId, @NotNull List listenerDescriptors); + void unsubscribeLazyListeners(@NotNull IdeaPluginDescriptor module, @NotNull List listenerDescriptors); /** * Must be called only on a root bus. diff --git a/platform/core-impl/src/com/intellij/ide/plugins/CachingSemiGraph.java b/platform/core-impl/src/com/intellij/ide/plugins/CachingSemiGraph.java index 5bb836117e83..e44dc149b2e5 100644 --- a/platform/core-impl/src/com/intellij/ide/plugins/CachingSemiGraph.java +++ b/platform/core-impl/src/com/intellij/ide/plugins/CachingSemiGraph.java @@ -25,9 +25,4 @@ final class CachingSemiGraph implements InboundSemiGraph { List inNodes = in.get(n); return inNodes == null ? Collections.emptyIterator() : inNodes.iterator(); } - - public @NotNull List getInList(@NotNull Node n) { - List inNodes = in.get(n); - return inNodes == null ? Collections.emptyList() : inNodes; - } } \ No newline at end of file diff --git a/platform/core-impl/src/com/intellij/ide/plugins/ClassLoaderConfigurationData.java b/platform/core-impl/src/com/intellij/ide/plugins/ClassLoaderConfigurationData.java index 1ebe7cb00711..a64b73e49100 100644 --- a/platform/core-impl/src/com/intellij/ide/plugins/ClassLoaderConfigurationData.java +++ b/platform/core-impl/src/com/intellij/ide/plugins/ClassLoaderConfigurationData.java @@ -3,16 +3,18 @@ package com.intellij.ide.plugins; import com.intellij.openapi.extensions.PluginId; import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.Collections; import java.util.Set; +@ApiStatus.Internal public final class ClassLoaderConfigurationData { - static final boolean SEPARATE_CLASSLOADER_FOR_SUB = Boolean.parseBoolean(System.getProperty("idea.classloader.per.descriptor", "true")); - private static final Set SEPARATE_CLASSLOADER_FOR_SUB_ONLY = new ReferenceOpenHashSet<>(); - private static final Set SEPARATE_CLASSLOADER_FOR_SUB_EXCLUDE = ReferenceOpenHashSet.of( + private ClassLoaderConfigurationData() { + } + + static final Set SEPARATE_CLASSLOADER_FOR_SUB_EXCLUDE = new ReferenceOpenHashSet<>(new PluginId[]{ PluginId.getId("org.jetbrains.kotlin"), PluginId.getId("com.intellij.java"), PluginId.getId("com.intellij.spring.batch"), @@ -27,33 +29,10 @@ public final class ClassLoaderConfigurationData { PluginId.getId("com.intellij.spring.data"), PluginId.getId("com.intellij.spring.boot.run.tests"), PluginId.getId("com.intellij.spring.boot"), - PluginId.getId("com.intellij.spring") - ); - - static { - String property = System.getProperty("idea.classloader.per.descriptor.only"); - String[] pluginIds = property == null ? - new String[]{ - "org.jetbrains.plugins.ruby", - "PythonCore", - "com.jetbrains.rubymine.customization", - "JavaScript", - "Docker", - "com.intellij.diagram" - } : - property.split(","); - - for (String idString : pluginIds) { - SEPARATE_CLASSLOADER_FOR_SUB_ONLY.add(PluginId.getId(idString)); - } - } + PluginId.getId("com.intellij.spring"), + }); public static boolean isClassloaderPerDescriptorEnabled(@NotNull PluginId pluginId, @Nullable String packagePrefix) { - if (!SEPARATE_CLASSLOADER_FOR_SUB || SEPARATE_CLASSLOADER_FOR_SUB_EXCLUDE.contains(pluginId)) { - return false; - } - return packagePrefix != null || - SEPARATE_CLASSLOADER_FOR_SUB_ONLY.isEmpty() || - SEPARATE_CLASSLOADER_FOR_SUB_ONLY.contains(pluginId); + return packagePrefix != null && !SEPARATE_CLASSLOADER_FOR_SUB_EXCLUDE.contains(pluginId); } } diff --git a/platform/core-impl/src/com/intellij/ide/plugins/ClassLoaderConfigurator.kt b/platform/core-impl/src/com/intellij/ide/plugins/ClassLoaderConfigurator.kt index 1dd5a2d1debe..c0b8d95f843f 100644 --- a/platform/core-impl/src/com/intellij/ide/plugins/ClassLoaderConfigurator.kt +++ b/platform/core-impl/src/com/intellij/ide/plugins/ClassLoaderConfigurator.kt @@ -1,14 +1,12 @@ -// 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. @file:Suppress("ReplaceNegatedIsEmptyWithIsNotEmpty") package com.intellij.ide.plugins import com.intellij.diagnostic.PluginException import com.intellij.ide.plugins.cl.PluginClassLoader import com.intellij.openapi.diagnostic.Logger -import com.intellij.openapi.extensions.PluginId import com.intellij.util.lang.ClassPath import com.intellij.util.lang.UrlClassLoader -import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet import org.jetbrains.annotations.ApiStatus import java.io.IOException import java.lang.invoke.MethodHandles @@ -20,20 +18,17 @@ import java.util.* @ApiStatus.Internal class ClassLoaderConfigurator( - private val usePluginClassLoader: Boolean /* grab classes from platform loader only if nothing is found in any of plugin dependencies */, - private val coreLoader: ClassLoader, - val idMap: Map, - private val additionalLayoutMap: Map>) { - + val pluginSet: PluginSet, + private val coreLoader: ClassLoader = ClassLoaderConfigurator::class.java.classLoader, + private val usePluginClassLoader: Boolean = true, /* grab classes from platform loader only if nothing is found in any of plugin dependencies */ +) { private var javaDep: Optional? = null // temporary set to produce arrays (avoid allocation for each plugin) // set to remove duplicated classloaders private val loaders = LinkedHashSet() - // temporary list to produce arrays (avoid allocation for each plugin) - private val packagePrefixes = ArrayList() - private val hasAllModules = idMap.containsKey(PluginManagerCore.ALL_MODULES_MARKER) + private val hasAllModules = pluginSet.isPluginEnabled(PluginManagerCore.ALL_MODULES_MARKER) private val urlClassLoaderBuilder = UrlClassLoader.build().useCache() // todo for dynamic reload this guard doesn't contain all used plugin prefixes @@ -56,252 +51,176 @@ class ClassLoaderConfigurator( } } - fun configureDependenciesIfNeeded(mainToSub: Map>, + fun configureDependenciesIfNeeded(mainToModule: Map>, dependencyPlugin: IdeaPluginDescriptorImpl) { - for ((mainDependent, value) in mainToSub) { + for ((mainDependent, modules) in mainToModule) { val mainDependentClassLoader = mainDependent.classLoader as PluginClassLoader - if (isClassloaderPerDescriptorEnabled(mainDependent)) { - for (dependency in mainDependent.pluginDependencies) { - urlClassLoaderBuilder.files(mainDependentClassLoader.files) - for (subDescriptor in value) { - if (subDescriptor === dependency.subDescriptor) { - configureSubPlugin(dependency, mainDependentClassLoader, mainDependent) - break - } - } + if (ClassLoaderConfigurationData.isClassloaderPerDescriptorEnabled(mainDependent.id, mainDependent.packagePrefix)) { + urlClassLoaderBuilder.files(mainDependentClassLoader.files) + for (module in modules) { + assert(module.packagePrefix != null) + configureModule(module, mainDependentClassLoader) } } else { mainDependentClassLoader.attachParent(dependencyPlugin.classLoader!!) - for (subDescriptor in value) { - subDescriptor.classLoader = mainDependentClassLoader + for (module in modules) { + module.classLoader = mainDependentClassLoader } } } loaders.clear() - urlClassLoaderBuilder.files(emptyList()) + urlClassLoaderBuilder.files(Collections.emptyList()) } - fun configure(mainDependent: IdeaPluginDescriptorImpl) { - val pluginPackagePrefix = mainDependent.packagePrefix - if (pluginPackagePrefix != null && !pluginPackagePrefixUniqueGuard.add(pluginPackagePrefix)) { - throw PluginException("Package prefix $pluginPackagePrefix is already used", mainDependent.pluginId) - } + @JvmOverloads + fun configure(plugin: IdeaPluginDescriptorImpl, fallbackClassLoader: ClassLoader? = null) { + checkPackagePrefixUniqueness(plugin) - if (mainDependent.pluginId == PluginManagerCore.CORE_ID || mainDependent.isUseCoreClassLoader) { - setPluginClassLoaderForMainAndSubPlugins(mainDependent, coreLoader) + if (plugin.pluginId == PluginManagerCore.CORE_ID || plugin.isUseCoreClassLoader) { + setPluginClassLoaderForMainAndSubPlugins(plugin, coreLoader) return } - else if (!usePluginClassLoader) { - setPluginClassLoaderForMainAndSubPlugins(mainDependent, null) + if (!usePluginClassLoader) { + setPluginClassLoaderForMainAndSubPlugins(plugin, fallbackClassLoader) } + loaders.clear() // first, set class loader for main descriptor if (hasAllModules) { - val implicitDependency = PluginManagerCore.getImplicitDependency(mainDependent) { + val implicitDependency = PluginManagerCore.getImplicitDependency(plugin) { // first, set class loader for main descriptor if (javaDep == null) { - javaDep = Optional.ofNullable(idMap.get(PluginManagerCore.JAVA_PLUGIN_ID)) + javaDep = Optional.ofNullable(pluginSet.findEnabledPlugin(PluginManagerCore.JAVA_PLUGIN_ID)) } javaDep!!.orElse(null) } - implicitDependency?.let { addLoaderOrLogError(mainDependent, it, loaders) } + implicitDependency?.let { addLoaderOrLogError(plugin, it, loaders) } } - var classPath = mainDependent.jarFiles + var classPath = plugin.jarFiles if (classPath == null) { - classPath = collectClassPath(mainDependent) + classPath = collectClassPath(plugin) } else { - mainDependent.jarFiles = null + plugin.jarFiles = null } urlClassLoaderBuilder.files(classPath) - val pluginDependencies = mainDependent.pluginDependencies - if (pluginDependencies.isEmpty()) { - assert(!mainDependent.isUseIdeaClassLoader) - mainDependent.classLoader = createPluginClassLoader(mainDependent) - return - } + var oldActiveSubModules: MutableList? = null + for (dependency in plugin.pluginDependencies) { + val p = pluginSet.findEnabledPlugin(dependency.pluginId) ?: continue + val loader = p.classLoader + if (loader == null) { + log.error(PluginLoadingError.formatErrorMessage(plugin, "requires missing class loader for $p")) + } + else if (loader !== coreLoader) { + loaders.add(loader) + } - for (dependency in pluginDependencies) { - if (!dependency.isDisabledOrBroken && (!isClassloaderPerDescriptorEnabled(mainDependent) || dependency.subDescriptor == null)) { - addClassloaderIfDependencyEnabled(dependency.pluginId, mainDependent) + dependency.subDescriptor?.let { + if (oldActiveSubModules == null) { + oldActiveSubModules = ArrayList() + } + oldActiveSubModules!!.add(it) } } // new format - for (dependency in mainDependent.dependencyDescriptor.plugins) { - addClassloaderIfDependencyEnabled(dependency.id, mainDependent) + for (item in plugin.dependencies.modules) { + val descriptor = (pluginSet.findEnabledModule(item.name) ?: continue).requireDescriptor() + if (descriptor.classLoader !== coreLoader) { + loaders.add(descriptor.classLoader!!) + } + } + for (item in plugin.dependencies.plugins) { + val descriptor = pluginSet.findEnabledPlugin(item.id) ?: continue + if (descriptor.classLoader !== coreLoader) { + loaders.add(descriptor.classLoader!!) + } } - val mainDependentClassLoader = if (mainDependent.isUseIdeaClassLoader) { - configureUsingIdeaClassloader(classPath, mainDependent) + val mainDependentClassLoader = if (plugin.isUseIdeaClassLoader) { + configureUsingIdeaClassloader(classPath, plugin) } else { - createPluginClassLoader(mainDependent) + createPluginClassLoader(plugin) } // second, set class loaders for sub descriptors - if (usePluginClassLoader && isClassloaderPerDescriptorEnabled(mainDependent)) { - mainDependent.classLoader = mainDependentClassLoader - for (dependencyInfo in pluginDependencies) { - configureSubPlugin(dependencyInfo, mainDependentClassLoader, mainDependent) + if (usePluginClassLoader) { + plugin.classLoader = mainDependentClassLoader + for (module in plugin.content.modules) { + configureModule(module.requireDescriptor(), mainDependentClassLoader) + } + + for (subDescriptor in (oldActiveSubModules ?: Collections.emptyList())) { + // classLoader must be set - otherwise sub descriptor considered as inactive + subDescriptor.classLoader = mainDependentClassLoader } } else { - setPluginClassLoaderForMainAndSubPlugins(mainDependent, mainDependentClassLoader) + setPluginClassLoaderForMainAndSubPlugins(plugin, mainDependentClassLoader) } // reset to ensure that stalled data will be not reused somehow later loaders.clear() - urlClassLoaderBuilder.files(emptyList()) + urlClassLoaderBuilder.files(Collections.emptyList()) + } + + private fun checkPackagePrefixUniqueness(module: IdeaPluginDescriptorImpl) { + val packagePrefix = module.packagePrefix + if (packagePrefix != null && !pluginPackagePrefixUniqueGuard.add(packagePrefix)) { + throw PluginException("Package prefix $packagePrefix is already used (module=$module)", module.pluginId) + } } private fun createPluginClassLoader(descriptor: IdeaPluginDescriptorImpl): PluginClassLoader { - val parentLoaders = if (loaders.isEmpty()) PluginClassLoader.EMPTY_CLASS_LOADER_ARRAY - else loaders.toArray(PluginClassLoader.EMPTY_CLASS_LOADER_ARRAY) - return createPluginClassLoader(parentLoaders, descriptor, urlClassLoaderBuilder, coreLoader, resourceFileFactory) - } - - private fun configureSubPlugin(dependencyInfo: PluginDependency, - mainDependentClassLoader: ClassLoader, - parentDescriptor: IdeaPluginDescriptorImpl) { - val dependent = (if (dependencyInfo.isDisabledOrBroken) null else dependencyInfo.subDescriptor) ?: return - assert(!dependent.isUseIdeaClassLoader) - val pluginPackagePrefix = dependent.packagePrefix - if (pluginPackagePrefix == null) { - if (parentDescriptor.packagePrefix != null) { - throw PluginException("Sub descriptor must specify package if it is specified for main plugin descriptor " + - "(descriptorFile=${dependent.descriptorPath}, parentPackagePrefix=${parentDescriptor.packagePrefix})", - parentDescriptor.id) - } + val parentLoaders = if (loaders.isEmpty()) { + PluginClassLoader.EMPTY_CLASS_LOADER_ARRAY } else { - if (pluginPackagePrefix == parentDescriptor.packagePrefix) { - throw PluginException("Sub descriptor must not specify the same package as main plugin descriptor", parentDescriptor.id) - } + loaders.toArray(arrayOfNulls(loaders.size)) + } + return createPluginClassLoader(parentLoaders = parentLoaders, + descriptor = descriptor, + urlClassLoaderBuilder = urlClassLoaderBuilder, + coreLoader = coreLoader, + resourceFileFactory = resourceFileFactory, + pluginSet = pluginSet) + } - if (parentDescriptor.packagePrefix == null) { - val parentId = parentDescriptor.id.idString - if (!(parentId == "Docker" || - parentId == "org.jetbrains.plugins.ruby" || - parentId == "org.intellij.grails" || - parentId == "JavaScript")) { - throw PluginException("Sub descriptor must not specify package if one is not specified for main plugin descriptor", - parentDescriptor.id) - } - } - if (!pluginPackagePrefixUniqueGuard.add(pluginPackagePrefix)) { - throw PluginException("Package prefix $pluginPackagePrefix is already used", parentDescriptor.id) - } + private fun configureModule(module: IdeaPluginDescriptorImpl, mainDependentClassLoader: ClassLoader) { + if (module.packagePrefix == null) { + throw PluginException("Package is not specified (module=$module)", module.pluginId) } - val dependency = idMap.get(dependencyInfo.pluginId) - if (dependency == null || !dependency.isEnabled) { - return - } - - if (pluginPackagePrefix == null) { - packagePrefixes.clear() - collectPackagePrefixes(dependent, packagePrefixes) - // no package prefixes if only bean extension points are configured - if (packagePrefixes.isEmpty()) { - log.debug( - "Optional descriptor $dependencyInfo contains only bean extension points or light services") - } - } + checkPackagePrefixUniqueness(module) loaders.clear() // must be before main descriptor classloader - // only first level is supported - N level is not supported for a new model (several requirements maybe specified instead) - if (parentDescriptor.descriptorPath == null) { - addSiblingClassloaderIfNeeded(dependent, parentDescriptor) + + 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).requireDescriptor() + if (descriptor.classLoader !== coreLoader) { + loaders.add(descriptor.classLoader!!) + } + } + for (item in module.dependencies.plugins) { + val descriptor = pluginSet.findEnabledPlugin(item.id) ?: return + if (descriptor.classLoader !== coreLoader) { + loaders.add(descriptor.classLoader!!) + } } // add main descriptor classloader as parent loaders.add(mainDependentClassLoader) - addLoaderOrLogError(dependent, dependency, loaders) - val pluginDependencies = dependent.pluginDependencies - // add config-less dependencies to classloader parents - for (subDependency in pluginDependencies) { - if (!subDependency.isDisabledOrBroken && subDependency.subDescriptor == null) { - addClassloaderIfDependencyEnabled(subDependency.pluginId, dependent) - } - } - val subClassloader = if (pluginPackagePrefix == null) { - SubPluginClassLoader(dependent, - urlClassLoaderBuilder, - loaders.toTypedArray(), - packagePrefixes.toTypedArray(), - coreLoader, resourceFileFactory) - } - else { - createPluginClassLoader(dependent) - } - - dependent.classLoader = subClassloader - for (subDependency in pluginDependencies) { - configureSubPlugin(subDependency, subClassloader, dependent) - } - } - - private fun addSiblingClassloaderIfNeeded(dependent: IdeaPluginDescriptorImpl, parentDescriptor: IdeaPluginDescriptorImpl) { - if (!ClassLoaderConfigurationData.SEPARATE_CLASSLOADER_FOR_SUB) { - return - } - - for (dependentModuleDependency in dependent.dependencyDescriptor.modules) { - if (parentDescriptor.contentDescriptor.findModuleByName(dependentModuleDependency.name) == null) { - // todo what about dependency on a module that contained in another plugin? - throw PluginException( - "Main descriptor $parentDescriptor must list module in content if it is specified as dependency in sub descriptor " + - "(descriptorFile=${dependent.descriptorPath})", parentDescriptor.id - ) - } - for (dependencyPluginDependency in parentDescriptor.pluginDependencies) { - if (!dependencyPluginDependency.isDisabledOrBroken && dependencyPluginDependency.subDescriptor != null && - dependentModuleDependency.packageName == dependencyPluginDependency.subDescriptor!!.packagePrefix) { - val classLoader = dependencyPluginDependency.subDescriptor!!.classLoader - ?: throw PluginException("Classloader is null for sibling. " + - "Please ensure that content entry in the main plugin specifies module with package `" + - dependentModuleDependency.packageName + - "` before module with package `${dependent.packagePrefix}`" + - "(descriptorFile=${dependent.descriptorPath})", parentDescriptor.id) - loaders.add(classLoader) - } - } - } - } - - private fun addClassloaderIfDependencyEnabled(dependencyId: PluginId, dependent: IdeaPluginDescriptorImpl) { - val dependency = idMap.get(dependencyId) ?: return - - // must be first to ensure that it is used first to search classes (very important if main plugin descriptor doesn't have package prefix) - // check dependencies between optional descriptors (aka modules in a new model) from different plugins - if (ClassLoaderConfigurationData.SEPARATE_CLASSLOADER_FOR_SUB && !dependency.pluginDependencies.isEmpty()) { - for (dependentModuleDependency in dependent.dependencyDescriptor.modules) { - if (dependency.contentDescriptor.findModuleByName(dependentModuleDependency.name) != null) { - for (pluginDependency in dependency.pluginDependencies) { - if (!pluginDependency.isDisabledOrBroken && pluginDependency.subDescriptor != null && - dependentModuleDependency.packageName == pluginDependency.subDescriptor!!.packagePrefix) { - loaders.add(pluginDependency.subDescriptor!!.classLoader!!) - } - } - break - } - } - } - - 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) - } + assert(module.pluginDependencies.isEmpty()) + val subClassloader = createPluginClassLoader(module) + module.classLoader = subClassloader } private fun addLoaderOrLogError(dependent: IdeaPluginDescriptorImpl, @@ -320,18 +239,32 @@ class ClassLoaderConfigurator( rootDescriptor.classLoader = classLoader for (dependency in rootDescriptor.pluginDependencies) { if (dependency.subDescriptor != null) { - val descriptor = idMap.get(dependency.pluginId) - if (descriptor != null && descriptor.isEnabled) { + val descriptor = pluginSet.findEnabledPlugin(dependency.pluginId) + if (descriptor != null) { setPluginClassLoaderForMainAndSubPlugins(dependency.subDescriptor!!, classLoader) } } } + + m@ for (item in rootDescriptor.content.modules) { + val module = item.requireDescriptor() + + // skip if some dependency is not available + for (dependency in module.dependencies.modules) { + pluginSet.findEnabledModule(dependency.name) ?: continue@m + } + for (dependency in module.dependencies.plugins) { + pluginSet.findEnabledPlugin(dependency.id) ?: continue@m + } + + setPluginClassLoaderForMainAndSubPlugins(module, classLoader) + } } private fun collectClassPath(descriptor: IdeaPluginDescriptorImpl): List { val pluginPath = descriptor.path if (!Files.isDirectory(pluginPath)) { - return listOf(pluginPath) + return Collections.singletonList(pluginPath) } val result = ArrayList() @@ -343,13 +276,6 @@ class ClassLoaderConfigurator( val productionDirectory = pluginPath.parent if (productionDirectory.endsWith("production")) { result.add(pluginPath) - val moduleName = pluginPath.fileName.toString() - val additionalPaths = additionalLayoutMap.get(moduleName) - if (additionalPaths != null) { - for (path in additionalPaths) { - result.add(productionDirectory.resolve(path)) - } - } } } try { @@ -376,12 +302,6 @@ class ClassLoaderConfigurator( } } -// this list doesn't duplicate of PluginXmlFactory.CLASS_NAMES - interface related must be not here -private val IMPL_CLASS_NAMES = ReferenceOpenHashSet(arrayOf( - "implementation", "implementationClass", "builderClass", - "serviceImplementation", "class", "className", - "instance", "implementation-class")) - // do not use class reference here @Suppress("SSBasedInspection") private val log: Logger @@ -392,7 +312,8 @@ private fun createPluginClassLoader(parentLoaders: Array, descriptor: IdeaPluginDescriptorImpl, urlClassLoaderBuilder: UrlClassLoader.Builder, coreLoader: ClassLoader, - resourceFileFactory: ClassPath.ResourceFileFactory?): PluginClassLoader { + resourceFileFactory: ClassPath.ResourceFileFactory?, + pluginSet: PluginSet): PluginClassLoader { // main plugin descriptor if (descriptor.descriptorPath == null) { when (descriptor.id.idString) { @@ -431,14 +352,9 @@ private fun createPluginClassLoader(parentLoaders: Array, } } } - - if (descriptor.packagePrefix == null) { - return PluginClassLoader(urlClassLoaderBuilder, parentLoaders, descriptor, descriptor.pluginPath, coreLoader, null, null, - resourceFileFactory) - } } else { - if (!descriptor.contentDescriptor.modules.isEmpty()) { + if (!descriptor.content.modules.isEmpty()) { // see "The `content.module` element" section about content handling for a module return createPluginClassloader(parentLoaders = parentLoaders, descriptor = descriptor, @@ -460,8 +376,14 @@ private fun createPluginClassLoader(parentLoaders: Array, } } - return createPluginClassloader(parentLoaders, descriptor, urlClassLoaderBuilder, coreLoader, resourceFileFactory, - createPluginDependencyAndContentBasedScope(descriptor)) + return createPluginClassloader( + parentLoaders = parentLoaders, + descriptor = descriptor, + urlClassLoaderBuilder = urlClassLoaderBuilder, + coreLoader = coreLoader, + resourceFileFactory = resourceFileFactory, + resolveScopeManager = createPluginDependencyAndContentBasedScope(descriptor = descriptor, pluginSet = pluginSet) + ) } private fun createPluginClassloader(parentLoaders: Array, @@ -496,9 +418,14 @@ private fun createPluginClassLoaderWithExtraPackage(parentLoaders: Array if (force) { @@ -511,6 +438,7 @@ private fun createPluginDependencyAndContentBasedScope(descriptor: IdeaPluginDes return@ResolveScopeManager true } } + for (prefix in dependencyPackagePrefixes) { if (name.startsWith(prefix)) { return@ResolveScopeManager true @@ -522,29 +450,28 @@ private fun createPluginDependencyAndContentBasedScope(descriptor: IdeaPluginDes } private fun getContentPackagePrefixes(descriptor: IdeaPluginDescriptorImpl): List { - var result: MutableList? = null - for (item in descriptor.contentDescriptor.modules) { - if (item.isInjected) { - continue - } - - val packagePrefix = item.packageName ?: continue - if (result == null) { - result = ArrayList(descriptor.contentDescriptor.modules.size) - } - result.add("$packagePrefix.") + val modules = descriptor.content.modules + if (modules.isEmpty()) { + return Collections.emptyList() } - return result ?: emptyList() + + val result = Array(modules.size) { + val module = modules.get(it).requireDescriptor() + "${module.packagePrefix ?: throw PluginException("Package is not specified (module=$module)", module.pluginId)}." + } + @Suppress("ReplaceJavaStaticMethodWithKotlinAnalog") + return Arrays.asList(*result) } -private fun getDependencyPackagePrefixes(descriptor: IdeaPluginDescriptorImpl): List { - if (descriptor.dependencyDescriptor.modules.isEmpty()) { - return emptyList() +private fun getDependencyPackagePrefixes(descriptor: IdeaPluginDescriptorImpl, pluginSet: PluginSet): List { + val dependencies = descriptor.dependencies.modules + if (dependencies.isEmpty()) { + return Collections.emptyList() } - val result = ArrayList(descriptor.dependencyDescriptor.modules.size) - for (item in descriptor.dependencyDescriptor.modules) { - val packagePrefix = item.packageName + val result = ArrayList(dependencies.size) + for (item in dependencies) { + val packagePrefix = (pluginSet.findEnabledModule(item.name) ?: continue).requireDescriptor().packagePrefix // intellij.platform.commercial.verifier is injected if (packagePrefix != null && item.name != "intellij.platform.commercial.verifier") { result.add("$packagePrefix.") @@ -554,11 +481,9 @@ private fun getDependencyPackagePrefixes(descriptor: IdeaPluginDescriptorImpl): } private fun createModuleContentBasedScope(descriptor: IdeaPluginDescriptorImpl): PluginClassLoader.ResolveScopeManager { - val packagePrefixes = ArrayList(descriptor.contentDescriptor.modules.size) - for (item in descriptor.contentDescriptor.modules) { - item.packageName?.let { - packagePrefixes.add("$it.") - } + val packagePrefixes = ArrayList(descriptor.content.modules.size) + for (item in descriptor.content.modules) { + packagePrefixes.add("${item.requireDescriptor().packagePrefix!!}.") } // force flag is ignored for module - e.g. RailsViewLineMarkerProvider is referenced as extension implementation in several modules @@ -579,91 +504,6 @@ private fun createModuleContentBasedScope(descriptor: IdeaPluginDescriptorImpl): } } -private fun isClassloaderPerDescriptorEnabled(descriptor: IdeaPluginDescriptorImpl): Boolean { - return ClassLoaderConfigurationData.isClassloaderPerDescriptorEnabled(descriptor.id, descriptor.packagePrefix) -} - -private fun collectPackagePrefixes(dependent: IdeaPluginDescriptorImpl, packagePrefixes: MutableList) { - // from extensions - dependent.unsortedEpNameToExtensionElements.values.forEach { extensionDescriptors -> - for (extensionDescriptor in extensionDescriptors) { - if (extensionDescriptor.implementation != null) { - addPackageByClassNameIfNeeded(extensionDescriptor.implementation!!, packagePrefixes) - continue - } - - val element = extensionDescriptor.element ?: continue - if (!element.attributes.isEmpty()) { - continue - } - - for (attributeName in IMPL_CLASS_NAMES) { - val className = element.getAttributeValue(attributeName) - if (className != null && !className.isEmpty()) { - addPackageByClassNameIfNeeded(className, packagePrefixes) - break - } - } - } - } - - // from services - collectFromServices(dependent.appContainerDescriptor, packagePrefixes) - collectFromServices(dependent.projectContainerDescriptor, packagePrefixes) - collectFromServices(dependent.moduleContainerDescriptor, packagePrefixes) -} - -private fun addPackageByClassNameIfNeeded(name: String, packagePrefixes: MutableList) { - for (packagePrefix in packagePrefixes) { - if (name.startsWith(packagePrefix)) { - return - } - } - - // for classes like com.intellij.thymeleaf.lang.ThymeleafParserDefinition$SPRING_SECURITY_EXPRESSIONS - // we must not try to load the containing package - if (name.indexOf('$') != -1) { - packagePrefixes.add(name) - return - } - - val lastPackageDot = name.lastIndexOf('.') - if (lastPackageDot > 0 && lastPackageDot != name.length) { - addPackagePrefixIfNeeded(packagePrefixes, name.substring(0, lastPackageDot + 1)) - } -} - -private fun addPackagePrefixIfNeeded(packagePrefixes: MutableList, packagePrefix: String) { - for (i in packagePrefixes.indices) { - val existingPackagePrefix = packagePrefixes.get(i) - if (packagePrefix.startsWith(existingPackagePrefix)) { - return - } - else if (existingPackagePrefix.startsWith(packagePrefix) && existingPackagePrefix.indexOf('$') == -1) { - packagePrefixes.set(i, packagePrefix) - for (j in packagePrefixes.size - 1 downTo i + 1) { - if (packagePrefixes.get(j).startsWith(packagePrefix)) { - packagePrefixes.removeAt(j) - } - } - return - } - } - packagePrefixes.add(packagePrefix) -} - -private fun collectFromServices(containerDescriptor: ContainerDescriptor, packagePrefixes: MutableList) { - for (service in containerDescriptor.services) { - // testServiceImplementation is ignored by intention - service.serviceImplementation?.let { - addPackageByClassNameIfNeeded(it, packagePrefixes) - } - service.headlessImplementation?.let { - addPackageByClassNameIfNeeded(it, packagePrefixes) - } - } -} - private fun configureUsingIdeaClassloader(classPath: List, descriptor: IdeaPluginDescriptorImpl): ClassLoader { log.warn("${descriptor.pluginId} uses deprecated `use-idea-classloader` attribute") val loader = ClassLoaderConfigurator::class.java.classLoader diff --git a/platform/core-impl/src/com/intellij/ide/plugins/ClassPathXmlPathResolver.kt b/platform/core-impl/src/com/intellij/ide/plugins/ClassPathXmlPathResolver.kt index a1efba2dbcea..34068e22a178 100644 --- a/platform/core-impl/src/com/intellij/ide/plugins/ClassPathXmlPathResolver.kt +++ b/platform/core-impl/src/com/intellij/ide/plugins/ClassPathXmlPathResolver.kt @@ -2,8 +2,11 @@ package com.intellij.ide.plugins import com.intellij.platform.util.plugins.DataLoader +import com.intellij.platform.util.plugins.LocalFsDataLoader +import java.nio.file.Files +import java.nio.file.NoSuchFileException -internal class ClassPathXmlPathResolver(private val classLoader: ClassLoader) : PathResolver { +internal class ClassPathXmlPathResolver(private val classLoader: ClassLoader, private val isRunningFromSources: Boolean) : PathResolver { override val isFlat: Boolean get() = true @@ -23,6 +26,33 @@ internal class ClassPathXmlPathResolver(private val classLoader: ClassLoader) : return true } + override fun resolveModuleFile(readContext: ReadModuleContext, + dataLoader: DataLoader, + path: String, + readInto: RawPluginDescriptor?): RawPluginDescriptor { + var resource = classLoader.getResourceAsStream(path) + if (resource == null) { + if (isRunningFromSources && path.startsWith("intellij.") && dataLoader is LocalFsDataLoader) { + try { + resource = Files.newInputStream(dataLoader.basePath.parent.resolve("${path.substring(0, path.length - 4)}/$path")) + } + catch (e: NoSuchFileException) { + } + } + + if (resource == null) { + throw RuntimeException("Cannot resolve $path (dataLoader=$dataLoader, classLoader=$classLoader)") + } + } + return readModuleDescriptor(inputStream = resource, + readContext = readContext, + pathResolver = this, + dataLoader = dataLoader, + includeBase = null, + readInto = readInto, + locationSource = dataLoader.toString()) + } + override fun resolvePath(readContext: ReadModuleContext, dataLoader: DataLoader, relativePath: String, diff --git a/platform/core-impl/src/com/intellij/ide/plugins/IdeaPluginDescriptorImpl.kt b/platform/core-impl/src/com/intellij/ide/plugins/IdeaPluginDescriptorImpl.kt index 252e95947529..fe894b915630 100644 --- a/platform/core-impl/src/com/intellij/ide/plugins/IdeaPluginDescriptorImpl.kt +++ b/platform/core-impl/src/com/intellij/ide/plugins/IdeaPluginDescriptorImpl.kt @@ -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.ide.plugins import com.intellij.AbstractBundle @@ -85,8 +85,8 @@ class IdeaPluginDescriptorImpl(raw: RawPluginDescriptor, @JvmField val projectContainerDescriptor = raw.projectContainerDescriptor @JvmField val moduleContainerDescriptor = raw.moduleContainerDescriptor - @JvmField internal val contentDescriptor = raw.contentDescriptor - @JvmField internal val dependencyDescriptor = raw.dependencyDescriptor + @JvmField val content = raw.content + @JvmField val dependencies = raw.dependencies @JvmField val modules: List = raw.modules ?: Collections.emptyList() private val descriptionChildText = raw.description @@ -120,7 +120,11 @@ class IdeaPluginDescriptorImpl(raw: RawPluginDescriptor, override fun getPluginPath() = path - fun createSub(raw: RawPluginDescriptor, descriptorPath: String): IdeaPluginDescriptorImpl { + private fun createSub(raw: RawPluginDescriptor, + descriptorPath: String, + pathResolver: PathResolver, + context: DescriptorListLoadingContext, + dataLoader: DataLoader): IdeaPluginDescriptorImpl { raw.name = name @Suppress("TestOnlyProblems") val result = IdeaPluginDescriptorImpl(raw, path = path, isBundled = isBundled, id = id) @@ -128,6 +132,8 @@ class IdeaPluginDescriptorImpl(raw: RawPluginDescriptor, result.vendor = vendor result.version = version result.resourceBundleBaseName = resourceBundleBaseName + + result.readExternal(raw = raw, pathResolver = pathResolver, context = context, isSub = true, dataLoader = dataLoader) return result } @@ -139,21 +145,17 @@ class IdeaPluginDescriptorImpl(raw: RawPluginDescriptor, // include module file descriptor if not specified as `depends` (old way - xi:include) // must be first because merged into raw descriptor if (!isSub) { - moduleLoop@ for (module in contentDescriptor.modules) { - val descriptorFile = module.configFile ?: "${module.name}.xml" - val oldDepends = raw.depends - if (oldDepends != null) { - for (dependency in oldDepends) { - if (descriptorFile == dependency.configFile) { - // ok, it is specified in old way as depends tag - skip it - continue@moduleLoop - } - } - } - - pathResolver.resolvePath(context, dataLoader, descriptorFile, raw) - ?: throw RuntimeException("Plugin $this misses optional descriptor $descriptorFile") - module.isInjected = true + for (module in content.modules) { + val subDescriptorFile = module.configFile ?: "${module.name}.xml" + val subDescriptor = createSub(raw = pathResolver.resolveModuleFile(readContext = context, + dataLoader = dataLoader, + path = subDescriptorFile, + readInto = null), + descriptorPath = subDescriptorFile, + pathResolver = pathResolver, + context = context, + dataLoader = dataLoader) + module.descriptor = subDescriptor } } @@ -179,7 +181,7 @@ class IdeaPluginDescriptorImpl(raw: RawPluginDescriptor, markAsIncomplete(context, null, null) } else { - for (pluginDependency in dependencyDescriptor.plugins) { + for (pluginDependency in dependencies.plugins) { if (context.isPluginDisabled(pluginDependency.id)) { markAsIncomplete(context, pluginDependency.id, shortMessage = "plugin.loading.error.short.depends.on.disabled.plugin") } @@ -213,7 +215,6 @@ class IdeaPluginDescriptorImpl(raw: RawPluginDescriptor, if (!dependency.isOptional && !isIncomplete) { markAsIncomplete(context, dependency.pluginId, "plugin.loading.error.short.depends.on.disabled.plugin") } - dependency.isDisabledOrBroken = true } else if (context.result.isBroken(dependency.pluginId)) { if (!dependency.isOptional && !isIncomplete) { @@ -222,7 +223,6 @@ class IdeaPluginDescriptorImpl(raw: RawPluginDescriptor, shortMessage = "plugin.loading.error.short.depends.on.broken.plugin", pluginId = dependency.pluginId) } - dependency.isDisabledOrBroken = true } // because of https://youtrack.jetbrains.com/issue/IDEA-206274, configFile maybe not only for optional dependencies @@ -260,10 +260,12 @@ class IdeaPluginDescriptorImpl(raw: RawPluginDescriptor, checkCycle(descriptor, configFile, visitedFiles) - val subDescriptor = descriptor.createSub(raw, configFile) visitedFiles.add(configFile) - - subDescriptor.readExternal(raw = raw, pathResolver = pathResolver, context = context, isSub = true, dataLoader = dataLoader) + val subDescriptor = descriptor.createSub(raw = raw, + descriptorPath = configFile, + pathResolver = pathResolver, + context = context, + dataLoader = dataLoader) dependency.subDescriptor = subDescriptor visitedFiles.clear() } diff --git a/platform/core-impl/src/com/intellij/ide/plugins/ModuleDependenciesDescriptor.kt b/platform/core-impl/src/com/intellij/ide/plugins/ModuleDependenciesDescriptor.kt index adeaa0fff890..ab44ed244952 100644 --- a/platform/core-impl/src/com/intellij/ide/plugins/ModuleDependenciesDescriptor.kt +++ b/platform/core-impl/src/com/intellij/ide/plugins/ModuleDependenciesDescriptor.kt @@ -1,71 +1,42 @@ -// 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. @file:Suppress("ReplaceNegatedIsEmptyWithIsNotEmpty") package com.intellij.ide.plugins import com.intellij.openapi.extensions.PluginId +import org.jetbrains.annotations.ApiStatus +import java.util.* -internal class ModuleDependenciesDescriptor(@JvmField val modules: List, @JvmField val plugins: List) { +class ModuleDependenciesDescriptor(@JvmField val modules: List, @JvmField val plugins: List) { companion object { - @JvmField - val EMPTY = ModuleDependenciesDescriptor(emptyList(), emptyList()) + @JvmField val EMPTY = ModuleDependenciesDescriptor(Collections.emptyList(), Collections.emptyList()) } - fun findModuleByName(name: String): ModuleItem? { - for (module in modules) { - if (module.name == name) { - return module - } - } - return null + class ModuleReference(@JvmField val name: String) { + override fun toString() = "ModuleItem(name=$name)" } - internal class ModuleItem(@JvmField val name: String, @JvmField val packageName: String?) { - init { - if (packageName != null && packageName.endsWith(".")) { - throw RuntimeException("packageName must not ends with dot: $packageName") - } - } - - override fun toString(): String { - return "ModuleItem(name=$name, packageName=$packageName)" - } + class PluginItem(@JvmField val id: PluginId) { + override fun toString() = "PluginItem(id=$id)" } - internal class PluginItem(@JvmField val id: PluginId) { - override fun toString(): String { - return "PluginItem(id=$id)" - } - } - - override fun toString(): String { - return "ModuleDependenciesDescriptor(modules=$modules, plugins=$plugins)" - } + override fun toString() = "ModuleDependenciesDescriptor(modules=$modules, plugins=$plugins)" } -internal class PluginContentDescriptor(@JvmField val modules: List) { +@ApiStatus.Internal +class PluginContentDescriptor(@JvmField val modules: List) { companion object { - @JvmField - val EMPTY = PluginContentDescriptor(emptyList()) + @JvmField val EMPTY = PluginContentDescriptor(Collections.emptyList()) } - fun findModuleByName(name: String): ModuleItem? { - for (module in modules) { - if (module.name == name) { - return module - } - } - return null - } + @ApiStatus.Internal + class ModuleItem(@JvmField val name: String, + @JvmField val configFile: String?) { + @JvmField internal var descriptor: IdeaPluginDescriptorImpl? = null - internal class ModuleItem(@JvmField val name: String, @JvmField val packageName: String?, @JvmField val configFile: String?) { - // - xi-include without classloader at all - @JvmField - var isInjected = false + fun requireDescriptor() = descriptor ?: throw IllegalStateException("Descriptor is not set for $this") - init { - if (packageName != null && packageName.endsWith(".")) { - throw RuntimeException("packageName must not ends with dot: $packageName") - } + override fun toString(): String { + return "ModuleItem(name=$name, descriptor=$descriptor, configFile=$configFile)" } } diff --git a/platform/core-impl/src/com/intellij/ide/plugins/PathBasedJdomXIncluder.kt b/platform/core-impl/src/com/intellij/ide/plugins/PathBasedJdomXIncluder.kt index a58d01e2b02e..59d9580cfe59 100644 --- a/platform/core-impl/src/com/intellij/ide/plugins/PathBasedJdomXIncluder.kt +++ b/platform/core-impl/src/com/intellij/ide/plugins/PathBasedJdomXIncluder.kt @@ -19,4 +19,10 @@ interface PathResolver { dataLoader: DataLoader, relativePath: String, readInto: RawPluginDescriptor?): RawPluginDescriptor? + + // module in a new file name format must be always resolved + fun resolveModuleFile(readContext: ReadModuleContext, + dataLoader: DataLoader, + path: String, + readInto: RawPluginDescriptor?): RawPluginDescriptor } \ No newline at end of file diff --git a/platform/core-impl/src/com/intellij/ide/plugins/PluginDependency.kt b/platform/core-impl/src/com/intellij/ide/plugins/PluginDependency.kt index e2b5bb2425c5..e998f4ee2b5c 100644 --- a/platform/core-impl/src/com/intellij/ide/plugins/PluginDependency.kt +++ b/platform/core-impl/src/com/intellij/ide/plugins/PluginDependency.kt @@ -7,8 +7,7 @@ import org.jetbrains.annotations.ApiStatus @ApiStatus.Internal class PluginDependency internal constructor(override val pluginId: PluginId, configFile: String?, - isOptional: Boolean, - @JvmField @field:Transient var isDisabledOrBroken: Boolean) : IdeaPluginDependency { + isOptional: Boolean) : IdeaPluginDependency { var configFile = configFile internal set @@ -23,7 +22,6 @@ class PluginDependency internal constructor(override val pluginId: PluginId, "pluginId=" + pluginId + ", isOptional=" + isOptional + ", configFile=" + configFile + - ", isDisabledOrBroken=" + isDisabledOrBroken + ", subDescriptor=" + subDescriptor + ')' } diff --git a/platform/core-impl/src/com/intellij/ide/plugins/PluginDescriptorLoader.kt b/platform/core-impl/src/com/intellij/ide/plugins/PluginDescriptorLoader.kt index aa323b3b410a..e0731a782e1b 100644 --- a/platform/core-impl/src/com/intellij/ide/plugins/PluginDescriptorLoader.kt +++ b/platform/core-impl/src/com/intellij/ide/plugins/PluginDescriptorLoader.kt @@ -394,7 +394,7 @@ private fun loadBundledDescriptorsAndDescriptorsFromDir(context: DescriptorListL val platformPrefix = PlatformUtils.getPlatformPrefix() // should be the only plugin in lib (only for Ultimate and WebStorm for now) - val pathResolver = ClassPathXmlPathResolver(classLoader) + val pathResolver = ClassPathXmlPathResolver(classLoader, isRunningFromSources = isRunningFromSources) if ((platformPrefix == PlatformUtils.IDEA_PREFIX || platformPrefix == PlatformUtils.WEB_PREFIX) && (java.lang.Boolean.getBoolean("idea.use.dev.build.server") || (!isUnitTestMode && !isRunningFromSources))) { val dataLoader = object : DataLoader { @@ -577,7 +577,7 @@ fun getDescriptorsToMigrate(dir: Path, fun createPathResolverForPlugin(descriptor: IdeaPluginDescriptorImpl, checkPluginJarFiles: Boolean): PathResolver { if (PluginManagerCore.isRunningFromSources() && descriptor.pluginPath.fileSystem == FileSystems.getDefault() && descriptor.pluginPath.toString().contains("out/classes")) { - return ClassPathXmlPathResolver(descriptor.pluginClassLoader) + return ClassPathXmlPathResolver(descriptor.pluginClassLoader, isRunningFromSources = false) } else if (checkPluginJarFiles) { val pluginJarFiles = ArrayList() @@ -598,7 +598,7 @@ fun testLoadDescriptorsFromClassPath(loader: ClassLoader): List descriptors) { - @SuppressWarnings("SuspiciousToArrayCall") - IdeaPluginDescriptorImpl[] list = descriptors.toArray(new IdeaPluginDescriptorImpl[0]); - PluginManagerCore.doSetPlugins(list); + public void setPlugins(@NotNull List descriptors) { + PluginManagerCore.doSetPlugins(Java11Shim.INSTANCE.copyOf(descriptors)); } @ApiStatus.Internal public boolean processAllBackwardDependencies(@NotNull IdeaPluginDescriptorImpl rootDescriptor, boolean withOptionalDeps, @NotNull Function consumer) { - Map idMap = PluginManagerCore.buildPluginIdMap(); - Collection allPlugins = PluginManagerCore.getAllPlugins(); - CachingSemiGraph semiGraph = PluginManagerCore.createPluginIdGraph(allPlugins, idMap, withOptionalDeps); + @NotNull PluginSet pluginSet = PluginManagerCore.getPluginSet(); + CachingSemiGraph semiGraph = PluginManagerCore.createPluginIdGraph(pluginSet.loadedPlugins, pluginSet, withOptionalDeps); Graph graph = GraphGenerator.generate(semiGraph); Set dependencies = new LinkedHashSet<>(); GraphAlgorithms.getInstance().collectOutsRecursively(graph, rootDescriptor, dependencies); @@ -213,9 +209,8 @@ public final class PluginManager { return true; } - @NotNull IdeaPluginDescriptorImpl @NotNull [] getPluginsSortedByDependency(@NotNull List descriptors) { - Map idMap = PluginManagerCore.buildPluginIdMap(); - InboundSemiGraph graph = PluginManagerCore.createPluginIdGraph(descriptors, idMap, true); + @NotNull List getPluginsSortedByDependency(@NotNull List descriptors) { + InboundSemiGraph graph = PluginManagerCore.createPluginIdGraph(descriptors, PluginManagerCore.getPluginSet(), true); return PluginManagerCore.getTopologicallySorted(graph); } diff --git a/platform/core-impl/src/com/intellij/ide/plugins/PluginManagerCore.java b/platform/core-impl/src/com/intellij/ide/plugins/PluginManagerCore.java index 08edd457aa55..d5712812b2dd 100644 --- a/platform/core-impl/src/com/intellij/ide/plugins/PluginManagerCore.java +++ b/platform/core-impl/src/com/intellij/ide/plugins/PluginManagerCore.java @@ -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.ide.plugins; import com.intellij.core.CoreBundle; @@ -17,15 +17,12 @@ import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.io.FileUtilRt; import com.intellij.openapi.util.text.HtmlChunk; import com.intellij.openapi.util.text.StringUtil; -import com.intellij.util.ArrayUtil; -import com.intellij.util.ArrayUtilRt; import com.intellij.util.PlatformUtils; -import com.intellij.util.ReflectionUtil; import com.intellij.util.containers.ContainerUtil; -import com.intellij.util.execution.ParametersListUtil; import com.intellij.util.graph.DFSTBuilder; import com.intellij.util.graph.GraphGenerator; import com.intellij.util.graph.InboundSemiGraph; +import com.intellij.util.lang.Java11Shim; import com.intellij.util.lang.UrlClassLoader; import org.jetbrains.annotations.*; @@ -39,7 +36,6 @@ import java.nio.file.*; import java.util.List; import java.util.*; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionStage; import java.util.concurrent.ForkJoinPool; import java.util.function.BiFunction; import java.util.function.Function; @@ -48,6 +44,9 @@ import java.util.stream.Collectors; import java.util.stream.Stream; // Prefer to use only JDK classes. Any post start-up functionality should be placed in PluginManager class. +/** + * See Plugin Model V2 documentation + */ public final class PluginManagerCore { public static final @NonNls String META_INF = "META-INF/"; @@ -78,19 +77,14 @@ public final class PluginManagerCore { private static final MethodType HAS_LOADED_CLASS_METHOD_TYPE = MethodType.methodType(boolean.class, String.class); private static Reference>> brokenPluginVersions; - private static volatile IdeaPluginDescriptorImpl[] ourPlugins; - private static volatile List ourLoadedPlugins; - private static Map ourPluginLoadingErrors; - - private static Map ourAdditionalLayoutMap = Collections.emptyMap(); + private static volatile @Nullable PluginSet pluginSet; + private static Map pluginLoadingErrors; @SuppressWarnings("StaticNonFinalField") public static volatile boolean isUnitTestMode = Boolean.getBoolean("idea.is.unit.test"); - @ApiStatus.Internal - static final boolean usePluginClassLoader = Boolean.getBoolean("idea.from.sources.plugins.class.loader"); @ApiStatus.Internal - private static final List> ourPluginErrors = new ArrayList<>(); + private static final List> pluginErrors = new ArrayList<>(); private static Set ourPluginsToDisable; private static Set ourPluginsToEnable; @@ -118,16 +112,11 @@ public final class PluginManagerCore { * Do not call this method during bootstrap, should be called in a copy of PluginManager, loaded by PluginClassLoader. */ public static @NotNull IdeaPluginDescriptor @NotNull[] getPlugins() { - IdeaPluginDescriptor[] result = ourPlugins; - if (result == null) { - loadAndInitializePlugins(null, null); - return ourPlugins; - } - return result; + return getPluginSet().allPlugins.toArray(new IdeaPluginDescriptor[0]); } - static @NotNull Collection getAllPlugins() { - return Arrays.asList(ourPlugins); + static @NotNull PluginSet getPluginSet() { + return Objects.requireNonNull(pluginSet); } /** @@ -140,37 +129,30 @@ public final class PluginManagerCore { @ApiStatus.Internal public static @NotNull List getLoadedPlugins(@Nullable ClassLoader coreClassLoader) { - List result = ourLoadedPlugins; - if (result == null) { - loadAndInitializePlugins(null, coreClassLoader); - return ourLoadedPlugins; + PluginSet result = pluginSet; + if (result != null) { + return result.loadedPlugins; } - return result; + return loadAndInitializePlugins(PluginDescriptorLoader.loadDescriptors(isUnitTestMode, isRunningFromSources()), + coreClassLoader == null ? PluginManagerCore.class.getClassLoader() : coreClassLoader).loadedPlugins; } @ApiStatus.Internal public static @NotNull List getAndClearPluginLoadingErrors() { - synchronized (ourPluginErrors) { - List errors = ContainerUtil.map(ourPluginErrors, Supplier::get); - ourPluginErrors.clear(); + synchronized (pluginErrors) { + List errors = ContainerUtil.map(pluginErrors, Supplier::get); + pluginErrors.clear(); return errors; } } - private static void registerPluginErrors(List> errors) { - synchronized (ourPluginErrors) { - ourPluginErrors.addAll(errors); - } - } - @ApiStatus.Internal public static boolean arePluginsInitialized() { - return ourPlugins != null; + return pluginSet != null; } - static synchronized void doSetPlugins(@NotNull IdeaPluginDescriptorImpl @Nullable [] value) { - ourPlugins = value; - ourLoadedPlugins = value == null ? null : Collections.unmodifiableList(getOnlyEnabledPlugins(value)); + static synchronized void doSetPlugins(@Nullable List value) { + pluginSet = value == null ? null : new PluginSet(value, getOnlyEnabledPlugins(value)); } public static boolean isDisabled(@NotNull PluginId pluginId) { @@ -308,8 +290,8 @@ public final class PluginManagerCore { @ApiStatus.Internal public static @Nullable PluginDescriptor getPluginDescriptorOrPlatformByClassName(@NotNull @NonNls String className) { - List loadedPlugins = ourLoadedPlugins; - if (loadedPlugins == null || + PluginSet pluginSet = PluginManagerCore.pluginSet; + if (pluginSet == null || className.startsWith("java.") || className.startsWith("javax.") || className.startsWith("kotlin.") || @@ -319,7 +301,7 @@ public final class PluginManagerCore { } IdeaPluginDescriptor result = null; - for (IdeaPluginDescriptorImpl o : loadedPlugins) { + for (IdeaPluginDescriptorImpl o : pluginSet.loadedPlugins) { ClassLoader classLoader = o.getPluginClassLoader(); if (!hasLoadedClass(className, classLoader)) { continue; @@ -344,7 +326,7 @@ public final class PluginManagerCore { // otherwise we need to check plugins with use-idea-classloader="true" String root = null; - for (IdeaPluginDescriptorImpl o : loadedPlugins) { + for (IdeaPluginDescriptorImpl o : pluginSet.loadedPlugins) { if (!o.isUseIdeaClassLoader) { continue; } @@ -420,14 +402,12 @@ public final class PluginManagerCore { static @Nullable IdeaPluginDescriptorImpl getImplicitDependency(@NotNull IdeaPluginDescriptorImpl descriptor, @NotNull Supplier javaDepGetter) { // skip our plugins as expected to be up-to-date whether bundled or not - if (descriptor.isBundled() || - VENDOR_JETBRAINS.equals(descriptor.getVendor())) { + if (descriptor.isBundled() || VENDOR_JETBRAINS.equals(descriptor.getVendor())) { return null; } PluginId pluginId = descriptor.getPluginId(); - if (CORE_ID.equals(pluginId) || - JAVA_PLUGIN_ID.equals(pluginId)) { + if (CORE_ID.equals(pluginId) || JAVA_PLUGIN_ID.equals(pluginId)) { return null; } @@ -459,8 +439,8 @@ public final class PluginManagerCore { ourShadowedBundledPlugins = null; } - private static void logPlugins(@NotNull IdeaPluginDescriptorImpl @NotNull [] plugins, - Collection incompletePlugins) { + private static void logPlugins(@NotNull List plugins, + @NotNull Collection incompletePlugins) { StringBuilder bundled = new StringBuilder(); StringBuilder disabled = new StringBuilder(); StringBuilder custom = new StringBuilder(); @@ -528,7 +508,7 @@ public final class PluginManagerCore { private static void prepareLoadingPluginsErrorMessage(@NotNull Map pluginErrors, @NotNull List> globalErrors, @NotNull List> actions) { - ourPluginLoadingErrors = pluginErrors; + pluginLoadingErrors = pluginErrors; if (pluginErrors.isEmpty() && globalErrors.isEmpty()) { return; @@ -552,7 +532,10 @@ public final class PluginManagerCore { ).collect(Collectors.toList()); if (!errorsList.isEmpty()) { - registerPluginErrors(ContainerUtil.concat(errorsList, actions)); + synchronized (PluginManagerCore.pluginErrors) { + PluginManagerCore.pluginErrors.addAll(errorsList); + PluginManagerCore.pluginErrors.addAll(actions); + } } getLogger().warn(logMessage); @@ -563,71 +546,61 @@ public final class PluginManagerCore { } public static @Nullable @NlsContexts.Label String getShortLoadingErrorMessage(@NotNull IdeaPluginDescriptor pluginDescriptor) { - PluginLoadingError error = ourPluginLoadingErrors.get(pluginDescriptor.getPluginId()); + PluginLoadingError error = pluginLoadingErrors.get(pluginDescriptor.getPluginId()); return error == null ? null : error.getShortMessage(); } public static @Nullable PluginId getFirstDisabledDependency(@NotNull IdeaPluginDescriptor pluginDescriptor) { - PluginLoadingError error = ourPluginLoadingErrors.get(pluginDescriptor.getPluginId()); + PluginLoadingError error = pluginLoadingErrors.get(pluginDescriptor.getPluginId()); return error == null ? null : error.disabledDependency; } static @NotNull CachingSemiGraph createPluginIdGraph(@NotNull Collection descriptors, - @NotNull Map idToDescriptorMap, + @NotNull PluginSet pluginSet, boolean withOptional) { - boolean hasAllModules = idToDescriptorMap.containsKey(ALL_MODULES_MARKER); - Supplier javaDep = () -> idToDescriptorMap.get(JAVA_MODULE_ID); + boolean hasAllModules = pluginSet.isPluginEnabled(ALL_MODULES_MARKER); + Supplier javaDep = () -> pluginSet.findEnabledPlugin(JAVA_MODULE_ID); Set uniqueCheck = new HashSet<>(); Map> in = new HashMap<>(descriptors.size()); + List list = new ArrayList<>(32); for (IdeaPluginDescriptorImpl descriptor : descriptors) { - List list = getDirectDependencies(descriptor, idToDescriptorMap, withOptional, hasAllModules, javaDep, uniqueCheck); + getDirectDependencies(descriptor, pluginSet, withOptional, hasAllModules, javaDep, uniqueCheck, list); if (!list.isEmpty()) { - in.put(descriptor, list); + in.put(descriptor, Java11Shim.INSTANCE.copyOf(list)); + list.clear(); } } return new CachingSemiGraph<>(descriptors, in); } - private static @NotNull List getDirectDependencies(@NotNull IdeaPluginDescriptorImpl rootDescriptor, - @NotNull Map idToDescriptorMap, - boolean withOptional, - boolean hasAllModules, - @NotNull Supplier javaDep, - @NotNull Set uniqueCheck) { - List dependencies = rootDescriptor.pluginDependencies; + private static void getDirectDependencies(@NotNull IdeaPluginDescriptorImpl rootDescriptor, + @NotNull PluginSet pluginSet, + boolean withOptional, + boolean hasAllModules, + @NotNull Supplier javaDep, + @NotNull Set uniqueCheck, + @NotNull List result) { IdeaPluginDescriptorImpl implicitDep = hasAllModules ? getImplicitDependency(rootDescriptor, javaDep) : null; - int capacity = dependencies.size() + rootDescriptor.incompatibilities.size(); - if (!withOptional) { - for (PluginDependency dependency : dependencies) { - if (dependency.isOptional()) { - capacity--; - } - } - } - if (capacity == 0) { - return implicitDep == null ? Collections.emptyList() : Collections.singletonList(implicitDep); - } uniqueCheck.clear(); - List plugins = new ArrayList<>(capacity + (implicitDep == null ? 0 : 1)); if (implicitDep != null) { if (rootDescriptor == implicitDep) { getLogger().error("Plugin " + rootDescriptor + " depends on self"); } else { uniqueCheck.add(implicitDep); - plugins.add(implicitDep); + result.add(implicitDep); } } - for (PluginDependency dependency : dependencies) { + for (PluginDependency dependency : rootDescriptor.pluginDependencies) { if (!withOptional && dependency.isOptional()) { continue; } // check for missing optional dependency - IdeaPluginDescriptorImpl dep = idToDescriptorMap.get(dependency.getPluginId()); + IdeaPluginDescriptorImpl dep = pluginSet.findEnabledPlugin(dependency.getPluginId()); // if 'dep' refers to a module we need to check the real plugin containing this module only if it's still enabled, // otherwise the graph will be inconsistent if (dep == null) { @@ -642,24 +615,51 @@ public final class PluginManagerCore { } } else if (uniqueCheck.add(dep)) { - plugins.add(dep); + result.add(dep); } } + directDependenciesOfModule(rootDescriptor, pluginSet, uniqueCheck, result); + + // graph for plugins, not for modules - so, dependency of content must be taken into account + for (PluginContentDescriptor.ModuleItem module : rootDescriptor.content.modules) { + directDependenciesOfModule(module.requireDescriptor(), pluginSet, uniqueCheck, result); + } + for (PluginId moduleId : rootDescriptor.incompatibilities) { - IdeaPluginDescriptorImpl dep = idToDescriptorMap.get(moduleId); + IdeaPluginDescriptorImpl dep = pluginSet.findEnabledPlugin(moduleId); if (dep != null && uniqueCheck.add(dep)) { - plugins.add(dep); + result.add(dep); + } + } + } + + private static void directDependenciesOfModule(@NotNull IdeaPluginDescriptorImpl module, + @NotNull PluginSet pluginSet, + @NotNull Set uniqueCheck, + @NotNull List result) { + for (ModuleDependenciesDescriptor.ModuleReference item : module.dependencies.modules) { + PluginContentDescriptor.ModuleItem dep = pluginSet.findEnabledModule(item.name); + if (dep != null) { + IdeaPluginDescriptorImpl descriptor = dep.requireDescriptor(); + if (uniqueCheck.add(descriptor)) { + result.add(descriptor); + } } } - return plugins; + for (ModuleDependenciesDescriptor.PluginItem item : module.dependencies.plugins) { + IdeaPluginDescriptorImpl descriptor = pluginSet.findEnabledPlugin(item.id); + if (descriptor != null && uniqueCheck.add(descriptor)) { + result.add(descriptor); + } + } } private static void checkPluginCycles(@NotNull List descriptors, - @NotNull Map idToDescriptorMap, + @NotNull PluginSet pluginSet, @NotNull List> errors) { - CachingSemiGraph graph = createPluginIdGraph(descriptors, idToDescriptorMap, true); + CachingSemiGraph graph = createPluginIdGraph(descriptors, pluginSet, true); DFSTBuilder builder = new DFSTBuilder<>(GraphGenerator.generate(graph)); if (builder.isAcyclic()) { return; @@ -776,52 +776,16 @@ public final class PluginManagerCore { } @ApiStatus.Internal - public static @NotNull CompletionStage> initPlugins(@NotNull ClassLoader coreClassLoader) { + public static @NotNull CompletableFuture> initPlugins(@NotNull ClassLoader coreClassLoader) { CompletableFuture future = descriptorListFuture; if (future == null) { - future = CompletableFuture.completedFuture(null); + throw new IllegalStateException("Call scheduleDescriptorLoading() first"); } return future.thenApply(context -> { - loadAndInitializePlugins(context, coreClassLoader); - return ourLoadedPlugins; + return loadAndInitializePlugins(context, coreClassLoader).loadedPlugins; }); } - private static @NotNull Map loadAdditionalLayoutMap() { - Path fileWithLayout = usePluginClassLoader - ? Paths.get(PathManager.getSystemPath(), PlatformUtils.getPlatformPrefix() + ".txt") - : null; - if (fileWithLayout == null || !Files.exists(fileWithLayout)) { - return Collections.emptyMap(); - } - - Map additionalLayoutMap = new LinkedHashMap<>(); - try (BufferedReader bufferedReader = Files.newBufferedReader(fileWithLayout)) { - String line; - while ((line = bufferedReader.readLine()) != null) { - List parameters = ParametersListUtil.parse(line.trim()); - if (parameters.size() < 2) { - continue; - } - additionalLayoutMap.put(parameters.get(0), ArrayUtilRt.toStringArray(parameters.subList(1, parameters.size()))); - } - } - catch (Exception ignored) { - } - return additionalLayoutMap; - } - - /** - * not used by plugin manager - only for dynamic plugin reloading. - * Building plugin graph and using `getInList` as it is done for regular loading is not required - all that magic and checks - * are not required here because only regular plugins maybe dynamically reloaded. - */ - @ApiStatus.Internal - public static @NotNull ClassLoaderConfigurator createClassLoaderConfiguratorForDynamicPlugin(@NotNull IdeaPluginDescriptorImpl pluginDescriptor) { - Map idMap = buildPluginIdMap(ContainerUtil.concat(getLoadedPlugins(null), Collections.singletonList(pluginDescriptor))); - return new ClassLoaderConfigurator(true, PluginManagerCore.class.getClassLoader(), idMap, ourAdditionalLayoutMap); - } - public static @NotNull BuildNumber getBuildNumber() { BuildNumber result = ourBuildNumber; if (result == null) { @@ -1019,11 +983,12 @@ public final class PluginManagerCore { } List descriptors = loadingResult.getEnabledPlugins(); + PluginSet rawPluginSet = new PluginSet(descriptors, descriptors); disableIncompatiblePlugins(descriptors, idMap, pluginErrors); - checkPluginCycles(descriptors, idMap, globalErrors); + checkPluginCycles(descriptors, rawPluginSet, globalErrors); // topological sort based on required dependencies only - IdeaPluginDescriptorImpl[] sortedRequired = getTopologicallySorted(createPluginIdGraph(descriptors, idMap, false)); + List sortedRequired = getTopologicallySorted(createPluginIdGraph(descriptors, rawPluginSet, false)); Set enabledPluginIds = new LinkedHashSet<>(); Set enabledModuleIds = new LinkedHashSet<>(); @@ -1047,92 +1012,32 @@ public final class PluginManagerCore { prepareLoadingPluginsErrorMessage(disabledIds, disabledRequiredIds, idMap, pluginErrors, globalErrors); // topological sort based on all (required and optional) dependencies - CachingSemiGraph graph = createPluginIdGraph(Arrays.asList(sortedRequired), idMap, true); - IdeaPluginDescriptorImpl[] sortedAll = getTopologicallySorted(graph); - - List enabledPlugins = getOnlyEnabledPlugins(sortedAll); - - for (IdeaPluginDescriptorImpl plugin : enabledPlugins) { - checkOptionalDescriptors(plugin.pluginDependencies, idMap); + CachingSemiGraph graph = createPluginIdGraph(sortedRequired, rawPluginSet, true); + List allPlugins = getTopologicallySorted(graph); + List enabledPlugins = getOnlyEnabledPlugins(allPlugins); + if (!context.result.incompletePlugins.isEmpty()) { + allPlugins.addAll(context.result.incompletePlugins.values()); } - Map additionalLayoutMap = loadAdditionalLayoutMap(); - ourAdditionalLayoutMap = additionalLayoutMap; - ClassLoaderConfigurator classLoaderConfigurator = new ClassLoaderConfigurator(context.usePluginClassLoader, coreLoader, idMap, - additionalLayoutMap); - enabledPlugins.forEach(classLoaderConfigurator::configure); + PluginSet pluginSet = new PluginSet(allPlugins, enabledPlugins); + + ClassLoaderConfigurator classLoaderConfigurator = new ClassLoaderConfigurator(pluginSet, coreLoader, context.usePluginClassLoader); + pluginSet.loadedPlugins.forEach(classLoaderConfigurator::configure); if (checkEssentialPlugins) { checkEssentialPluginsAreAvailable(idMap); } - Set effectiveDisabledIds = disabledIds.isEmpty() ? Collections.emptySet() : new HashSet<>(disabledIds.keySet()); - return new PluginManagerState(sortedAll, enabledPlugins, disabledRequiredIds, effectiveDisabledIds, idMap); + Set effectiveDisabledIds = disabledIds.isEmpty() ? Collections.emptySet() : Java11Shim.INSTANCE.copyOf(disabledIds.keySet()); + return new PluginManagerState(pluginSet, disabledRequiredIds, effectiveDisabledIds); } - private static void checkOptionalDescriptors(@NotNull List pluginDependencies, - @NotNull Map idMap) { - for (PluginDependency dependency : pluginDependencies) { - IdeaPluginDescriptorImpl subDescriptor = dependency.subDescriptor; - if (subDescriptor == null || dependency.isDisabledOrBroken) { - continue; - } - - IdeaPluginDescriptorImpl dependencyDescriptor = idMap.get(dependency.getPluginId()); - if (dependencyDescriptor == null || !dependencyDescriptor.isEnabled()) { - dependency.isDisabledOrBroken = true; - continue; - } - - // check that plugin doesn't depend on unavailable plugin - List childDependencies = subDescriptor.pluginDependencies; - if (!checkChildDeps(childDependencies, idMap)) { - dependency.isDisabledOrBroken = true; - } - } - } - - // multiple dependency condition is not supported, so, - // jsp-javaee.xml depends on com.intellij.javaee.web, and included file in turn define jsp-css.xml that depends on com.intellij.css - // that's why nesting level is more than one - private static boolean checkChildDeps(@NotNull List childDependencies, @NotNull Map idMap) { - for (PluginDependency dependency : childDependencies) { - if (dependency.isDisabledOrBroken) { - if (dependency.isOptional()) { - continue; - } - return false; - } - - IdeaPluginDescriptorImpl dependentDescriptor = idMap.get(dependency.getPluginId()); - if (dependentDescriptor == null || !dependentDescriptor.isEnabled()) { - dependency.isDisabledOrBroken = true; - if (dependency.isOptional()) { - continue; - } - return false; - } - - if (dependency.subDescriptor != null) { - List list = dependency.subDescriptor.pluginDependencies; - if (!checkChildDeps(list, idMap)) { - dependency.isDisabledOrBroken = true; - if (dependency.isOptional()) { - continue; - } - return false; - } - } - } - return true; - } - - static @NotNull IdeaPluginDescriptorImpl @NotNull [] getTopologicallySorted(@NotNull InboundSemiGraph graph) { + static @NotNull List getTopologicallySorted(@NotNull InboundSemiGraph graph) { DFSTBuilder requiredOnlyGraph = new DFSTBuilder<>(GraphGenerator.generate(graph)); - IdeaPluginDescriptorImpl[] sortedRequired = graph.getNodes().toArray(new IdeaPluginDescriptorImpl[0]); + List sortedRequired = new ArrayList<>(graph.getNodes()); Comparator 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) - Arrays.sort(sortedRequired, (o1, o2) -> { + sortedRequired.sort((o1, o2) -> { return CORE_ID.equals(o1.getPluginId()) ? -1 : CORE_ID.equals(o2.getPluginId()) ? 1 : comparator.compare(o1, o2); }); return sortedRequired; @@ -1280,38 +1185,23 @@ public final class PluginManagerCore { } @SuppressWarnings("NonPrivateFieldAccessedInSynchronizedContext") - private static synchronized void loadAndInitializePlugins(@Nullable DescriptorListLoadingContext context, @Nullable ClassLoader coreLoader) { - if (coreLoader == null) { - Class callerClass = ReflectionUtil.findCallerClass(1); - assert callerClass != null; - coreLoader = callerClass.getClassLoader(); - } - + private static synchronized @NotNull PluginSet loadAndInitializePlugins(@NotNull DescriptorListLoadingContext context, + @NotNull ClassLoader coreLoader) { try { - if (context == null) { - //noinspection resource - context = PluginDescriptorLoader.loadDescriptors(isUnitTestMode, isRunningFromSources()); - } Activity activity = StartUpMeasurer.startActivity("plugin initialization", ActivityCategory.DEFAULT); PluginManagerState initResult = initializePlugins(context, coreLoader, !isUnitTestMode); - - ourPlugins = initResult.sortedPlugins; PluginLoadingResult result = context.result; - if (!result.incompletePlugins.isEmpty()) { - int oldSize = initResult.sortedPlugins.length; - IdeaPluginDescriptorImpl[] all = Arrays.copyOf(initResult.sortedPlugins, oldSize + result.incompletePlugins.size()); - ArrayUtil.copy(result.incompletePlugins.values(), all, oldSize); - ourPlugins = all; - } ourPluginsToDisable = initResult.effectiveDisabledIds; ourPluginsToEnable = initResult.disabledRequiredIds; - ourLoadedPlugins = initResult.sortedEnabledPlugins; + ourShadowedBundledPlugins = result.shadowedBundledIds; activity.end(); - activity.setDescription("plugin count: " + ourLoadedPlugins.size()); - logPlugins(initResult.sortedPlugins, result.incompletePlugins.values()); + activity.setDescription("plugin count: " + initResult.pluginSet.loadedPlugins.size()); + logPlugins(initResult.pluginSet.allPlugins, result.incompletePlugins.values()); + pluginSet = initResult.pluginSet; + return initResult.pluginSet; } catch (RuntimeException e) { getLogger().error(e); @@ -1348,7 +1238,7 @@ public final class PluginManagerCore { } public static @Nullable IdeaPluginDescriptor findPluginByModuleDependency(@NotNull PluginId id) { - for (IdeaPluginDescriptorImpl descriptor : ourPlugins) { + for (IdeaPluginDescriptorImpl descriptor : getPluginSet().allPlugins) { if (descriptor.modules.contains(id)) { return descriptor; } @@ -1363,7 +1253,7 @@ public final class PluginManagerCore { @ApiStatus.Internal public static @NotNull Map buildPluginIdMap() { LoadingState.COMPONENTS_REGISTERED.checkOccurred(); - return buildPluginIdMap(Arrays.asList(ourPlugins)); + return buildPluginIdMap(Objects.requireNonNull(pluginSet).allPlugins); } /** @@ -1430,8 +1320,8 @@ public final class PluginManagerCore { return true; } - private static @NotNull List getOnlyEnabledPlugins(@NotNull IdeaPluginDescriptorImpl @NotNull[] sortedAll) { - List enabledPlugins = new ArrayList<>(sortedAll.length); + private static @NotNull List getOnlyEnabledPlugins(@NotNull List sortedAll) { + List enabledPlugins = new ArrayList<>(sortedAll.size()); for (IdeaPluginDescriptorImpl descriptor : sortedAll) { if (descriptor.isEnabled()) { enabledPlugins.add(descriptor); diff --git a/platform/core-impl/src/com/intellij/ide/plugins/PluginManagerState.java b/platform/core-impl/src/com/intellij/ide/plugins/PluginManagerState.java index 24abd1d5ce5a..d1a5f60e9f78 100644 --- a/platform/core-impl/src/com/intellij/ide/plugins/PluginManagerState.java +++ b/platform/core-impl/src/com/intellij/ide/plugins/PluginManagerState.java @@ -1,30 +1,22 @@ -// 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. 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 org.jetbrains.annotations.NotNull; -import java.util.List; -import java.util.Map; import java.util.Set; +// todo merge into PluginSetState? public final class PluginManagerState { final Set effectiveDisabledIds; final Set disabledRequiredIds; - final IdeaPluginDescriptorImpl @NotNull [] sortedPlugins; - final List sortedEnabledPlugins; + final PluginSet pluginSet; - final Map idMap; - - PluginManagerState(@NotNull IdeaPluginDescriptorImpl @NotNull [] sortedPlugins, - @NotNull List sortedEnabledPlugins, + PluginManagerState(@NotNull PluginSet pluginSet, @NotNull Set disabledRequiredIds, - @NotNull Set effectiveDisabledIds, - @NotNull Map idMap) { - this.sortedPlugins = sortedPlugins; - this.sortedEnabledPlugins = sortedEnabledPlugins; + @NotNull Set effectiveDisabledIds) { + this.pluginSet = pluginSet; this.disabledRequiredIds = disabledRequiredIds; this.effectiveDisabledIds = effectiveDisabledIds; - this.idMap = idMap; } } diff --git a/platform/core-impl/src/com/intellij/ide/plugins/PluginSet.kt b/platform/core-impl/src/com/intellij/ide/plugins/PluginSet.kt new file mode 100644 index 000000000000..35b43394ea9a --- /dev/null +++ b/platform/core-impl/src/com/intellij/ide/plugins/PluginSet.kt @@ -0,0 +1,75 @@ +// 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.lang.Java11Shim +import org.jetbrains.annotations.ApiStatus + +// if otherwise not specified, `module` in terms of v2 plugin model +@ApiStatus.Internal +class PluginSet( + @JvmField val allPlugins: List, + loadedPlugins: List, +) { + @JvmField val loadedPlugins: List = Java11Shim.INSTANCE.copyOf(loadedPlugins) + + private val enabledPluginAndV1ModuleMap: Map + //private val allPluginAndV1ModuleMap: Map + + // module map in a new v2 format + private val moduleMap: Map + + init { + val enabledPluginAndV1ModuleMap = HashMap(loadedPlugins.size) + //val allPluginAndV1ModuleMap = HashMap(allPlugins.size) + val moduleMap = HashMap() + for (descriptor in loadedPlugins) { + addWithV1Modules(enabledPluginAndV1ModuleMap, descriptor) + + for (module in descriptor.content.modules) { + if (module.configFile == null) { + val duplicate = moduleMap.putIfAbsent(module.name, module) + if (duplicate != null) { + throw RuntimeException("Duplicated module name (first=$duplicate, second=$module)") + } + } + } + + if (descriptor.packagePrefix != null) { + val pluginAsModuleItem = PluginContentDescriptor.ModuleItem(name = descriptor.id.idString, configFile = null) + pluginAsModuleItem.descriptor = descriptor + moduleMap.put(pluginAsModuleItem.name, pluginAsModuleItem) + } + } + + //for (descriptor in allPlugins) { + // addWithV1Modules(allPluginAndV1ModuleMap, descriptor) + //} + + val java11Shim = Java11Shim.INSTANCE + this.enabledPluginAndV1ModuleMap = java11Shim.copyOf(enabledPluginAndV1ModuleMap) + //this.allPluginAndV1ModuleMap = java11Shim.copyOf(allPluginAndV1ModuleMap) + this.moduleMap = java11Shim.copyOf(moduleMap) + } + + fun isPluginEnabled(id: PluginId): Boolean = enabledPluginAndV1ModuleMap.containsKey(id) + + fun findEnabledPlugin(id: PluginId): IdeaPluginDescriptorImpl? = enabledPluginAndV1ModuleMap.get(id) + + //fun findPlugin(id: PluginId): IdeaPluginDescriptorImpl? = allPluginAndV1ModuleMap.get(id) + + // module in term of v2 model + fun findEnabledModule(id: String): PluginContentDescriptor.ModuleItem? = moduleMap.get(id) + + fun concat(descriptor: IdeaPluginDescriptorImpl): PluginSet { + return PluginSet(allPlugins = allPlugins.plus(descriptor), + loadedPlugins = Java11Shim.INSTANCE.copyOf(loadedPlugins.plus(descriptor))) + } + + private fun addWithV1Modules(result: MutableMap, descriptor: IdeaPluginDescriptorImpl) { + result.put(descriptor.id, descriptor) + for (module in descriptor.modules) { + result.put(module, descriptor) + } + } +} \ No newline at end of file diff --git a/platform/core-impl/src/com/intellij/ide/plugins/PluginXmlPathResolver.kt b/platform/core-impl/src/com/intellij/ide/plugins/PluginXmlPathResolver.kt index a94c2a17ed37..9c9fab436031 100644 --- a/platform/core-impl/src/com/intellij/ide/plugins/PluginXmlPathResolver.kt +++ b/platform/core-impl/src/com/intellij/ide/plugins/PluginXmlPathResolver.kt @@ -136,6 +136,21 @@ class PluginXmlPathResolver(private val pluginJarFiles: List) : PathResolv return null } + override fun resolveModuleFile(readContext: ReadModuleContext, + dataLoader: DataLoader, + path: String, + readInto: RawPluginDescriptor?): RawPluginDescriptor { + val input = dataLoader.load(path) + ?: throw RuntimeException("Cannot resolve $path (dataLoader=$dataLoader, pluginJarFiles=${pluginJarFiles.joinToString(separator = "\n ")})") + return readModuleDescriptor(input = input, + readContext = readContext, + pathResolver = this, + dataLoader = dataLoader, + includeBase = null, + readInto = readInto, + locationSource = null) + } + private fun findInJarFiles(readInto: RawPluginDescriptor, readContext: ReadModuleContext, dataLoader: DataLoader, diff --git a/platform/core-impl/src/com/intellij/ide/plugins/RawPluginDescriptor.kt b/platform/core-impl/src/com/intellij/ide/plugins/RawPluginDescriptor.kt index 58d327e99096..e98d72c1a16b 100644 --- a/platform/core-impl/src/com/intellij/ide/plugins/RawPluginDescriptor.kt +++ b/platform/core-impl/src/com/intellij/ide/plugins/RawPluginDescriptor.kt @@ -52,8 +52,8 @@ class RawPluginDescriptor { @JvmField var epNameToExtensions: MutableMap>? = null - @JvmField internal var contentDescriptor = PluginContentDescriptor.EMPTY - @JvmField internal var dependencyDescriptor = ModuleDependenciesDescriptor.EMPTY + @JvmField internal var content = PluginContentDescriptor.EMPTY + @JvmField internal var dependencies = ModuleDependenciesDescriptor.EMPTY class ActionDescriptor( @JvmField val name: String, diff --git a/platform/core-impl/src/com/intellij/ide/plugins/SubPluginClassLoader.java b/platform/core-impl/src/com/intellij/ide/plugins/SubPluginClassLoader.java deleted file mode 100644 index a052d94e2106..000000000000 --- a/platform/core-impl/src/com/intellij/ide/plugins/SubPluginClassLoader.java +++ /dev/null @@ -1,55 +0,0 @@ -// 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. -package com.intellij.ide.plugins; - -import com.intellij.ide.plugins.cl.PluginClassLoader; -import com.intellij.util.lang.ClassPath; -import com.intellij.util.lang.UrlClassLoader; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.io.IOException; - -final class SubPluginClassLoader extends PluginClassLoader { - private final String[] packagePrefixes; - - static { - registerAsParallelCapable(); - } - - SubPluginClassLoader(@NotNull IdeaPluginDescriptorImpl pluginDescriptor, - @NotNull UrlClassLoader.Builder urlClassLoaderBuilder, - @NotNull ClassLoader @NotNull [] parents, - @NotNull String @NotNull [] packagePrefixes, - @NotNull ClassLoader coreLoader, - @Nullable ClassPath.ResourceFileFactory resourceFileFactory) { - super(urlClassLoaderBuilder, parents, pluginDescriptor, pluginDescriptor.getPluginPath(), coreLoader, null, null, resourceFileFactory); - - assert pluginDescriptor.packagePrefix == null; - this.packagePrefixes = packagePrefixes; - } - - @Override - public @Nullable Class loadClassInsideSelf(@NotNull String name, boolean forceLoadFromSubPluginClassloader) throws IOException { - if (forceLoadFromSubPluginClassloader) { - return super.loadClassInsideSelf(name, true); - } - - for (String packagePrefix : packagePrefixes) { - if (name.startsWith(packagePrefix)) { - return super.loadClassInsideSelf(name, true); - } - } - - int subIndex = name.indexOf('$'); - if (subIndex > 0) { - // load inner classes - // we check findLoadedClass because classNames doesn't have full set of suitable names - PluginAwareClassLoader.SubClassLoader is used to force loading classes from sub classloader - Class loadedClass = findLoadedClass(name.substring(0, subIndex)); - if (loadedClass != null && loadedClass.getClassLoader() == this) { - return super.loadClassInsideSelf(name, true); - } - } - - return null; - } -} diff --git a/platform/core-impl/src/com/intellij/ide/plugins/XmlReader.kt b/platform/core-impl/src/com/intellij/ide/plugins/XmlReader.kt index 5d368097e3a0..09c007796354 100644 --- a/platform/core-impl/src/com/intellij/ide/plugins/XmlReader.kt +++ b/platform/core-impl/src/com/intellij/ide/plugins/XmlReader.kt @@ -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. @file:JvmName("XmlReader") @file:Suppress("ReplaceNegatedIsEmptyWithIsNotEmpty") package com.intellij.ide.plugins @@ -14,6 +14,7 @@ import com.intellij.openapi.util.createNonCoalescingXmlStreamReader import com.intellij.platform.util.plugins.DataLoader import com.intellij.util.NoOpXmlInterner import com.intellij.util.XmlInterner +import com.intellij.util.lang.Java11Shim import com.intellij.util.lang.ZipFilePool import com.intellij.util.messages.ListenerDescriptor import com.intellij.util.readXmlAsModel @@ -31,6 +32,8 @@ import javax.xml.stream.XMLStreamException import javax.xml.stream.XMLStreamReader import javax.xml.stream.events.XMLEvent +private const val defaultXPointerValue = "xpointer(/idea-plugin/*)" + fun readModuleDescriptor(inputStream: InputStream, readContext: ReadModuleContext, pathResolver: PathResolver, @@ -149,6 +152,7 @@ private fun readRootAttributes(reader: XMLStreamReader2, descriptor: RawPluginDe } } + private fun readRootElementChild(reader: XMLStreamReader2, descriptor: RawPluginDescriptor, localName: String, @@ -247,8 +251,10 @@ private fun readRootElementChild(reader: XMLStreamReader2, dataLoader = dataLoader, includeBase = includeBase) - "content" -> readContent(reader, descriptor) - "dependencies" -> readDependencies(reader, descriptor) + "content" -> readContent(reader = reader, + descriptor = descriptor, + readContext = readContext) + "dependencies" -> readDependencies(reader = reader, descriptor = descriptor, readContext = readContext) "depends" -> readOldDepends(reader, descriptor) @@ -260,7 +266,7 @@ private fun readRootElementChild(reader: XMLStreamReader2, pathResolver = pathResolver, dataLoader = dataLoader, includeBase = includeBase, - allowedPointer = "xpointer(/idea-plugin/*)") + allowedPointer = defaultXPointerValue) "helpset" -> { // deprecated and not used element reader.skipElement() @@ -311,7 +317,7 @@ private fun readOldDepends(reader: XMLStreamReader2, descriptor: RawPluginDescri for (i in 0 until reader.attributeCount) { when (reader.getAttributeLocalName(i)) { "optional" -> isOptional = reader.getAttributeAsBoolean(i) - "config-file" -> configFile = getNullifiedAttributeValue(reader, i) + "config-file" -> configFile = reader.getAttributeValue(i) } } @@ -321,8 +327,7 @@ private fun readOldDepends(reader: XMLStreamReader2, descriptor: RawPluginDescri depends = ArrayList() descriptor.depends = depends } - depends.add(PluginDependency(pluginId = PluginId.getId(dependencyIdString), configFile = configFile, isOptional = isOptional, - isDisabledOrBroken = false)) + depends.add(PluginDependency(pluginId = PluginId.getId(dependencyIdString), configFile = configFile, isOptional = isOptional)) } private fun readExtensions(reader: XMLStreamReader2, descriptor: RawPluginDescriptor, interner: XmlInterner) { @@ -638,64 +643,69 @@ private fun readComponents(reader: XMLStreamReader2, containerDescriptor: Contai } } -private fun readContent(reader: XMLStreamReader2, descriptor: RawPluginDescriptor) { +private fun readContent(reader: XMLStreamReader2, + descriptor: RawPluginDescriptor, + readContext: ReadModuleContext) { val items = ArrayList() reader.consumeChildElements { elementName -> when (elementName) { "module" -> { var name: String? = null - var packageName: String? = null - var configFile: String? = null for (i in 0 until reader.attributeCount) { when (reader.getAttributeLocalName(i)) { - "name" -> name = getNullifiedAttributeValue(reader, i) - "package" -> packageName = getNullifiedAttributeValue(reader, i) - "configFile" -> configFile = getNullifiedAttributeValue(reader, i) + "name" -> name = readContext.interner.name(reader.getAttributeValue(i)) } } - items.add(PluginContentDescriptor.ModuleItem(name = name ?: throw RuntimeException("Name is not specified at ${reader.location}"), - packageName = packageName, + if (name.isNullOrEmpty()) { + throw RuntimeException("Name is not specified at ${reader.location}") + } + + var configFile: String? = null + val index = name.lastIndexOf('/') + if (index != -1) { + configFile = "${name.substring(0, index)}.${name.substring(index + 1)}.xml" + } + + items.add(PluginContentDescriptor.ModuleItem(name = name, configFile = configFile)) } - else -> throw RuntimeException("Unknown content item type: ${elementName}") + else -> throw RuntimeException("Unknown content item type: $elementName") } reader.skipElement() } - descriptor.contentDescriptor = PluginContentDescriptor(items) + descriptor.content = PluginContentDescriptor(Java11Shim.INSTANCE.copyOf(items)) assert(reader.isEndElement) } -private fun readDependencies(reader: XMLStreamReader2, descriptor: RawPluginDescriptor) { - var modules: MutableList? = null +private fun readDependencies(reader: XMLStreamReader2, descriptor: RawPluginDescriptor, readContext: ReadModuleContext) { + var modules: MutableList? = null var plugins: MutableList? = null reader.consumeChildElements { elementName -> when (elementName) { "module" -> { var name: String? = null - var packageName: String? = null for (i in 0 until reader.attributeCount) { when (reader.getAttributeLocalName(i)) { - "name" -> name = getNullifiedAttributeValue(reader, i) - "package" -> packageName = getNullifiedAttributeValue(reader, i) + "name" -> name = readContext.interner.name(reader.getAttributeValue(i)) } } if (modules == null) { - modules = mutableListOf() + modules = ArrayList() } - modules!!.add(ModuleDependenciesDescriptor.ModuleItem(name!!, packageName)) + modules!!.add(ModuleDependenciesDescriptor.ModuleReference(name!!)) } "plugin" -> { var id: String? = null for (i in 0 until reader.attributeCount) { when (reader.getAttributeLocalName(i)) { - "id" -> id = getNullifiedAttributeValue(reader, i) + "id" -> id = readContext.interner.name(reader.getAttributeValue(i)) } } if (plugins == null) { - plugins = mutableListOf() + plugins = ArrayList() } plugins!!.add(ModuleDependenciesDescriptor.PluginItem(PluginId.getId(id!!))) } @@ -703,7 +713,7 @@ private fun readDependencies(reader: XMLStreamReader2, descriptor: RawPluginDesc } reader.skipElement() } - descriptor.dependencyDescriptor = ModuleDependenciesDescriptor(modules ?: emptyList(), plugins ?: emptyList()) + descriptor.dependencies = ModuleDependenciesDescriptor(modules ?: Collections.emptyList(), plugins ?: Collections.emptyList()) assert(reader.isEndElement) } @@ -716,11 +726,9 @@ private fun findAttributeValue(reader: XMLStreamReader2, name: String): String? return null } -private fun getNullifiedContent(reader: XMLStreamReader2): String? { - return reader.elementText.takeIf { it.isNotEmpty() } -} +private fun getNullifiedContent(reader: XMLStreamReader2): String? = reader.elementText.takeIf { !it.isEmpty() } -private fun getNullifiedAttributeValue(reader: XMLStreamReader2, i: Int) = reader.getAttributeValue(i).takeIf { it.isNotEmpty() } +private fun getNullifiedAttributeValue(reader: XMLStreamReader2, i: Int) = reader.getAttributeValue(i).takeIf { !it.isEmpty() } interface ReadModuleContext { val interner: XmlInterner diff --git a/platform/core-impl/src/com/intellij/ide/plugins/cl/PluginClassLoader.java b/platform/core-impl/src/com/intellij/ide/plugins/cl/PluginClassLoader.java index 771f542e9539..f4c166bfb135 100644 --- a/platform/core-impl/src/com/intellij/ide/plugins/cl/PluginClassLoader.java +++ b/platform/core-impl/src/com/intellij/ide/plugins/cl/PluginClassLoader.java @@ -86,6 +86,10 @@ public class PluginClassLoader extends UrlClassLoader implements PluginAwareClas String debugFilePath = System.getProperty("plugin.classloader.debug", ""); if (!debugFilePath.isEmpty()) { try { + if (debugFilePath.startsWith("~/") || debugFilePath.startsWith("~\\")) { + debugFilePath = System.getProperty("user.home") + debugFilePath.substring(1); + } + logStreamCandidate = Files.newBufferedWriter(Paths.get(debugFilePath)); ShutDownTracker.getInstance().registerShutdownTask(new Runnable() { @Override diff --git a/platform/credential-store/readme.md b/platform/credential-store/readme.md deleted file mode 100644 index ea00b079a777..000000000000 --- a/platform/credential-store/readme.md +++ /dev/null @@ -1 +0,0 @@ -Please refer to [Storing Sensitive Data](https://plugins.jetbrains.com/docs/intellij/persisting-sensitive-data.html) \ No newline at end of file diff --git a/platform/diagnostic/src/startUpPerformanceReporter/StartUpPerformanceReporter.kt b/platform/diagnostic/src/startUpPerformanceReporter/StartUpPerformanceReporter.kt index ff15c66e0114..0a2094070259 100644 --- a/platform/diagnostic/src/startUpPerformanceReporter/StartUpPerformanceReporter.kt +++ b/platform/diagnostic/src/startUpPerformanceReporter/StartUpPerformanceReporter.kt @@ -17,6 +17,7 @@ import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.extensions.ExtensionNotApplicableException import com.intellij.openapi.project.Project import com.intellij.openapi.startup.StartupActivity +import com.intellij.openapi.util.io.FileUtil import com.intellij.util.SystemProperties import com.intellij.util.concurrency.NonUrgentExecutor import com.intellij.util.io.jackson.IntelliJPrettyPrinter @@ -145,7 +146,7 @@ class StartUpPerformanceReporter : StartupActivity, StartUpPerformanceService { val classReport = System.getProperty("idea.log.class.list.file") if (!classReport.isNullOrBlank()) { - generateJarAccessLog(Path.of(classReport)) + generateJarAccessLog(Path.of(FileUtil.expandUserHome(classReport))) } return StartUpPerformanceReporterValues(pluginCostMap, currentReport, w.publicStatMetrics) } diff --git a/platform/docs/out/getting-service.svg b/platform/docs/out/getting-service.svg deleted file mode 100644 index 01f83f7c5741..000000000000 --- a/platform/docs/out/getting-service.svg +++ /dev/null @@ -1,165 +0,0 @@ - - - - - - - - - - - - - - - In any thread. - Get on demand only. - Do not cache result. - Do not request in constructor unless needed. - - getService - - Return - null - - no - Is - - Service Declaration - - Found - yes - - no - Is - - Light Service - - yes - - - Is Container Active? - active - disposed or dispose in progress - - - synchronized on service class - - Is Initializing? - yes - no - - Throw - PluginException - Cyclic Service Initialization - - - non cancelable - - - Avoid getting other services to reduce initialization tree. - As less dependencies, as more faster and reliable. - - Create Instance - - Register to be Disposed on Container Dispose - if Implements - Disposable - - Load Persistent State - if Implements - PersistentStateComponent - - no - Is Created and Initialized? - yes - - - Throw - ProcessCanceledException - - no - Is Created and Initialized? - yes - - - Return Instance - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/platform/docs/out/icon-loading-stat.svg b/platform/docs/out/icon-loading-stat.svg deleted file mode 100644 index fb8a65505754..000000000000 --- a/platform/docs/out/icon-loading-stat.svg +++ /dev/null @@ -1,186 +0,0 @@ - - - - - - - - - - - - - - - Externally called method - IconLoader.findIcon - - find-icon - - - Internally called method - CachedImageIcon.loadImage - . - Called numerous times per - CachedImageIcon - instance — to compute a new scale. - - find-icon-load - - Is SVG - yes - no - - svg-load - - png-load - - - Is resourceClass Provided - yes - no - - load-from-resource - - Is prebuiltCacheId Provided - yes - - svg-prebuilt - - Return Image - - Return - null - - no - Is Resource Exist - yes - - load-from-url - - - Is SVG - yes - no - - svg-cache-read - - svg-decode - - no - Is Cached - yes - - - png-decode - - - no - Is Cached - yes - - - Return Image - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/platform/docs/out/index.html b/platform/docs/out/index.html deleted file mode 100644 index cd97dc3f76e7..000000000000 --- a/platform/docs/out/index.html +++ /dev/null @@ -1,50 +0,0 @@ - - - - - - IntelliJ Platform Activity Diagrams - - - - -
-
- -
- - -
- - - - diff --git a/platform/docs/out/projectClose-dispose-flow.svg b/platform/docs/out/projectClose-dispose-flow.svg deleted file mode 100644 index 8ec70ba077d5..000000000000 --- a/platform/docs/out/projectClose-dispose-flow.svg +++ /dev/null @@ -1,176 +0,0 @@ - - - - - - - - - - - - - - - Project closing executed in a dispatch thread. - - closeProject - - canClose - (ep=ProjectCloseHandler) - - canClose - - (ep=ProjectCloseHandler) - yes - no - - Stop Service Preloading - - - In unit test mode in a light tests, - light project is not closed and not disposed. - - Fire - projectClosingBeforeSave - Event - (l=ProjectManagerListener) - - Save Project Files - - Save Project Settings - Successfully or Error Ignored - Error Occurred - - Fire - projectClosing - Event - (l=ProjectManagerListener) - - - write action - - - If you incorrectly specify project for - MessageBus.connect() - , - it will be disconnected on this step. - Do not specify - parentDisposable - unless needed. - - Dispose everything that uses Project as parent disposable - - Dispose Project Message Bus Connections - without explicitly specified parent disposable - - - Getting services and publishing to message bus - is prohibited from now on. - Project.isDisposed - returns - true - (not changed in a read action, - because state is set in a write action). - - Set Project State to - DISPOSE_IN_PROGRESS - - - Connecting to message bus is prohibited from now on. - - Dispose Project Message Bus Connection Disposable - - - Result of - ProjectManager.getOpenProjects() - is valid only in a read action. - - Remove Project from List of Opened - - Fire - projectClosed - Event - (l=ProjectManagerListener) - - Set Project State to - DISPOSED - - - First created, last disposed. - Children are disposed before parent. - - Dispose Services and Components - - Dispose Message Bus - - Set Project State to - DISPOSE_COMPLETED - - Close Cancelled - - Close Cancelled - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/platform/docs/readme.md b/platform/docs/readme.md deleted file mode 100644 index db141ac15c91..000000000000 --- a/platform/docs/readme.md +++ /dev/null @@ -1,6 +0,0 @@ -IntelliJ Platform SDK [docs](https://www.jetbrains.org/intellij/sdk/docs/welcome.html) sources located on [GitHub](https://github.com/JetBrains/intellij-sdk-docs). -This directory contains only activity diagrams sources. - -### How to write diagram in PlantUML - -Install [PlantUML integration](https://plugins.jetbrains.com/plugin/7017-plantuml-integration/) plugin to add PlantUML support to IntelliJ IDEA. \ No newline at end of file diff --git a/platform/extensions/src/com/intellij/util/XmlElement.kt b/platform/extensions/src/com/intellij/util/XmlElement.kt deleted file mode 100644 index 10ab73c983ec..000000000000 --- a/platform/extensions/src/com/intellij/util/XmlElement.kt +++ /dev/null @@ -1,3 +0,0 @@ -// 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. -package com.intellij.util - diff --git a/platform/lang-impl/src/com/intellij/ide/fileTemplates/impl/FileTemplatesLoader.java b/platform/lang-impl/src/com/intellij/ide/fileTemplates/impl/FileTemplatesLoader.java index c134fc0b0d8a..a62f66aeb5b8 100644 --- a/platform/lang-impl/src/com/intellij/ide/fileTemplates/impl/FileTemplatesLoader.java +++ b/platform/lang-impl/src/com/intellij/ide/fileTemplates/impl/FileTemplatesLoader.java @@ -191,7 +191,11 @@ class FileTemplatesLoader implements Disposable { if (!processedUrls.add(url)) { continue; } - loadDefaultsFromRoot(url, prefixes, result); + + List children = UrlUtil.getChildrenRelativePaths(url); + if (!children.isEmpty()) { + loadDefaultsFromRoot(url, children, prefixes, result); + } } } catch (IOException e) { @@ -201,14 +205,11 @@ class FileTemplatesLoader implements Disposable { return result; } - private static void loadDefaultsFromRoot(@NotNull URL root, @NotNull List prefixes, @NotNull FileTemplateLoadResult result) - throws IOException { - final List children = UrlUtil.getChildrenRelativePaths(root); - if (children.isEmpty()) { - return; - } - - final Set descriptionPaths = new HashSet<>(); + private static void loadDefaultsFromRoot(@NotNull URL root, + @NotNull List children, + @NotNull List prefixes, + @NotNull FileTemplateLoadResult result) throws IOException { + Set descriptionPaths = new HashSet<>(); for (String path : children) { if (path.equals("default.html")) { result.setDefaultTemplateDescription( @@ -222,7 +223,7 @@ class FileTemplatesLoader implements Disposable { } } - for (final String path : children) { + for (String path : children) { if (!path.endsWith(FTManager.TEMPLATE_EXTENSION_SUFFIX)) { continue; } diff --git a/platform/lang-impl/src/com/intellij/ide/fileTemplates/impl/UrlUtil.java b/platform/lang-impl/src/com/intellij/ide/fileTemplates/impl/UrlUtil.java index ebc05ff1d4c7..50dd6b66077a 100644 --- a/platform/lang-impl/src/com/intellij/ide/fileTemplates/impl/UrlUtil.java +++ b/platform/lang-impl/src/com/intellij/ide/fileTemplates/impl/UrlUtil.java @@ -35,34 +35,36 @@ public final class UrlUtil { } } - @NotNull - public static List getChildrenRelativePaths(@NotNull URL root) throws IOException { - final String protocol = root.getProtocol(); + public static @NotNull List getChildrenRelativePaths(@NotNull URL root) throws IOException { + String protocol = root.getProtocol(); if ("jar".equalsIgnoreCase(protocol)) { return getChildPathsFromJar(root); } - if (FILE_PROTOCOL.equalsIgnoreCase(protocol)){ + else if (FILE_PROTOCOL.equalsIgnoreCase(protocol)) { return getChildPathsFromFile(root); } - return Collections.emptyList(); + else { + return Collections.emptyList(); + } } - @NotNull - private static List getChildPathsFromFile(@NotNull URL root) { + private static @NotNull List getChildPathsFromFile(@NotNull URL root) { final List paths = new ArrayList<>(); final File rootFile = new File(FileUtil.unquote(root.getPath())); new Object() { void collectFiles(File fromFile, String prefix) { - final File[] list = fromFile.listFiles(); - if (list != null) { - for (File file : list) { - final String childRelativePath = prefix.isEmpty() ? file.getName() : prefix + URL_PATH_SEPARATOR + file.getName(); - if (file.isDirectory()) { - collectFiles(file, childRelativePath); - } - else { - paths.add(childRelativePath); - } + File[] list = fromFile.listFiles(); + if (list == null) { + return; + } + + for (File file : list) { + String childRelativePath = prefix.isEmpty() ? file.getName() : prefix + URL_PATH_SEPARATOR + file.getName(); + if (file.isDirectory()) { + collectFiles(file, childRelativePath); + } + else { + paths.add(childRelativePath); } } } @@ -70,24 +72,24 @@ public final class UrlUtil { return paths; } - @NotNull - private static List getChildPathsFromJar(@NotNull URL root) throws IOException { + private static @NotNull List getChildPathsFromJar(@NotNull URL root) throws IOException { String file = root.getFile(); file = StringUtil.trimStart(file, FILE_PROTOCOL_PREFIX); - final int jarSeparatorIndex = file.indexOf(JAR_SEPARATOR); + int jarSeparatorIndex = file.indexOf(JAR_SEPARATOR); assert jarSeparatorIndex > 0; String rootDirName = file.substring(jarSeparatorIndex + 2); if (!rootDirName.endsWith(URL_PATH_SEPARATOR)) { rootDirName += URL_PATH_SEPARATOR; } + try (ZipFile zipFile = new ZipFile(FileUtil.unquote(file.substring(0, jarSeparatorIndex)))) { - final Enumeration entries = zipFile.entries(); - final List paths = new ArrayList<>(); + Enumeration entries = zipFile.entries(); + List paths = new ArrayList<>(); while (entries.hasMoreElements()) { - final ZipEntry entry = entries.nextElement(); + ZipEntry entry = entries.nextElement(); if (!entry.isDirectory()) { - final String relPath = entry.getName(); + String relPath = entry.getName(); if (relPath.startsWith(rootDirName)) { paths.add(relPath.substring(rootDirName.length())); } diff --git a/platform/lang-impl/src/com/intellij/workspaceModel/ide/impl/legacyBridge/module/ModuleManagerComponentBridge.kt b/platform/lang-impl/src/com/intellij/workspaceModel/ide/impl/legacyBridge/module/ModuleManagerComponentBridge.kt index b10e9904d77d..441f7307d0c0 100644 --- a/platform/lang-impl/src/com/intellij/workspaceModel/ide/impl/legacyBridge/module/ModuleManagerComponentBridge.kt +++ b/platform/lang-impl/src/com/intellij/workspaceModel/ide/impl/legacyBridge/module/ModuleManagerComponentBridge.kt @@ -325,7 +325,7 @@ class ModuleManagerComponentBridge(private val project: Project) : ModuleManager LOG.debug { "Loading modules for ${loadedEntities.size} entities" } val plugins = PluginManagerCore.getLoadedPlugins(null) - val corePlugin = plugins.find { it.pluginId == PluginManagerCore.CORE_ID } + val corePlugin = plugins.firstOrNull { it.pluginId == PluginManagerCore.CORE_ID } val precomputedExtensionModel = precomputeExtensionModel(plugins) diff --git a/platform/platform-impl/src/com/intellij/ide/plugins/ClassLoaderTreeChecker.kt b/platform/platform-impl/src/com/intellij/ide/plugins/ClassLoaderTreeChecker.kt index 26f3a63253ee..9afb462910a2 100644 --- a/platform/platform-impl/src/com/intellij/ide/plugins/ClassLoaderTreeChecker.kt +++ b/platform/platform-impl/src/com/intellij/ide/plugins/ClassLoaderTreeChecker.kt @@ -10,7 +10,7 @@ private val LOG = logger() internal class ClassLoaderTreeChecker(private val unloadedMainDescriptor: IdeaPluginDescriptorImpl, private val classLoaders: WeakList) { fun checkThatClassLoaderNotReferencedByPluginClassLoader() { - if (!ClassLoaderConfigurationData.SEPARATE_CLASSLOADER_FOR_SUB || unloadedMainDescriptor.classLoader !is PluginClassLoader) { + if (unloadedMainDescriptor.classLoader !is PluginClassLoader) { return } @@ -37,6 +37,7 @@ internal class ClassLoaderTreeChecker(private val unloadedMainDescriptor: IdeaPl } } + @Suppress("TestOnlyProblems") val parents = classLoader._getParents() for (unloadedClassLoader in classLoaders) { diff --git a/platform/platform-impl/src/com/intellij/ide/plugins/CreateAllServicesAndExtensionsAction.kt b/platform/platform-impl/src/com/intellij/ide/plugins/CreateAllServicesAndExtensionsAction.kt index 169e8ee004da..9fb345b22747 100644 --- a/platform/platform-impl/src/com/intellij/ide/plugins/CreateAllServicesAndExtensionsAction.kt +++ b/platform/platform-impl/src/com/intellij/ide/plugins/CreateAllServicesAndExtensionsAction.kt @@ -1,4 +1,5 @@ // 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. +@file:Suppress("TestOnlyProblems") package com.intellij.ide.plugins import com.intellij.diagnostic.PluginException @@ -24,6 +25,7 @@ import com.intellij.openapi.project.Project import com.intellij.openapi.util.registry.Registry import com.intellij.psi.stubs.StubElementTypeHolderEP import com.intellij.serviceContainer.ComponentManagerImpl +import com.intellij.util.getErrorsAsString import io.github.classgraph.AnnotationEnumValue import io.github.classgraph.ClassGraph import io.github.classgraph.ClassInfo @@ -32,11 +34,9 @@ import kotlin.properties.Delegates.notNull @Suppress("HardCodedStringLiteral") private class CreateAllServicesAndExtensionsAction : AnAction("Create All Services And Extensions"), DumbAware { - override fun actionPerformed(e: AnActionEvent) { val errors = mutableListOf() runModalTask("Creating All Services And Extensions", cancellable = true) { indicator -> - val logger = logger() val taskExecutor: (task: () -> Unit) -> Unit = { task -> try { task() @@ -45,7 +45,6 @@ private class CreateAllServicesAndExtensionsAction : AnAction("Create All Servic throw e } catch (e: Throwable) { - logger.error(e) errors.add(e) } } @@ -59,7 +58,11 @@ private class CreateAllServicesAndExtensionsAction : AnAction("Create All Servic } indicator.text2 = "Checking light services..." - checkLightServices(taskExecutor) + checkLightServices(taskExecutor, errors) + } + + if (errors.isNotEmpty()) { + logger().error(getErrorsAsString(errors)) } // some errors are not thrown but logged val message = (if (errors.isEmpty()) "No errors" else "${errors.size} errors were logged") + ". Check also that no logged errors." @@ -98,7 +101,6 @@ const val ACTION_ID = "CreateAllServicesAndExtensions" private val badServices = java.util.Set.of( "com.intellij.usageView.impl.UsageViewContentManagerImpl", "com.jetbrains.python.scientific.figures.PyPlotToolWindow", - "org.jetbrains.plugins.grails.runner.GrailsConsole", "com.intellij.analysis.pwa.analyser.PwaServiceImpl", "com.intellij.analysis.pwa.view.toolwindow.PwaProblemsViewImpl", ) @@ -110,7 +112,8 @@ private fun checkContainer(container: ComponentManagerImpl, indicator: ProgressI indicator.text2 = "Checking ${container.activityNamePrefix()}extensions..." container.extensionArea.processExtensionPoints { extensionPoint -> // requires read action - if (extensionPoint.name == "com.intellij.favoritesListProvider" || extensionPoint.name == "com.intellij.favoritesListProvider") { + if (extensionPoint.name == "com.intellij.favoritesListProvider" || + extensionPoint.name == "org.jetbrains.kotlin.defaultErrorMessages") { return@processExtensionPoints } @@ -143,10 +146,10 @@ private fun checkExtensionPoint(extensionPoint: ExtensionPointImpl<*>, taskExecu } } -private fun checkLightServices(taskExecutor: (task: () -> Unit) -> Unit) { - for (plugin in PluginManagerCore.getLoadedPlugins(null)) { +private fun checkLightServices(taskExecutor: (task: () -> Unit) -> Unit, errors: MutableList) { + for (plugin in PluginManagerCore.getPluginSet().loadedPlugins) { // we don't check classloader for sub descriptors because url set is the same - if (plugin.classLoader !is PluginClassLoader || plugin.pluginDependencies.isEmpty()) { + if (plugin.classLoader !is PluginClassLoader) { continue } @@ -158,8 +161,19 @@ private fun checkLightServices(taskExecutor: (task: () -> Unit) -> Unit) { .use { scanResult -> val lightServices = scanResult.getClassesWithAnnotation(Service::class.java.name) for (lightService in lightServices) { + if (lightService.name == "org.jetbrains.plugins.grails.runner.GrailsConsole") { + // wants EDT in constructor + continue + } + // not clear - from what classloader light service will be loaded in reality - val lightServiceClass = loadLightServiceClass(lightService, plugin) + val lightServiceClass = try { + loadLightServiceClass(lightService, plugin) + } + catch (e: Throwable) { + errors.add(e) + continue + } val isProjectLevel: Boolean val isAppLevel: Boolean @@ -176,7 +190,12 @@ private fun checkLightServices(taskExecutor: (task: () -> Unit) -> Unit) { if (isAppLevel) { taskExecutor { - ApplicationManager.getApplication().getService(lightServiceClass) + try { + ApplicationManager.getApplication().getService(lightServiceClass) + } + catch (e: Throwable) { + errors.add(RuntimeException("Cannot create $lightServiceClass", e)) + } } } if (isProjectLevel) { @@ -190,21 +209,10 @@ private fun checkLightServices(taskExecutor: (task: () -> Unit) -> Unit) { } private fun loadLightServiceClass(lightService: ClassInfo, mainDescriptor: IdeaPluginDescriptorImpl): Class<*> { - // - for (pluginDependency in mainDescriptor.pluginDependencies) { - val subPluginClassLoader = pluginDependency.subDescriptor?.classLoader as? PluginClassLoader ?: continue - val packagePrefix = subPluginClassLoader.packagePrefix ?: continue - if (lightService.name.startsWith(packagePrefix)) { - return subPluginClassLoader.loadClass(lightService.name, true) - } - } - - for (pluginDependency in mainDescriptor.pluginDependencies) { - val subPluginClassLoader = pluginDependency.subDescriptor?.classLoader as? PluginClassLoader ?: continue - val clazz = subPluginClassLoader.loadClass(lightService.name, true) - if (clazz != null && clazz.classLoader === subPluginClassLoader) { - // light class is resolved from this sub plugin classloader - check successful - return clazz + for (item in mainDescriptor.content.modules) { + val classLoader = item.requireDescriptor().classLoader as? PluginClassLoader ?: continue + if (lightService.name.startsWith(classLoader.packagePrefix!!)) { + return classLoader.loadClass(lightService.name, true) } } diff --git a/platform/platform-impl/src/com/intellij/ide/plugins/DynamicPlugins.kt b/platform/platform-impl/src/com/intellij/ide/plugins/DynamicPlugins.kt index 84dbd7a851c4..d653c37ce0e6 100644 --- a/platform/platform-impl/src/com/intellij/ide/plugins/DynamicPlugins.kt +++ b/platform/platform-impl/src/com/intellij/ide/plugins/DynamicPlugins.kt @@ -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.ide.plugins import com.fasterxml.jackson.databind.type.TypeFactory @@ -23,7 +23,6 @@ import com.intellij.ide.ui.TopHitCache import com.intellij.ide.ui.UIThemeProvider import com.intellij.ide.util.TipDialog import com.intellij.idea.IdeaLogger -import com.intellij.idea.ZipFilePoolImpl import com.intellij.internal.statistic.eventLog.FeatureUsageData import com.intellij.internal.statistic.service.fus.collectors.FUCounterUsageLogger import com.intellij.internal.statistic.utils.getPluginInfoByDescriptor @@ -68,8 +67,6 @@ import com.intellij.openapi.vfs.newvfs.FileAttribute import com.intellij.openapi.wm.WindowManager import com.intellij.openapi.wm.impl.IdeFrameImpl import com.intellij.openapi.wm.impl.ProjectFrameHelper -import com.intellij.platform.util.plugins.DataLoader -import com.intellij.platform.util.plugins.LocalFsDataLoader import com.intellij.psi.util.CachedValuesManager import com.intellij.serviceContainer.ComponentManagerImpl import com.intellij.ui.IconDeferrer @@ -80,7 +77,6 @@ import com.intellij.util.ReflectionUtil import com.intellij.util.SystemProperties import com.intellij.util.containers.ContainerUtil import com.intellij.util.containers.WeakList -import com.intellij.util.lang.ZipFilePool import com.intellij.util.messages.impl.MessageBusEx import com.intellij.util.ref.GCWatcher import net.sf.cglib.core.ClassNameReader @@ -89,19 +85,17 @@ import java.awt.KeyboardFocusManager import java.awt.Window import java.nio.channels.FileChannel import java.nio.file.FileVisitResult -import java.nio.file.Path import java.nio.file.Paths import java.nio.file.StandardOpenOption import java.text.SimpleDateFormat import java.util.* -import java.util.function.Function import java.util.function.Predicate import javax.swing.JComponent import javax.swing.ToolTipManager import kotlin.collections.component1 import kotlin.collections.component2 -private val LOG = logger() + private val LOG = logger() private val classloadersFromUnloadedPlugins = ContainerUtil.createWeakValueMap() object DynamicPlugins { @@ -124,9 +118,8 @@ object DynamicPlugins { */ @JvmStatic fun loadPlugins(descriptors: Collection): Boolean { - val loader = lazy(LazyThreadSafetyMode.NONE) { OptionalDependencyDescriptorLoader() } return updateDescriptorsWithoutRestart(descriptors, load = true) { - loadPlugin(it, checkImplementationDetailDependencies = true, loader = loader) + loadPlugin(it, checkImplementationDetailDependencies = true) } } @@ -183,12 +176,9 @@ object DynamicPlugins { return true } - private fun pluginsSortedByDependency( - descriptors: List, - load: Boolean, - ): List { + private fun pluginsSortedByDependency(descriptors: List, load: Boolean): List { val plugins = PluginManager.getInstance().getPluginsSortedByDependency(descriptors) - return if (load) plugins.asList() else plugins.reversed() + return if (load) plugins else plugins.reversed() } /** @@ -235,19 +225,20 @@ object DynamicPlugins { return e.cause?.localizedMessage ?: "checkUnloadPlugin listener blocked plugin unload" } - val pluginStateChecker = PluginStateChecker() + val pluginSet = PluginManagerCore.getPluginSet() + if (!Registry.`is`("ide.plugins.allow.unload.from.sources")) { val loadedPluginDescriptor = if (descriptor === baseDescriptor) { - PluginManagerCore.getPlugin(descriptor.pluginId) as IdeaPluginDescriptorImpl? + pluginSet.findEnabledPlugin(descriptor.pluginId) } else { null } if (loadedPluginDescriptor != null && !descriptor.isUseIdeaClassLoader && - pluginStateChecker.isPluginOrModuleLoaded(loadedPluginDescriptor.pluginId)) { - val pluginClassLoader = loadedPluginDescriptor.pluginClassLoader - if (pluginClassLoader !is PluginClassLoader && !app.isUnitTestMode) { + pluginSet.isPluginEnabled(loadedPluginDescriptor.pluginId)) { + val pluginClassLoader = loadedPluginDescriptor.classLoader + if (pluginClassLoader != null && pluginClassLoader !is PluginClassLoader && !app.isUnitTestMode) { return "Plugin ${descriptor.pluginId} is not unload-safe because of use of ${pluginClassLoader.javaClass.name} as the default class loader. " + "For example, the IDE is started from the sources with the plugin." } @@ -258,14 +249,16 @@ object DynamicPlugins { val epNameToExtensions = descriptor.epNameToExtensions if (epNameToExtensions != null) { - doCheckExtensionsCanUnloadWithoutRestart(extensions = epNameToExtensions, - descriptor = descriptor, - baseDescriptor = baseDescriptor, - isSubDescriptor = isSubDescriptor, - app = app, - optionalDependencyPluginId = optionalDependencyPluginId, - context = context, - pluginStateChecker = pluginStateChecker)?.let { return it } + doCheckExtensionsCanUnloadWithoutRestart( + extensions = epNameToExtensions, + descriptor = descriptor, + baseDescriptor = baseDescriptor, + isSubDescriptor = isSubDescriptor, + app = app, + optionalDependencyPluginId = optionalDependencyPluginId, + context = context, + pluginSet = pluginSet, + )?.let { return it } } val pluginId = descriptor.pluginId @@ -273,7 +266,7 @@ object DynamicPlugins { ActionManagerImpl.checkUnloadActions(pluginId, descriptor)?.let { return it } for (dependency in descriptor.pluginDependencies) { - if (pluginStateChecker.isPluginOrModuleLoaded(dependency.pluginId)) { + if (pluginSet.isPluginEnabled(dependency.pluginId)) { checkCanUnloadWithoutRestart(dependency.subDescriptor ?: continue, baseDescriptor ?: descriptor, null, context)?.let { return "$it in optional dependency on ${dependency.pluginId}" } @@ -286,15 +279,10 @@ object DynamicPlugins { } var dependencyMessage: String? = null - processLoadedOptionalDependenciesOnPlugin(descriptor.pluginId) { mainDescriptor, subDescriptor -> - if (subDescriptor == null) { - // XPathView Here subDescriptor will be null. - return@processLoadedOptionalDependenciesOnPlugin true - } - + processOptionalDependenciesOnPlugin(descriptor, pluginSet, isLoaded = true) { mainDescriptor, subDescriptor -> if (!ClassLoaderConfigurationData.isClassloaderPerDescriptorEnabled(mainDescriptor.pluginId, subDescriptor.packagePrefix)) { dependencyMessage = "Plugin ${subDescriptor.pluginId} that optionally depends on ${descriptor.pluginId} does not have a separate classloader for the dependency" - return@processLoadedOptionalDependenciesOnPlugin false + return@processOptionalDependenciesOnPlugin false } dependencyMessage = checkCanUnloadWithoutRestart(subDescriptor, mainDescriptor, subDescriptor.pluginId, context) @@ -442,14 +430,10 @@ object DynamicPlugins { } @JvmStatic - fun unloadPlugin(fullyLoadedPluginDescriptor: IdeaPluginDescriptorImpl, options: UnloadPluginOptions = UnloadPluginOptions()): Boolean { + fun unloadPlugin(pluginDescriptor: IdeaPluginDescriptorImpl, options: UnloadPluginOptions = UnloadPluginOptions()): Boolean { val app = ApplicationManager.getApplication() as ApplicationImpl - val pluginId = fullyLoadedPluginDescriptor.pluginId - - // The descriptor passed to `unloadPlugin` is the full descriptor loaded from disk, it does not have a classloader. - // We need to find the real plugin loaded into the current instance and unload its classloader. - val pluginDescriptor = PluginManagerCore.getPlugin(pluginId) as? IdeaPluginDescriptorImpl - ?: return false + val pluginId = pluginDescriptor.pluginId + val pluginSet = PluginManagerCore.getPluginSet() if (options.checkImplementationDetailDependencies) { processImplementationDetailDependenciesOnPlugin(pluginDescriptor) { dependentDescriptor -> @@ -483,9 +467,9 @@ object DynamicPlugins { // mark plugin classloaders as being unloaded to ensure that new extension instances will be not created during unload setClassLoaderState(pluginDescriptor, PluginClassLoader.UNLOAD_IN_PROGRESS) - unloadLoadedOptionalDependenciesOnPlugin(pluginDescriptor, classLoaders) + unloadLoadedOptionalDependenciesOnPlugin(pluginDescriptor, pluginSet = pluginSet, classLoaders = classLoaders) - unloadDependencyDescriptors(pluginDescriptor.pluginDependencies, PluginStateChecker(), classLoaders) + unloadDependencyDescriptors(pluginDescriptor, pluginSet, classLoaders) unloadPluginDescriptorNotRecursively(pluginDescriptor, true) clearPluginClassLoaderParentListCache() @@ -522,10 +506,10 @@ object DynamicPlugins { if (options.disable) { // update list of disabled plugins - PluginManager.getInstance().setPlugins(PluginManagerCore.getPlugins().asList()) + PluginManager.getInstance().setPlugins(PluginManagerCore.getPluginSet().allPlugins) } else { - PluginManager.getInstance().setPlugins(PluginManagerCore.getPlugins().asSequence().minus(pluginDescriptor).toList()) + PluginManager.getInstance().setPlugins(PluginManagerCore.getPluginSet().allPlugins.minus(pluginDescriptor)) } } finally { @@ -627,46 +611,42 @@ object DynamicPlugins { } } - private fun unloadLoadedOptionalDependenciesOnPlugin(dependencyPluginDescriptor: IdeaPluginDescriptorImpl, classLoaders: WeakList) { - val dependencyClassloader = dependencyPluginDescriptor.classLoader - for (descriptor in PluginManagerCore.getLoadedPlugins(null)) { - val ok = processLoadedOptionalDependenciesOnPlugin(dependencyPluginDescriptor.pluginId, descriptor) { mainDescriptor, subDescriptor -> - val classLoader = (subDescriptor ?: mainDescriptor).pluginClassLoader - if (subDescriptor != null) { - unloadPluginDescriptorNotRecursively(subDescriptor, false) - } + private fun unloadLoadedOptionalDependenciesOnPlugin(dependencyPlugin: IdeaPluginDescriptorImpl, + pluginSet: PluginSet, + classLoaders: WeakList) { + val dependencyClassloader = dependencyPlugin.classLoader + processOptionalDependenciesOnPlugin(dependencyPlugin, pluginSet, isLoaded = true) { mainDescriptor, subDescriptor -> + val classLoader = subDescriptor.classLoader + unloadPluginDescriptorNotRecursively(subDescriptor, false) - // this additional code is required because in unit tests PluginClassLoader is not used - if (subDescriptor != null && mainDescriptor !== subDescriptor) { - subDescriptor.classLoader = null - } + // this additional code is required because in unit tests PluginClassLoader is not used + if (mainDescriptor !== subDescriptor) { + subDescriptor.classLoader = null + } - if (dependencyClassloader is PluginClassLoader && classLoader is PluginClassLoader) { - LOG.info("Detach classloader $dependencyClassloader from $classLoader") - if (subDescriptor != null && mainDescriptor !== subDescriptor && classLoader.pluginDescriptor === subDescriptor) { - classLoaders.add(classLoader) - classLoader.state = PluginClassLoader.UNLOAD_IN_PROGRESS - } - else if (!classLoader.detachParent(dependencyClassloader)) { - LOG.warn("Classloader $dependencyClassloader doesn't have $classLoader as parent") - } + if (dependencyClassloader is PluginClassLoader && classLoader is PluginClassLoader) { + LOG.info("Detach classloader $dependencyClassloader from $classLoader") + if (mainDescriptor !== subDescriptor && classLoader.pluginDescriptor === subDescriptor) { + classLoaders.add(classLoader) + classLoader.state = PluginClassLoader.UNLOAD_IN_PROGRESS + } + else if (!classLoader.detachParent(dependencyClassloader)) { + LOG.warn("Classloader $dependencyClassloader doesn't have $classLoader as parent") } - true - } - if (!ok) { - break } + true } } - private fun unloadDependencyDescriptors(pluginDependencies: List, - pluginStateChecker: PluginStateChecker, + private fun unloadDependencyDescriptors(plugin: IdeaPluginDescriptorImpl, + pluginSet: PluginSet, classLoaders: WeakList) { - for (dependency in pluginDependencies) { + for (dependency in plugin.pluginDependencies) { val subDescriptor = dependency.subDescriptor ?: continue val classLoader = subDescriptor.classLoader - if (!pluginStateChecker.isPluginOrModuleLoaded(dependency.pluginId)) { - LOG.assertTrue(classLoader == null, "Expected not to have any sub descriptor classloader when dependency ${dependency.pluginId} is not loaded") + if (!pluginSet.isPluginEnabled(dependency.pluginId)) { + LOG.assertTrue(classLoader == null, + "Expected not to have any sub descriptor classloader when dependency ${dependency.pluginId} is not loaded") continue } @@ -674,7 +654,19 @@ object DynamicPlugins { classLoaders.add(classLoader) } - unloadDependencyDescriptors(subDescriptor.pluginDependencies, pluginStateChecker, classLoaders) + unloadDependencyDescriptors(subDescriptor, pluginSet, classLoaders) + unloadPluginDescriptorNotRecursively(subDescriptor, true) + subDescriptor.classLoader = null + } + + for (module in plugin.content.modules) { + val subDescriptor = module.requireDescriptor() + + val classLoader = subDescriptor.classLoader ?: continue + if (classLoader is PluginClassLoader && classLoader.pluginDescriptor === subDescriptor) { + classLoaders.add(classLoader) + } + unloadPluginDescriptorNotRecursively(subDescriptor, true) subDescriptor.classLoader = null } @@ -737,12 +729,12 @@ object DynamicPlugins { val pluginId = pluginDescriptor.pluginId app.unloadServices(pluginDescriptor.appContainerDescriptor.services, pluginId) val appMessageBus = app.messageBus as MessageBusEx - pluginDescriptor.appContainerDescriptor.listeners?.let { appMessageBus.unsubscribeLazyListeners(pluginId, it) } + pluginDescriptor.appContainerDescriptor.listeners?.let { appMessageBus.unsubscribeLazyListeners(pluginDescriptor, it) } for (project in openedProjects) { (project as ComponentManagerImpl).unloadServices(pluginDescriptor.projectContainerDescriptor.services, pluginId) pluginDescriptor.projectContainerDescriptor.listeners?.let { - ((project as ComponentManagerImpl).messageBus as MessageBusEx).unsubscribeLazyListeners(pluginId, it) + ((project as ComponentManagerImpl).messageBus as MessageBusEx).unsubscribeLazyListeners(pluginDescriptor, it) } val moduleServices = pluginDescriptor.moduleContainerDescriptor.services @@ -807,16 +799,13 @@ object DynamicPlugins { } } - @JvmStatic - @JvmOverloads - fun loadPlugin(pluginDescriptor: IdeaPluginDescriptorImpl, checkImplementationDetailDependencies: Boolean = true): Boolean { - return loadPlugin(pluginDescriptor, checkImplementationDetailDependencies, - lazy(LazyThreadSafetyMode.NONE) { OptionalDependencyDescriptorLoader() }) + fun loadPlugin(pluginDescriptor: IdeaPluginDescriptorImpl): Boolean { + return loadPlugin(pluginDescriptor, checkImplementationDetailDependencies = true) } - private fun loadPlugin(pluginDescriptor: IdeaPluginDescriptorImpl, - checkImplementationDetailDependencies: Boolean, - loader: Lazy): Boolean { + fun loadPlugin(pluginDescriptor: IdeaPluginDescriptorImpl, + checkImplementationDetailDependencies: Boolean = true, + classLoaderForTest: ClassLoader? = null): Boolean { if (classloadersFromUnloadedPlugins[pluginDescriptor.pluginId] != null) { LOG.info("Requiring restart for loading plugin ${pluginDescriptor.pluginId}" + " because previous version of the plugin wasn't fully unloaded") @@ -825,23 +814,17 @@ object DynamicPlugins { val loadStartTime = System.currentTimeMillis() val app = ApplicationManager.getApplication() as ApplicationImpl - val classLoaderConfigurator: ClassLoaderConfigurator? - if (app.isUnitTestMode) { - classLoaderConfigurator = null - } - else { - classLoaderConfigurator = PluginManagerCore.createClassLoaderConfiguratorForDynamicPlugin(pluginDescriptor) - classLoaderConfigurator.configure(pluginDescriptor) - } + val pluginSet = PluginManagerCore.getPluginSet().concat(pluginDescriptor) + val classLoaderConfigurator = ClassLoaderConfigurator(pluginSet) + classLoaderConfigurator.configure(pluginDescriptor, classLoaderForTest) app.messageBus.syncPublisher(DynamicPluginListener.TOPIC).beforePluginLoaded(pluginDescriptor) app.runWriteAction { try { addToLoadedPlugins(pluginDescriptor) - val pluginStateChecker = PluginStateChecker(classLoaderConfigurator?.idMap) val listenerCallbacks = mutableListOf() - loadPluginDescriptor(pluginDescriptor, app, pluginStateChecker, listenerCallbacks) - loadOptionalDependenciesOnPlugin(pluginDescriptor, loader, pluginStateChecker, classLoaderConfigurator, listenerCallbacks) + loadPluginDescriptor(pluginDescriptor, app, listenerCallbacks) + loadOptionalDependenciesOnPlugin(pluginDescriptor, classLoaderConfigurator, pluginSet, listenerCallbacks) clearPluginClassLoaderParentListCache() for (openProject in ProjectUtil.getOpenProjects()) { @@ -865,7 +848,7 @@ object DynamicPlugins { processImplementationDetailDependenciesOnPlugin(pluginDescriptor) { dependentDescriptor -> val dependencies = dependentDescriptor.pluginDependencies if (dependencies.all { it.isOptional || PluginManagerCore.getPlugin(it.pluginId) != null }) { - if (!loadPlugin(dependentDescriptor, checkImplementationDetailDependencies = false, loader = loader)) { + if (!loadPlugin(dependentDescriptor, checkImplementationDetailDependencies = false)) { implementationDetailsLoadedWithoutRestart = false } } @@ -878,7 +861,7 @@ object DynamicPlugins { private fun addToLoadedPlugins(pluginDescriptor: IdeaPluginDescriptorImpl) { var foundExistingPlugin = false - val newPlugins = PluginManagerCore.getPlugins().map { + val newPlugins = PluginManagerCore.getPluginSet().allPlugins.map { if (it.pluginId == pluginDescriptor.pluginId) { foundExistingPlugin = true pluginDescriptor @@ -892,7 +875,7 @@ object DynamicPlugins { PluginManager.getInstance().setPlugins(newPlugins) } else { - PluginManager.getInstance().setPlugins(PluginManagerCore.getPlugins().asSequence().plus(pluginDescriptor).toList()) + PluginManager.getInstance().setPlugins(PluginManagerCore.getPluginSet().allPlugins.plus(pluginDescriptor)) } } @@ -1022,74 +1005,32 @@ private fun processImplementationDetailDependenciesOnPlugin(pluginDescriptor: Id } } -private class OptionalDependencyDescriptorLoader { - private val listContext = DescriptorListLoadingContext(disabledPlugins = DisabledPluginsState.disabledPlugins()) - - fun load(mainDescriptor: IdeaPluginDescriptorImpl, dependencyConfigFile: String): IdeaPluginDescriptorImpl? { - val pathResolver = createPathResolverForPlugin(mainDescriptor, true) - val zipFilePool = ZipFilePoolImpl() - ZipFilePool.POOL = zipFilePool - try { - val dataLoader: DataLoader - if (mainDescriptor.pluginPath.toString().endsWith(".jar")) { - val resolver = zipFilePool.load(mainDescriptor.pluginPath) - dataLoader = ImmutableZipFileDataLoader(resolver, mainDescriptor.pluginPath, zipFilePool) - } - else { - dataLoader = LocalFsDataLoader(mainDescriptor.pluginPath) - } - - val raw = pathResolver.resolvePath(readContext = listContext, - dataLoader = dataLoader, - relativePath = dependencyConfigFile, - readInto = null)!! - // readExternal requires not-null id - val subDescriptor = mainDescriptor.createSub(raw, dependencyConfigFile) - subDescriptor.readExternal(raw = raw, pathResolver = pathResolver, context = listContext, isSub = true, dataLoader = dataLoader) - return subDescriptor - } - catch (e: Exception) { - LOG.info("Can't resolve optional dependency on plugin being loaded/unloaded: config file $dependencyConfigFile", e) - return null - } - finally { - ZipFilePool.POOL = null - // help GC - zipFilePool.clear() - } - } -} - /** * Load all sub plugins that depend on specified [dependencyPlugin]. */ private fun loadOptionalDependenciesOnPlugin(dependencyPlugin: IdeaPluginDescriptorImpl, - loader: Lazy, - pluginStateChecker: PluginStateChecker, - classLoaderConfigurator: ClassLoaderConfigurator?, + classLoaderConfigurator: ClassLoaderConfigurator, + pluginSet: PluginSet, listenerCallbacks: MutableList) { - val mainToSub = LinkedHashMap>() - // 1. read and collect optional descriptors - for (descriptor in PluginManagerCore.getLoadedPlugins(null)) { - for (dependency in descriptor.pluginDependencies) { - readAndProcessOptionalDependencyDescriptor(dependencyPlugin.pluginId, descriptor, dependency, loader) { subDescriptor: IdeaPluginDescriptorImpl -> - mainToSub.computeIfAbsent(descriptor) { mutableListOf() }.add(subDescriptor) - } - } + // 1. collect optional descriptors + val mainToModule = LinkedHashMap>() + + processOptionalDependenciesOnPlugin(dependencyPlugin, pluginSet, isLoaded = false) { mainDescriptor, subDescriptor -> + mainToModule.computeIfAbsent(mainDescriptor) { mutableListOf() }.add(subDescriptor) } - if (mainToSub.isEmpty()) { + if (mainToModule.isEmpty()) { return } // 2. setup classloaders - classLoaderConfigurator?.configureDependenciesIfNeeded(mainToSub, dependencyPlugin) + classLoaderConfigurator.configureDependenciesIfNeeded(mainToModule, dependencyPlugin) val app = ApplicationManager.getApplication() as ComponentManagerImpl // 3. load into service container - for (entry in mainToSub.entries) { + for (entry in mainToModule.entries) { for (subDescriptor in entry.value) { - loadPluginDescriptor(subDescriptor, app, pluginStateChecker, listenerCallbacks) + loadPluginDescriptor(subDescriptor, app, listenerCallbacks) } } } @@ -1109,101 +1050,22 @@ private fun clearPluginClassLoaderParentListCache(descriptor: IdeaPluginDescript } } -private fun readAndProcessOptionalDependencyDescriptor(dependencyPluginId: PluginId, - mainDescriptor: IdeaPluginDescriptorImpl, - dependency: PluginDependency, - loader: Lazy, - processor: (pluginDescriptor: IdeaPluginDescriptorImpl) -> Unit) { - if (!dependency.isOptional) { - return - } - - val newPluginDescriptor = dependency.configFile?.let { loader.value.load(mainDescriptor, it) } ?: return - if (dependency.pluginId == dependencyPluginId) { - dependency.subDescriptor = newPluginDescriptor - dependency.isDisabledOrBroken = false - processor(newPluginDescriptor) - } - - for (subDependency in newPluginDescriptor.pluginDependencies) { - readAndProcessOptionalDependencyDescriptor(dependencyPluginId, mainDescriptor, subDependency, loader, processor) - } -} - -private fun updateDependenciesStatus(pluginDescriptor: IdeaPluginDescriptorImpl, pluginStateChecker: PluginStateChecker) { - for (dependency in pluginDescriptor.pluginDependencies) { - val subDescriptor = dependency.subDescriptor ?: continue - if (pluginStateChecker.isPluginOrModuleLoaded(dependency.pluginId)) { - dependency.isDisabledOrBroken = false - updateDependenciesStatus(subDescriptor, pluginStateChecker) - } - else { - dependency.isDisabledOrBroken = true - } - } -} - private fun loadPluginDescriptor(pluginDescriptor: IdeaPluginDescriptorImpl, - app: ComponentManagerImpl, - pluginStateChecker: PluginStateChecker, - listenerCallbacks: MutableList) { - updateDependenciesStatus(pluginDescriptor, pluginStateChecker) + app: ComponentManagerImpl, + listenerCallbacks: MutableList) { + val list = listOf(pluginDescriptor) + app.registerComponents(plugins = list, + app = ApplicationManager.getApplication(), + precomputedExtensionModel = null, + listenerCallbacks = listenerCallbacks) + for (openProject in ProjectUtil.getOpenProjects()) { + (openProject as ComponentManagerImpl).registerComponents(list, ApplicationManager.getApplication(), null, listenerCallbacks) + for (module in ModuleManager.getInstance(openProject).modules) { + (module as ComponentManagerImpl).registerComponents(list, ApplicationManager.getApplication(), null, listenerCallbacks) + } + } - val list = listOf(pluginDescriptor) - app.registerComponents(plugins = list, - app = ApplicationManager.getApplication(), - precomputedExtensionModel = null, - listenerCallbacks = listenerCallbacks) - for (openProject in ProjectUtil.getOpenProjects()) { - (openProject as ComponentManagerImpl).registerComponents(list, ApplicationManager.getApplication(), null, listenerCallbacks) - for (module in ModuleManager.getInstance(openProject).modules) { - (module as ComponentManagerImpl).registerComponents(list, ApplicationManager.getApplication(), null, listenerCallbacks) - } - } - - val actionManager = ActionManager.getInstance() as ActionManagerImpl - actionManager.registerActions(list, false) -} - -private class PluginStateChecker(private val loadedIdMap: Map? = null) { - companion object { - @JvmStatic - private val NULL_PLUGIN_DESCRIPTOR: IdeaPluginDescriptorImpl - - init { - val raw = RawPluginDescriptor() - raw.id = "" - @Suppress("TestOnlyProblems") - NULL_PLUGIN_DESCRIPTOR = IdeaPluginDescriptorImpl(raw, Path.of(""), false, null) - } - } - - private val loadedPlugins = PluginManagerCore.getLoadedPlugins(null) - - private val moduleToPluginCache = IdentityHashMap() - - private fun findLoadedPluginByModuleDependency(pluginId: PluginId): IdeaPluginDescriptor? { - return moduleToPluginCache.computeIfAbsent(pluginId, Function { - for (descriptor in loadedPlugins) { - if (descriptor.modules.contains(it)) { - return@Function descriptor - } - } - NULL_PLUGIN_DESCRIPTOR - }).takeIf { it !== NULL_PLUGIN_DESCRIPTOR } - } - - fun isPluginOrModuleLoaded(pluginId: PluginId): Boolean { - return when { - PluginManagerCore.isModuleDependency(pluginId) -> findLoadedPluginByModuleDependency(pluginId) != null - loadedIdMap != null -> loadedIdMap.containsKey(pluginId) - else -> loadedPlugins.any { it.pluginId == pluginId } - } - } - - fun findDescriptor(pluginId: PluginId): IdeaPluginDescriptorImpl? { - return loadedPlugins.find { it.pluginId == pluginId } - } + (ActionManager.getInstance() as ActionManagerImpl).registerActions(list) } private fun analyzeSnapshot(hprofPath: String, pluginId: PluginId): String { @@ -1230,28 +1092,59 @@ private fun createDisposeTreePredicate(pluginDescriptor: IdeaPluginDescriptorImp } } -private fun processLoadedOptionalDependenciesOnPlugin(dependencyPluginId: PluginId, - processor: (mainDescriptor: IdeaPluginDescriptorImpl, subDescriptor: IdeaPluginDescriptorImpl?) -> Boolean) { - for (descriptor in PluginManagerCore.getLoadedPlugins(null)) { - if (!processLoadedOptionalDependenciesOnPlugin(dependencyPluginId, descriptor, processor)) { - break +private fun processOptionalDependenciesOnPlugin(dependencyPlugin: IdeaPluginDescriptorImpl, + pluginSet: PluginSet, + isLoaded: Boolean, + processor: (pluginDescriptor: IdeaPluginDescriptorImpl, + moduleDescriptor: IdeaPluginDescriptorImpl) -> Boolean) { + val wantedIds = HashSet(1 + dependencyPlugin.content.modules.size) + wantedIds.add(dependencyPlugin.id.idString) + for (module in dependencyPlugin.content.modules) { + wantedIds.add(module.name) + } + + for (plugin in pluginSet.loadedPlugins) { + if (!processOptionalDependenciesInOldFormatOnPlugin(dependencyPlugin.id, plugin, isLoaded, processor)) { + return + } + + for (moduleItem in plugin.content.modules) { + val module = moduleItem.requireDescriptor() + + val isModuleLoaded = module.classLoader != null + if (isModuleLoaded != isLoaded) { + continue + } + + for (item in module.dependencies.modules) { + if (wantedIds.contains(item.name) && !processor(plugin, module)) { + return + } + } } } } -private fun processLoadedOptionalDependenciesOnPlugin(dependencyPluginId: PluginId, - mainDescriptor: IdeaPluginDescriptorImpl, - processor: (mainDescriptor: IdeaPluginDescriptorImpl, subDescriptor: IdeaPluginDescriptorImpl?) -> Boolean): Boolean { +private fun processOptionalDependenciesInOldFormatOnPlugin(dependencyPluginId: PluginId, + mainDescriptor: IdeaPluginDescriptorImpl, + isLoaded: Boolean, + processor: (mainDescriptor: IdeaPluginDescriptorImpl, subDescriptor: IdeaPluginDescriptorImpl) -> Boolean): Boolean { for (dependency in mainDescriptor.pluginDependencies) { - if (!dependency.isOptional || dependency.isDisabledOrBroken) { + if (!dependency.isOptional) { continue } - if (dependency.pluginId == dependencyPluginId && !processor(mainDescriptor, dependency.subDescriptor)) { + val subDescriptor = dependency.subDescriptor ?: continue + val isModuleLoaded = subDescriptor.classLoader != null + if (isModuleLoaded != isLoaded) { + continue + } + + if (dependency.pluginId == dependencyPluginId && !processor(mainDescriptor, subDescriptor)) { return false } - if (!processLoadedOptionalDependenciesOnPlugin(dependencyPluginId, dependency.subDescriptor ?: continue, processor)) { + if (!processOptionalDependenciesInOldFormatOnPlugin(dependencyPluginId, subDescriptor, isLoaded, processor)) { return false } } @@ -1265,7 +1158,7 @@ private fun doCheckExtensionsCanUnloadWithoutRestart(extensions: Map, - pluginStateChecker: PluginStateChecker): String? { + pluginSet: PluginSet): String? { val firstProject = ProjectUtil.getOpenProjects().firstOrNull() val anyProject = firstProject ?: ProjectManager.getInstance().defaultProject val anyModule = firstProject?.let { ModuleManager.getInstance(it).modules.firstOrNull() } @@ -1273,11 +1166,11 @@ private fun doCheckExtensionsCanUnloadWithoutRestart(extensions: Map = Collections.newSetFromMap(IdentityHashMap()) epLoop@ for (epName in extensions.keys) { seenPlugins.clear() - val result = findPluginExtensionPointRecursive(pluginDescriptor = baseDescriptor ?: descriptor, - epName = epName, - pluginStateChecker = pluginStateChecker, - context = context, - seenPlugins = seenPlugins) + val result = findLoadedPluginExtensionPointRecursive(pluginDescriptor = baseDescriptor ?: descriptor, + epName = epName, + pluginSet = pluginSet, + context = context, + seenPlugins = seenPlugins) if (result != null) { val (pluginExtensionPoint, foundInDependencies) = result // descriptor.pluginId is null when we check the optional dependencies of the plugin which is being loaded @@ -1304,9 +1197,9 @@ private fun doCheckExtensionsCanUnloadWithoutRestart(extensions: Map, - seenPlugins: MutableSet): Pair? { - if (pluginDescriptor in seenPlugins) { +private fun findLoadedPluginExtensionPointRecursive(pluginDescriptor: IdeaPluginDescriptorImpl, + epName: String, + pluginSet: PluginSet, + context: List, + seenPlugins: MutableSet): Pair? { + if (!seenPlugins.add(pluginDescriptor)) { return null } - seenPlugins.add(pluginDescriptor) findPluginExtensionPoint(pluginDescriptor, epName)?.let { return it to false } for (dependency in pluginDescriptor.pluginDependencies) { - if (pluginStateChecker.isPluginOrModuleLoaded(dependency.pluginId) || context.any { it.id == dependency.pluginId }) { + if (pluginSet.isPluginEnabled(dependency.pluginId) || context.any { it.id == dependency.pluginId }) { dependency.subDescriptor?.let { subDescriptor -> - findPluginExtensionPointRecursive(subDescriptor, epName, pluginStateChecker, context, seenPlugins)?.let { return it } + findLoadedPluginExtensionPointRecursive(subDescriptor, epName, pluginSet, context, seenPlugins)?.let { return it } } - pluginStateChecker.findDescriptor(dependency.pluginId)?.let { dependencyDescriptor -> - findPluginExtensionPointRecursive(dependencyDescriptor, epName, pluginStateChecker, context, seenPlugins)?.let { return it.first to true } + pluginSet.findEnabledPlugin(dependency.pluginId)?.let { dependencyDescriptor -> + findLoadedPluginExtensionPointRecursive(dependencyDescriptor, epName, pluginSet, context, seenPlugins)?.let { return it.first to true } } } } + for (item in pluginDescriptor.dependencies.modules) { + pluginSet.findEnabledModule(item.name)?.requireDescriptor()?.let { d -> + findLoadedPluginExtensionPointRecursive(d, epName, pluginSet, context, seenPlugins)?.let { return it.first to true } + } + } + for (item in pluginDescriptor.dependencies.plugins) { + pluginSet.findEnabledPlugin(item.id)?.let { d -> + findLoadedPluginExtensionPointRecursive(d, epName, pluginSet, context, seenPlugins)?.let { return it.first to true } + } + } return null } diff --git a/platform/platform-impl/src/com/intellij/ide/plugins/PluginInstaller.java b/platform/platform-impl/src/com/intellij/ide/plugins/PluginInstaller.java index 103aa12becc8..b277d48a34c7 100644 --- a/platform/platform-impl/src/com/intellij/ide/plugins/PluginInstaller.java +++ b/platform/platform-impl/src/com/intellij/ide/plugins/PluginInstaller.java @@ -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.ide.plugins; import com.intellij.CommonBundle; @@ -389,7 +389,7 @@ public final class PluginInstaller { return false; } - return DynamicPlugins.loadPlugin(targetDescriptor); + return DynamicPlugins.INSTANCE.loadPlugin(targetDescriptor); } private static @NotNull Set findNotInstalledPluginDependencies(@NotNull List dependencies, diff --git a/platform/platform-impl/src/com/intellij/idea/StartupUtil.java b/platform/platform-impl/src/com/intellij/idea/StartupUtil.java index cad7a0c4d483..35a9ae6d0e54 100644 --- a/platform/platform-impl/src/com/intellij/idea/StartupUtil.java +++ b/platform/platform-impl/src/com/intellij/idea/StartupUtil.java @@ -10,10 +10,7 @@ import com.intellij.ide.AssertiveRepaintManager; import com.intellij.ide.BootstrapBundle; import com.intellij.ide.CliResult; import com.intellij.ide.IdeEventQueue; -import com.intellij.ide.customize.AbstractCustomizeWizardStep; import com.intellij.ide.customize.CommonCustomizeIDEWizardDialog; -import com.intellij.ide.customize.CustomizeIDEWizardDialog; -import com.intellij.ide.customize.CustomizeIDEWizardStepsProvider; import com.intellij.ide.gdpr.Agreements; import com.intellij.ide.gdpr.ConsentOptions; import com.intellij.ide.gdpr.EndUserAgreement; @@ -41,6 +38,7 @@ import com.intellij.ui.IconManager; import com.intellij.ui.mac.MacOSApplicationProvider; import com.intellij.ui.scale.JBUIScale; import com.intellij.util.EnvironmentUtil; +import com.intellij.util.lang.Java11Shim; import com.intellij.util.lang.ZipFilePool; import com.intellij.util.ui.StartupUiUtil; import com.intellij.util.ui.accessibility.ScreenReader; @@ -207,6 +205,7 @@ public final class StartupUtil { ZipFilePool.POOL = new ZipFilePoolImpl(); PluginManagerCore.scheduleDescriptorLoading(); } + Java11Shim.INSTANCE = new Java11ShimImpl(); forkJoinPool.execute(() -> { setupSystemLibraries(); @@ -974,4 +973,21 @@ public final class StartupUtil { } } } + + public static final class Java11ShimImpl extends Java11Shim { + @Override + public Map copyOf(Map map) { + return Map.copyOf(map); + } + + @Override + public Set copyOf(Set collection) { + return Set.copyOf(collection); + } + + @Override + public List copyOf(List collection) { + return List.copyOf(collection); + } + } } diff --git a/platform/platform-impl/src/com/intellij/openapi/actionSystem/impl/ActionManagerImpl.java b/platform/platform-impl/src/com/intellij/openapi/actionSystem/impl/ActionManagerImpl.java index a8afd1a6974b..167d4abc51e2 100644 --- a/platform/platform-impl/src/com/intellij/openapi/actionSystem/impl/ActionManagerImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/actionSystem/impl/ActionManagerImpl.java @@ -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.actionSystem.impl; import com.intellij.AbstractBundle; @@ -11,7 +11,10 @@ import com.intellij.icons.AllIcons; import com.intellij.ide.ActivityTracker; import com.intellij.ide.DataManager; import com.intellij.ide.ProhibitAWTEvents; -import com.intellij.ide.plugins.*; +import com.intellij.ide.plugins.IdeaPluginDescriptor; +import com.intellij.ide.plugins.IdeaPluginDescriptorImpl; +import com.intellij.ide.plugins.PluginManagerCore; +import com.intellij.ide.plugins.RawPluginDescriptor; import com.intellij.ide.ui.customization.ActionUrl; import com.intellij.ide.ui.customization.CustomActionsSchema; import com.intellij.idea.IdeaLogger; @@ -47,6 +50,7 @@ import com.intellij.openapi.util.text.StringUtilRt; import com.intellij.openapi.util.text.Strings; import com.intellij.openapi.wm.IdeFocusManager; import com.intellij.openapi.wm.IdeFrame; +import com.intellij.serviceContainer.ContainerUtilKt; import com.intellij.ui.icons.IconLoadMeasurer; import com.intellij.util.ArrayUtilRt; import com.intellij.util.ReflectionUtil; @@ -58,6 +62,7 @@ import com.intellij.util.messages.MessageBusConnection; import com.intellij.util.ui.UIUtil; import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import kotlin.Unit; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -134,7 +139,7 @@ public final class ActionManagerImpl extends ActionManagerEx implements Disposab } } - registerActions(PluginManagerCore.getLoadedPlugins(null), true); + registerActions(PluginManagerCore.getLoadedPlugins(null)); EP.forEachExtensionSafe(customizer -> customizer.customize(this)); DYNAMIC_EP_NAME.forEachExtensionSafe(customizer -> customizer.registerActions(this)); @@ -158,26 +163,12 @@ public final class ActionManagerImpl extends ActionManagerEx implements Disposab } @ApiStatus.Internal - public void registerActions(@NotNull List plugins, @SuppressWarnings("unused") boolean initialStartup) { + public void registerActions(@NotNull List plugins) { KeymapManagerEx keymapManager = Objects.requireNonNull(KeymapManagerEx.getInstanceEx()); - - for (IdeaPluginDescriptorImpl plugin : plugins) { - registerPluginActions(plugin, keymapManager); - for (PluginDependency pluginDependency : plugin.pluginDependencies) { - IdeaPluginDescriptorImpl subPlugin = pluginDependency.isDisabledOrBroken ? null : pluginDependency.subDescriptor; - if (subPlugin == null) { - continue; - } - - registerPluginActions(subPlugin, keymapManager); - for (PluginDependency subPluginDependency : subPlugin.pluginDependencies) { - IdeaPluginDescriptorImpl subSubPlugin = subPluginDependency.isDisabledOrBroken ? null : subPluginDependency.subDescriptor; - if (subSubPlugin != null) { - registerPluginActions(subSubPlugin, keymapManager); - } - } - } - } + ContainerUtilKt.executeRegisterTask(plugins, it -> { + registerPluginActions(it, keymapManager); + return Unit.INSTANCE; + }); } private static @NotNull AnActionListener publisher() { @@ -498,13 +489,11 @@ public final class ActionManagerImpl extends ActionManagerEx implements Disposab } @Override - @Nullable - public AnAction getAction(@NotNull String id) { + public @Nullable AnAction getAction(@NotNull String id) { return getActionImpl(id, false); } - @Nullable - private AnAction getActionImpl(@NotNull String id, boolean canReturnStub) { + private @Nullable AnAction getActionImpl(@NotNull String id, boolean canReturnStub) { AnAction action; synchronized (myLock) { action = idToAction.get(id); @@ -1266,20 +1255,17 @@ public final class ActionManagerImpl extends ActionManagerEx implements Disposab private @Nullable AnAction addToMap(@NotNull String actionId, @NotNull AnAction action, @Nullable ProjectType projectType) { - AnAction chameleonAction = idToAction.computeIfPresent(actionId, - (__, old) -> old instanceof ChameleonAction - ? old - : new ChameleonAction(old, projectType)); - if (chameleonAction != null) { - return ((ChameleonAction)chameleonAction).addAction(action, projectType); - } - else { - AnAction result = projectType != null ? - new ChameleonAction(action, projectType) : - action; + AnAction chameleonAction = idToAction.computeIfPresent(actionId, (__, old) -> { + return old instanceof ChameleonAction ? old : new ChameleonAction(old, projectType); + }); + if (chameleonAction == null) { + AnAction result = projectType == null ? action : new ChameleonAction(action, projectType); idToAction.put(actionId, result); return result; } + else { + return ((ChameleonAction)chameleonAction).addAction(action, projectType); + } } private void reportActionIdCollision(@NotNull String actionId, @@ -1299,7 +1285,7 @@ public final class ActionManagerImpl extends ActionManagerEx implements Disposab @Override public void registerAction(@NotNull String actionId, @NotNull AnAction action) { - registerAction(actionId, action, null); + registerAction(actionId, action, null, null); } @Override diff --git a/platform/platform-resources/src/META-INF/PlatformLangXmlPlugin.xml b/platform/platform-resources/src/META-INF/PlatformLangXmlPlugin.xml index c0d6415c5f3f..7bed0b97ef2c 100644 --- a/platform/platform-resources/src/META-INF/PlatformLangXmlPlugin.xml +++ b/platform/platform-resources/src/META-INF/PlatformLangXmlPlugin.xml @@ -7,7 +7,7 @@ - + diff --git a/platform/platform-tests/intellij.platform.tests.iml b/platform/platform-tests/intellij.platform.tests.iml index 9185b8f42daa..5c8bc2b74177 100644 --- a/platform/platform-tests/intellij.platform.tests.iml +++ b/platform/platform-tests/intellij.platform.tests.iml @@ -4,6 +4,7 @@ + diff --git a/platform/platform-tests/testSnapshots/plugin-validator/content module in the same source module.json b/platform/platform-tests/testSnapshots/plugin-validator/content module in the same source module.json new file mode 100644 index 000000000000..0c30f9576e11 --- /dev/null +++ b/platform/platform-tests/testSnapshots/plugin-validator/content module in the same source module.json @@ -0,0 +1,12 @@ +{ + "intellij.angularJs" : { + "name" : "intellij.angularJs", + "package" : null, + "descriptor" : "/intellij.angularJs/META-INF/plugin.xml", + "content" : [ { + "name" : "intellij.angularJs/diagram", + "package" : "org.angularjs.diagram", + "descriptor" : "/intellij.angularJs/intellij.angularJs.diagram.xml" + } ] + } +} \ No newline at end of file diff --git a/platform/platform-tests/testSnapshots/plugin-validator/dependency on a plugin in a new format must be in a plugin with package prefix.json b/platform/platform-tests/testSnapshots/plugin-validator/dependency on a plugin in a new format must be in a plugin with package prefix.json new file mode 100644 index 000000000000..ea723d0df266 --- /dev/null +++ b/platform/platform-tests/testSnapshots/plugin-validator/dependency on a plugin in a new format must be in a plugin with package prefix.json @@ -0,0 +1,6 @@ +1 errors: +[1]: ------------------------------ +`dependencies` must be specified only for plugin in a new format: package prefix is not specified ( + referencingDescriptorFile=/dependent/META-INF/plugin.xml +) +----------------------------------- \ No newline at end of file diff --git a/platform/platform-tests/testSnapshots/plugin-validator/dependency on a plugin is specified as a plugin.json b/platform/platform-tests/testSnapshots/plugin-validator/dependency on a plugin is specified as a plugin.json new file mode 100644 index 000000000000..ba1ad70abcd2 --- /dev/null +++ b/platform/platform-tests/testSnapshots/plugin-validator/dependency on a plugin is specified as a plugin.json @@ -0,0 +1,15 @@ +{ + "intellij.dependency" : { + "name" : "intellij.dependency", + "package" : "dependencyPackagePrefix", + "descriptor" : "/dependency/META-INF/plugin.xml" + }, + "intellij.dependent" : { + "name" : "intellij.dependent", + "package" : "dependentPackagePrefix", + "descriptor" : "/dependent/META-INF/plugin.xml", + "dependencies" : [ { + "plugin" : "dependency" + } ] + } +} \ No newline at end of file diff --git a/platform/platform-tests/testSnapshots/plugin-validator/dependency on a plugin must be resolvable.json b/platform/platform-tests/testSnapshots/plugin-validator/dependency on a plugin must be resolvable.json new file mode 100644 index 000000000000..344f6f085af3 --- /dev/null +++ b/platform/platform-tests/testSnapshots/plugin-validator/dependency on a plugin must be resolvable.json @@ -0,0 +1,7 @@ +1 errors: +[1]: ------------------------------ +Plugin not found: incorrectId ( + entry=XmlElement(name=plugin, attributes={id=incorrectId}, children=[], content=null), + referencingDescriptorFile=/dependent/META-INF/plugin.xml +) +----------------------------------- \ No newline at end of file diff --git a/platform/platform-tests/testSnapshots/plugin-validator/dependency on a plugin must be specified as a plugin.json b/platform/platform-tests/testSnapshots/plugin-validator/dependency on a plugin must be specified as a plugin.json new file mode 100644 index 000000000000..8f364f52bd39 --- /dev/null +++ b/platform/platform-tests/testSnapshots/plugin-validator/dependency on a plugin must be specified as a plugin.json @@ -0,0 +1,14 @@ +1 errors: +[1]: ------------------------------ +Dependency on plugin must be specified using `plugin` and not `module` ( + entry=XmlElement(name=module, attributes={name=intellij.dependent}, children=[], content=null), + referencingDescriptorFile=/dependent/META-INF/plugin.xml +) + +Proposed fix: + +Change dependency element to: + + + +----------------------------------- \ No newline at end of file diff --git a/platform/platform-tests/testSnapshots/plugin-validator/validate dependencies of content module in the same source module.json b/platform/platform-tests/testSnapshots/plugin-validator/validate dependencies of content module in the same source module.json new file mode 100644 index 000000000000..45d5f8e952e9 --- /dev/null +++ b/platform/platform-tests/testSnapshots/plugin-validator/validate dependencies of content module in the same source module.json @@ -0,0 +1,7 @@ +1 errors: +[1]: ------------------------------ +Module not found: com.intellij.diagram ( + entry=XmlElement(name=module, attributes={name=com.intellij.diagram}, children=[], content=null), + referencingDescriptorFile=/intellij.angularJs/intellij.angularJs.diagram.xml +) +----------------------------------- \ No newline at end of file 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 17ea86e06817..6f8e4326ca2a 100644 --- a/platform/platform-tests/testSrc/com/intellij/ide/plugins/ClassLoaderConfiguratorTest.kt +++ b/platform/platform-tests/testSrc/com/intellij/ide/plugins/ClassLoaderConfiguratorTest.kt @@ -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.ide.plugins import com.intellij.ide.plugins.cl.PluginClassLoader @@ -6,78 +6,89 @@ import com.intellij.openapi.util.BuildNumber import com.intellij.testFramework.assertions.Assertions.assertThat import com.intellij.testFramework.assertions.Assertions.assertThatThrownBy import com.intellij.testFramework.rules.InMemoryFsRule +import com.intellij.util.io.Murmur3_32Hash import com.intellij.util.io.directoryStreamIfExists import org.junit.Rule import org.junit.Test +import org.junit.rules.TestName import java.nio.file.Path import java.util.* -import java.util.function.Consumer import java.util.function.Supplier private val buildNumber = BuildNumber.fromString("2042.0")!! internal class ClassLoaderConfiguratorTest { - @Rule - @JvmField - val inMemoryFs = InMemoryFsRule() + @Rule @JvmField val name = TestName() + + @Rule @JvmField val inMemoryFs = InMemoryFsRule() @Test fun packageForOptionalMustBeSpecified() { assertThatThrownBy { - loadPlugins { - PluginBuilder().noDepends() - } - }.hasMessageStartingWith("Sub descriptor must specify package if it is specified for main plugin descriptor ") + loadPlugins(modulePackage = null) + }.hasMessage("Package is not specified (module=PluginDescriptor(name=p_dependent_i0ihh8, id=p_dependent_i0ihh8, descriptorPath=com.example.sub.xml, path=/p_dependent_i0ihh8, version=2042.0, package=null))") } @Test fun packageForOptionalMustBeDifferent() { assertThatThrownBy { - loadPlugins { - PluginBuilder().noDepends().packagePrefix("com.example") - } - }.hasMessageStartingWith("Sub descriptor must not specify the same package as main plugin descriptor [") + loadPlugins(modulePackage = "com.example") + }.hasMessage("Package prefix com.example is already used (module=PluginDescriptor(name=p_dependent_12m42wo, id=p_dependent_12m42wo, descriptorPath=com.example.sub.xml, path=/p_dependent_12m42wo, version=2042.0, package=com.example))") } @Test fun packageMustBeUnique() { assertThatThrownBy { - loadPlugins { - PluginBuilder().noDepends().packagePrefix("com.bar") - } - }.hasMessageStartingWith("Package prefix com.bar is already used [") + loadPlugins(modulePackage = "com.bar") + }.hasMessage("Package prefix com.bar is already used (module=PluginDescriptor(name=p_dependent_x9e2t2, id=p_dependent_x9e2t2, descriptorPath=com.example.sub.xml, path=/p_dependent_x9e2t2, version=2042.0, package=com.bar))") } @Test fun regularPluginClassLoaderIsUsedIfPackageSpecified() { - loadPlugins { - PluginBuilder().noDepends().packagePrefix("com.example.extraSupportedFeature") - }.getEnabledPlugins().get(1).pluginDependencies.get(0).subDescriptor!!.classLoader!!.javaClass === PluginClassLoader::class.java + val plugin = loadPlugins(modulePackage = "com.example.extraSupportedFeature").getEnabledPlugins().get(1) + assertThat(plugin.content.modules.get(0).requireDescriptor().classLoader).isInstanceOf(PluginClassLoader::class.java) } - private fun loadPlugins(dependent: () -> PluginBuilder): PluginLoadingResult { + @Suppress("PluginXmlValidity") + private fun loadPlugins(modulePackage: String?): PluginLoadingResult { val rootDir = inMemoryFs.fs.getPath("/") - val pluginDependency = PluginBuilder() - .noDepends() - .packagePrefix("com.bar") - .extensionPoints( - """""") - .buildToAutoGeneratedSubDir(rootDir) + // toUnsignedLong - avoid - symbol + val pluginIdSuffix = Integer.toUnsignedLong(Murmur3_32Hash.MURMUR3_32.hashString(javaClass.name + name.methodName)).toString(36) + val dependencyId = "p_dependency_$pluginIdSuffix" + plugin(rootDir, """ + + $dependencyId + + " + + + """) + + val dependentPluginId = "p_dependent_$pluginIdSuffix" + plugin(rootDir, """ + + $dependentPluginId + + + + + """) + module(rootDir, dependentPluginId, "com.example.sub", """ + + + + " + + + """) - PluginBuilder() - .noDepends() - .packagePrefix("com.example") - // dependent must not be empty, add some extension - .depends(pluginDependency.buildToAutoGeneratedSubDir(rootDir).id, - dependent().extensions("""""", "bar")) - .buildToAutoGeneratedSubDir(rootDir) val loadResult = loadDescriptors(rootDir) val plugins = loadResult.getEnabledPlugins() assertThat(plugins).hasSize(2) - val classLoaderConfigurator = ClassLoaderConfigurator(true, PluginManagerCore::class.java.classLoader, loadResult.idMap, emptyMap()) - plugins.forEach(Consumer(classLoaderConfigurator::configure)) + val classLoaderConfigurator = ClassLoaderConfigurator(PluginSet(plugins, plugins)) + plugins.forEach(classLoaderConfigurator::configure) return loadResult } } @@ -87,7 +98,7 @@ private fun loadDescriptors(dir: Path): PluginLoadingResult { val context = DescriptorListLoadingContext(disabledPlugins = Collections.emptySet(), result = result) // constant order in tests - val paths: List = dir.directoryStreamIfExists { it.sorted() }!! + val paths = dir.directoryStreamIfExists { it.sorted() }!! context.use { for (file in paths) { result.add(loadDescriptor(file, false, context) ?: continue, false) 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 ee63e12a1960..e6a31eaa9592 100644 --- a/platform/platform-tests/testSrc/com/intellij/ide/plugins/DynamicPluginsTest.kt +++ b/platform/platform-tests/testSrc/com/intellij/ide/plugins/DynamicPluginsTest.kt @@ -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. @file:Suppress("UsePropertyAccessSyntax") package com.intellij.ide.plugins @@ -24,6 +24,7 @@ import com.intellij.openapi.components.Storage import com.intellij.openapi.components.service import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.editor.Editor +import com.intellij.openapi.extensions.InternalIgnoreDependencyViolation import com.intellij.openapi.extensions.PluginId import com.intellij.openapi.module.ModuleConfigurationEditor import com.intellij.openapi.options.Configurable @@ -109,7 +110,7 @@ class DynamicPluginsTest { DisabledPluginsState.saveDisabledPlugins(PathManager.getConfigDir()) val newDescriptor = loadDescriptorInTest(path) - PluginManagerCore.createClassLoaderConfiguratorForDynamicPlugin(newDescriptor).configure(newDescriptor) + ClassLoaderConfigurator(PluginManagerCore.getPluginSet().concat(newDescriptor)).configure(newDescriptor) DynamicPlugins.loadPlugin(newDescriptor) try { assertThat(PluginManagerCore.getPlugin(descriptor.pluginId)?.pluginClassLoader as? PluginClassLoader).isNotNull() @@ -188,22 +189,29 @@ class DynamicPluginsTest { @Test fun loadOptionalDependency() { + // match production - on plugin load/unload ActionManager is already initialized + val actionManager = ActionManager.getInstance() + val beforeList = PluginManagerCore.getLoadedPlugins() - val plugin2Builder = PluginBuilder().randomId("bar") - val plugin1Disposable = loadPluginWithOptionalDependency( - PluginBuilder().randomId("foo").packagePrefix("org.foo"), - PluginBuilder().actions("""""").packagePrefix("org.foo.bar"), - plugin2Builder - ) + val plugin2Builder = PluginBuilder().randomId("bar").packagePrefix("bar") + val pluginDescriptor = PluginBuilder() + .randomId("foo") + .packagePrefix("org.foo") + .module( + "intellij.org.foo", + PluginBuilder().actions("""""").packagePrefix("org.foo.bar").dependency(plugin2Builder.id) + ) + val plugin1Disposable = loadPluginWithText(pluginDescriptor) + assertThat(actionManager.getAction("FooBarGroup")).isNull() try { val pluginToDisposable = loadPluginWithText(plugin2Builder) try { - assertThat(ActionManager.getInstance().getAction("FooBarGroup")).isNotNull() + assertThat(actionManager.getAction("FooBarGroup")).isNotNull() } finally { Disposer.dispose(pluginToDisposable) } - assertThat(ActionManager.getInstance().getAction("FooBarGroup")).isNull() + assertThat(actionManager.getAction("FooBarGroup")).isNull() } finally { Disposer.dispose(plugin1Disposable) @@ -235,12 +243,21 @@ class DynamicPluginsTest { val pluginTwoBuilder = PluginBuilder() .randomId("optionalDependencyExtension-two") .packagePrefix("org.foo.two") - .extensionPoints("""""") + .extensionPoints( + """""") - val plugin1Disposable = loadPluginWithOptionalDependency( - PluginBuilder().randomId("optionalDependencyExtension-one").packagePrefix("org.foo.one"), - PluginBuilder().extensions("""""", "bar").packagePrefix("org.foo"), - pluginTwoBuilder + val plugin1Disposable = loadPluginWithText( + PluginBuilder() + .randomId("optionalDependencyExtension-one") + .packagePrefix("org.foo.one") + .noDepends() + .module( + moduleName = "intellij.foo.one.module1", + moduleDescriptor = PluginBuilder() + .extensions("""""", "bar") + .dependency(pluginTwoBuilder.id) + .packagePrefix("org.foo"), + ) ) try { val plugin2Disposable = loadPluginWithText(pluginTwoBuilder) @@ -266,12 +283,15 @@ class DynamicPluginsTest { fun loadOptionalDependencyOwnExtension() { val barBuilder = PluginBuilder().randomId("bar").packagePrefix("bar") val fooBuilder = PluginBuilder().randomId("foo").packagePrefix("foo") - .extensionPoints("""""") - val plugin1Disposable = loadPluginWithOptionalDependency( - fooBuilder, - PluginBuilder().extensions("""""", "foo").packagePrefix("foo1"), - barBuilder - ) + .extensionPoints( + """""") + .module("intellij.foo.sub", + PluginBuilder() + .extensions("""""", "foo") + .packagePrefix("foo1") + .dependency(barBuilder.id) + ) + val plugin1Disposable = loadPluginWithText(fooBuilder) try { val ep = ApplicationManager.getApplication().extensionArea.getExtensionPointIfRegistered>("foo.barExtension") assertThat(ep).isNotNull() @@ -279,7 +299,8 @@ class DynamicPluginsTest { try { val extension = ep!!.extensionList.single() assertThat(extension.key).isEqualTo("foo") - assertThat(extension.pluginDescriptor).isEqualTo(PluginManagerCore.getPlugin(PluginId.getId(fooBuilder.id))) + assertThat(extension.pluginDescriptor) + .isEqualTo(PluginManagerCore.getPluginSet().findEnabledModule(fooBuilder.id)!!.requireDescriptor()) } finally { Disposer.dispose(plugin2Disposable) @@ -323,17 +344,20 @@ class DynamicPluginsTest { receivedNotifications.clear() receivedNotifications2.clear() - val pluginTwoBuilder = PluginBuilder().randomId("optionalDependencyListener-two") - val pluginOneDisposable = loadPluginWithOptionalDependency( - PluginBuilder().randomId("optionalDependencyListener-one") - .applicationListeners("""""") - .packagePrefix("org.foo.one"), - PluginBuilder() - .applicationListeners("""""") - .packagePrefix("org.foo"), - pluginTwoBuilder - ) - + val pluginTwoBuilder = PluginBuilder().randomId("optionalDependencyListener-two").packagePrefix("optionalDependencyListener-two") + val pluginDescriptor = PluginBuilder().randomId("optionalDependencyListener-one").packagePrefix("optionalDependencyListener-one") + .applicationListeners( + """""") + .packagePrefix("org.foo.one") + .module( + "intellij.org.foo", + PluginBuilder() + .applicationListeners( + """""") + .packagePrefix("org.foo") + .dependency(pluginTwoBuilder.id), + ) + val pluginOneDisposable = loadPluginWithText(pluginDescriptor) try { val app = ApplicationManager.getApplication() app.messageBus.syncPublisher(UISettingsListener.TOPIC).uiSettingsChanged(UISettings()) @@ -416,13 +440,13 @@ class DynamicPluginsTest { } @Test - fun testExtensionOnServiceDependency() { + fun extensionOnServiceDependency() { val project = projectRule.project StartupManagerImpl.addActivityEpListener(project) val disposable = loadExtensionWithText(""" - """.trimIndent(), DynamicPluginsTest::class.java.classLoader) + """, DynamicPluginsTest::class.java.classLoader) try { assertThat(project.service().executed).isTrue() } @@ -575,67 +599,6 @@ class DynamicPluginsTest { } } - @Test - fun loadNestedOptionalDependency() { - val barBuilder = PluginBuilder().randomId("bar") - val quuxBuilder = PluginBuilder().randomId("quux") - - val quuxDependencyDescriptor = PluginBuilder().extensions( - """""") - val barDependencyDescriptor = PluginBuilder().depends(quuxBuilder.id, quuxDependencyDescriptor) - val mainDescriptor = PluginBuilder().depends(barBuilder.id, barDependencyDescriptor) - val barDisposable = loadPluginWithText(barBuilder) - try { - val quuxDisposable = loadPluginWithText(quuxBuilder) - try { - val mainDisposable = loadPluginWithText(mainDescriptor) - try { - assertThat(ApplicationManager.getApplication().getService(MyPersistentComponent::class.java)).isNotNull() - } - finally { - Disposer.dispose(mainDisposable) - assertThat(ApplicationManager.getApplication().getService(MyPersistentComponent::class.java)).isNull() - } - } - finally { - Disposer.dispose(quuxDisposable) - } - } - finally { - Disposer.dispose(barDisposable) - } - } - - @Test - fun unloadNestedOptionalDependency() { - val barBuilder = PluginBuilder().randomId("bar") - val quuxBuilder = PluginBuilder().randomId("quux") - - val quuxDependencyDescriptor = PluginBuilder() - .extensions("""""") - .packagePrefix("org.foo.quux") - val barDependencyDescriptor = PluginBuilder().depends(quuxBuilder.id, quuxDependencyDescriptor) - .packagePrefix("org.foo.bar") - val mainDescriptor = PluginBuilder().depends(barBuilder.id, barDependencyDescriptor).packagePrefix("org.foo") - - val barDisposable = loadPluginWithText(barBuilder) - try { - val quuxDisposable = loadPluginWithText(quuxBuilder) - val mainDisposable = loadPluginWithText(mainDescriptor) - try { - assertThat(ApplicationManager.getApplication().getService(MyPersistentComponent::class.java)).isNotNull() - Disposer.dispose(quuxDisposable) - assertThat(ApplicationManager.getApplication().getService(MyPersistentComponent::class.java)).isNull() - } - finally { - Disposer.dispose(mainDisposable) - } - } - finally { - Disposer.dispose(barDisposable) - } - } - private fun loadPluginWithOptionalDependency(pluginDescriptor: PluginBuilder, optionalDependencyDescriptor: PluginBuilder, dependsOn: PluginBuilder): Disposable { @@ -669,6 +632,7 @@ private class MyPersistentComponent : PersistentStateComponent { - return arrayOf() - } + override fun createEditors(state: ModuleConfigurationState?): Array = arrayOf() } diff --git a/platform/platform-tests/testSrc/com/intellij/ide/plugins/DynamicPluginsTestUtil.kt b/platform/platform-tests/testSrc/com/intellij/ide/plugins/DynamicPluginsTestUtil.kt index 588ee52a7c22..440342b65fdd 100644 --- a/platform/platform-tests/testSrc/com/intellij/ide/plugins/DynamicPluginsTestUtil.kt +++ b/platform/platform-tests/testSrc/com/intellij/ide/plugins/DynamicPluginsTestUtil.kt @@ -1,10 +1,8 @@ -// 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. @file:JvmName("DynamicPluginsTestUtil") @file:Suppress("UsePropertyAccessSyntax") package com.intellij.ide.plugins -import com.intellij.ide.plugins.DynamicPlugins.loadPlugin -import com.intellij.ide.plugins.DynamicPlugins.unloadPlugin import com.intellij.openapi.Disposable import com.intellij.openapi.extensions.PluginId import com.intellij.openapi.util.BuildNumber @@ -60,21 +58,18 @@ internal fun loadPluginWithText(pluginBuilder: PluginBuilder, loader: ClassLoade pluginBuilder.build(pluginDirectory) val descriptor = loadDescriptorInTest(pluginDirectory) assertThat(DynamicPlugins.checkCanUnloadWithoutRestart(descriptor)).isNull() - setPluginClassLoaderForMainAndSubPlugins(descriptor, loader) try { - loadPlugin(descriptor) + DynamicPlugins.loadPlugin(pluginDescriptor = descriptor, classLoaderForTest = loader) } catch (e: Exception) { - unloadPlugin(descriptor) + DynamicPlugins.unloadPlugin(descriptor) throw e } return Disposable { - val unloadDescriptor = loadDescriptorInTest(pluginDirectory) - val canBeUnloaded = DynamicPlugins.allowLoadUnloadWithoutRestart(unloadDescriptor) - unloadPlugin(descriptor) - - assertThat(canBeUnloaded).isTrue() + val reason = DynamicPlugins.checkCanUnloadWithoutRestart(descriptor) + DynamicPlugins.unloadPlugin(descriptor) + assertThat(reason).isNull() } } 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 5b226c78fcaa..d1d6f3ea4317 100644 --- a/platform/platform-tests/testSrc/com/intellij/ide/plugins/PluginBuilder.kt +++ b/platform/platform-tests/testSrc/com/intellij/ide/plugins/PluginBuilder.kt @@ -1,8 +1,9 @@ -// 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.ide.plugins import com.intellij.util.io.Compressor 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 @@ -11,6 +12,39 @@ import java.util.concurrent.atomic.AtomicInteger private val pluginIdCounter = AtomicInteger() +fun plugin(outDir: Path, @Language("XML") descriptor: String) { + val rawDescriptor = try { + readModuleDescriptorForTest(descriptor.toByteArray()) + } + catch (e: Throwable) { + throw RuntimeException("Cannot parse:\n ${descriptor.trimIndent().prependIndent(" ")}", e) + } + outDir.resolve("${rawDescriptor.id!!}/META-INF/plugin.xml").write(descriptor.trimIndent()) +} + +fun module(outDir: Path, ownerId: String, moduleId: String, @Language("XML") descriptor: String) { + try { + readModuleDescriptorForTest(descriptor.toByteArray()) + } + catch (e: Throwable) { + throw RuntimeException("Cannot parse:\n ${descriptor.trimIndent().prependIndent(" ")}", e) + } + 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?) @@ -31,7 +65,10 @@ class PluginBuilder { private var untilBuild: String? = null private var version: String? = null - private val subDescriptors = mutableMapOf() + private val content = mutableListOf() + private val dependencies = mutableListOf() + + private val subDescriptors = HashMap() init { depends("com.intellij.modules.lang") @@ -57,7 +94,7 @@ class PluginBuilder { return this } - fun packagePrefix(value: String): PluginBuilder { + fun packagePrefix(value: String?): PluginBuilder { packagePrefix = value return this } @@ -69,11 +106,26 @@ class PluginBuilder { fun depends(pluginId: String, subDescriptor: PluginBuilder): PluginBuilder { val fileName = "dep_${pluginIdCounter.incrementAndGet()}.xml" - subDescriptors[fileName] = subDescriptor + subDescriptors.put("META-INF/$fileName", subDescriptor) depends(pluginId, fileName) return this } + fun module(moduleName: String, moduleDescriptor: PluginBuilder): PluginBuilder { + val fileName = "$moduleName.xml" + subDescriptors.put(fileName, moduleDescriptor) + content.add(PluginContentDescriptor.ModuleItem(name = moduleName, configFile = null)) + + // remove default dependency on lang + moduleDescriptor.noDepends() + return this + } + + fun dependency(moduleName: String): PluginBuilder { + dependencies.add(ModuleDependenciesDescriptor.ModuleReference(moduleName)) + return this + } + fun noDepends(): PluginBuilder { dependsTags.clear() return this @@ -104,7 +156,7 @@ class PluginBuilder { return this } - fun extensionPoints(text: String): PluginBuilder { + fun extensionPoints(@Language("XML") text: String): PluginBuilder { extensionPoints = text return this } @@ -148,14 +200,22 @@ class PluginBuilder { extensionPoints?.let { append("$it") } applicationListeners?.let { append("$it") } actions?.let { append("$it") } + + if (content.isNotEmpty()) { + append("\n\n ") + content.joinTo(this, separator = "\n ") { """""" } + append("\n") + } + if (dependencies.isNotEmpty()) { + append("\n\n ") + dependencies.joinTo(this, separator = "\n ") { """""" } + append("\n") + } + append("") } } - fun buildToAutoGeneratedSubDir(parentDir: Path): PluginBuilder { - return build(parentDir.resolve(id)) - } - fun build(path: Path): PluginBuilder { path.resolve("META-INF/plugin.xml").write(text()) writeSubDescriptors(path) @@ -164,7 +224,7 @@ class PluginBuilder { fun writeSubDescriptors(path: Path) { for ((fileName, subDescriptor) in subDescriptors) { - path.resolve("META-INF/$fileName").write(subDescriptor.text(requireId = false)) + path.resolve(fileName).write(subDescriptor.text(requireId = false)) subDescriptor.writeSubDescriptors(path) } } 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 3c0fa5c6fa13..97b1b001c98f 100644 --- a/platform/platform-tests/testSrc/com/intellij/ide/plugins/PluginDescriptorTest.kt +++ b/platform/platform-tests/testSrc/com/intellij/ide/plugins/PluginDescriptorTest.kt @@ -29,6 +29,7 @@ import java.nio.file.Path import java.nio.file.Paths import java.text.SimpleDateFormat import java.util.* +import java.util.function.Function import java.util.function.Supplier import kotlin.test.assertEquals import kotlin.test.assertFalse @@ -115,9 +116,9 @@ class PluginDescriptorTest { fun testProductionPlugins() { IoTestUtil.assumeMacOS() assumeNotUnderTeamcity() - val descriptors = loadAndInitDescriptors(Paths.get("/Applications/Idea.app/Contents/plugins"), PluginManagerCore.getBuildNumber()).sortedPlugins + val descriptors = loadAndInitDescriptors(Paths.get("/Applications/Idea.app/Contents/plugins"), PluginManagerCore.getBuildNumber()).pluginSet.allPlugins assertThat(descriptors).isNotEmpty() - assertThat(descriptors.find { it!!.pluginId.idString == "com.intellij.java" }).isNotNull + assertThat(descriptors.find { it.pluginId.idString == "com.intellij.java" }).isNotNull } @Test @@ -140,7 +141,7 @@ class PluginDescriptorTest { IoTestUtil.assumeMacOS() assumeNotUnderTeamcity() - val descriptors = loadAndInitDescriptors(Paths.get("/Volumes/data/plugins"), PluginManagerCore.getBuildNumber()).sortedPlugins + val descriptors = loadAndInitDescriptors(Paths.get("/Volumes/data/plugins"), PluginManagerCore.getBuildNumber()).pluginSet.allPlugins assertThat(descriptors).isNotEmpty() } @@ -195,15 +196,15 @@ class PluginDescriptorTest { 2.0 """) - val result = loadAndInitDescriptors(pluginDir, PluginManagerCore.getBuildNumber()) - val plugins = result.sortedEnabledPlugins + val pluginSet = loadAndInitDescriptors(pluginDir, PluginManagerCore.getBuildNumber()).pluginSet + val plugins = pluginSet.loadedPlugins assertThat(plugins).hasSize(1) val foo = plugins[0] assertThat(foo.version).isEqualTo("2.0") assertThat(foo.pluginId.idString).isEqualTo("foo") - assertThat(result.idMap).containsOnlyKeys(foo.pluginId) - assertThat(result.idMap.get(foo.pluginId)).isSameAs(foo) + assertThat(pluginSet.allPlugins.toList()).map(Function { it.id }).containsOnly(foo.pluginId) + assertThat(pluginSet.findEnabledPlugin(foo.pluginId)).isSameAs(foo) } @Suppress("PluginXmlValidity") @@ -284,16 +285,16 @@ class PluginDescriptorTest { """) - val result = loadAndInitDescriptors(pluginDir, BuildNumber.fromString("3.12")!!) + val pluginSet = loadAndInitDescriptors(pluginDir, BuildNumber.fromString("3.12")!!).pluginSet - val plugins = result.sortedEnabledPlugins + val plugins = pluginSet.loadedPlugins assertThat(plugins).hasSize(1) val foo = plugins[0] assertThat(foo.version).isEqualTo("2.0") assertThat(foo.pluginId.idString).isEqualTo("foo") - assertThat(result.idMap).containsOnlyKeys(foo.pluginId) - assertThat(result.idMap[foo.pluginId]).isSameAs(foo) + assertThat(pluginSet.allPlugins.toList()).map(Function { it.id }).containsOnly(foo.pluginId) + assertThat(pluginSet.findEnabledPlugin(foo.pluginId)).isSameAs(foo) } @Suppress("PluginXmlValidity") @@ -303,15 +304,15 @@ class PluginDescriptorTest { PluginBuilder().noDepends().id("foo").version("1.0").build(pluginDir.resolve("foo_1-0")) PluginBuilder().noDepends().id("foo").version("1.0").build(pluginDir.resolve("foo_another")) - val result = loadAndInitDescriptors(pluginDir, PluginManagerCore.getBuildNumber()) - val plugins = result.sortedEnabledPlugins + val pluginSet = loadAndInitDescriptors(pluginDir, PluginManagerCore.getBuildNumber()).pluginSet + val plugins = pluginSet.loadedPlugins assertThat(plugins).hasSize(1) val foo = plugins[0] assertThat(foo.version).isEqualTo("1.0") assertThat(foo.pluginId.idString).isEqualTo("foo") - assertThat(result.idMap).containsOnlyKeys(foo.pluginId) - assertThat(result.idMap.get(foo.pluginId)).isSameAs(foo) + assertThat(pluginSet.allPlugins.toList()).map(Function { it.id }).containsOnly(foo.pluginId) + assertThat(pluginSet.findEnabledPlugin(foo.pluginId)).isSameAs(foo) } @Suppress("PluginXmlValidity") @@ -353,7 +354,7 @@ class PluginDescriptorTest { } private fun checkClassLoader(pluginDir: Path) { - val list = loadAndInitDescriptors(pluginDir, PluginManagerCore.getBuildNumber()).sortedEnabledPlugins + val list = loadAndInitDescriptors(pluginDir, PluginManagerCore.getBuildNumber()).pluginSet.loadedPlugins assertThat(list).hasSize(2) val bar = list[0] @@ -453,8 +454,8 @@ class PluginDescriptorTest { PluginBuilder().noDepends().id("foo").depends("bar").build(pluginDir.resolve("foo")) PluginBuilder().noDepends().id("bar").build(pluginDir.resolve("bar")) - val result = loadAndInitDescriptors(pluginDir, PluginManagerCore.getBuildNumber(), setOf(PluginId.getId("bar"))) - assertThat(result.sortedEnabledPlugins).isEmpty() + val pluginSet = loadAndInitDescriptors(pluginDir, PluginManagerCore.getBuildNumber(), setOf(PluginId.getId("bar"))).pluginSet + assertThat(pluginSet.loadedPlugins).isEmpty() } @Test @@ -477,8 +478,8 @@ class PluginDescriptorTest { .id("com.intellij.gradle") .build(pluginDir.resolve("intellij.gradle")) - val result = loadAndInitDescriptors(pluginDir, PluginManagerCore.getBuildNumber(), setOf(PluginId.getId("com.intellij.gradle"))) - assertThat(result.sortedEnabledPlugins).isEmpty() + val result = loadAndInitDescriptors(pluginDir, PluginManagerCore.getBuildNumber(), setOf(PluginId.getId("com.intellij.gradle"))).pluginSet + assertThat(result.loadedPlugins).isEmpty() } } diff --git a/platform/platform-tests/testSrc/com/intellij/ide/plugins/PluginManagerTest.java b/platform/platform-tests/testSrc/com/intellij/ide/plugins/PluginManagerTest.java index e34e59d45aac..51e37270bfd2 100644 --- a/platform/platform-tests/testSrc/com/intellij/ide/plugins/PluginManagerTest.java +++ b/platform/platform-tests/testSrc/com/intellij/ide/plugins/PluginManagerTest.java @@ -195,7 +195,7 @@ public class PluginManagerTest { PluginManagerCore.getAndClearPluginLoadingErrors(); PluginManagerState loadPluginResult = loadAndInitializeDescriptors(testDataName + ".xml", isBundled); StringBuilder text = new StringBuilder(); - for (IdeaPluginDescriptorImpl descriptor : loadPluginResult.sortedPlugins) { + for (IdeaPluginDescriptorImpl descriptor : loadPluginResult.pluginSet.loadedPlugins) { text.append(descriptor.isEnabled() ? "+ " : " ").append(descriptor.getPluginId().getIdString()).append('\n'); } text.append("\n\n"); @@ -270,6 +270,15 @@ public class PluginManagerTest { } throw new AssertionError("Unexpected: " + relativePath); } + + @NotNull + @Override + public RawPluginDescriptor resolveModuleFile(@NotNull ReadModuleContext readContext, + @NotNull DataLoader dataLoader, + @NotNull String path, + @Nullable RawPluginDescriptor readInto) { + return resolvePath(readContext, dataLoader, path, readInto); + } }; for (XmlElement element : root.children) { diff --git a/platform/platform-tests/testSrc/com/intellij/ide/plugins/PluginModelTest.kt b/platform/platform-tests/testSrc/com/intellij/ide/plugins/PluginModelTest.kt index 121f17871a75..25f80448b43c 100644 --- a/platform/platform-tests/testSrc/com/intellij/ide/plugins/PluginModelTest.kt +++ b/platform/platform-tests/testSrc/com/intellij/ide/plugins/PluginModelTest.kt @@ -1,517 +1,35 @@ // 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. package com.intellij.ide.plugins -import com.fasterxml.jackson.core.JsonFactory -import com.fasterxml.jackson.core.JsonGenerator -import com.intellij.openapi.application.PathManager -import com.intellij.openapi.util.JDOMUtil import com.intellij.project.IntelliJProjectConfiguration -import com.intellij.util.io.jackson.array -import com.intellij.util.io.jackson.obj -import com.intellij.util.throwIfNotEmpty -import org.jdom.Element -import org.jetbrains.jps.model.java.JavaResourceRootType -import org.jetbrains.jps.model.java.JavaSourceRootType -import org.jetbrains.jps.model.module.JpsModule +import com.intellij.util.getErrorsAsString import org.jetbrains.jps.util.JpsPathUtil +import org.junit.Assert import org.junit.Test -import java.io.StringWriter -import java.nio.file.Files -import java.nio.file.NoSuchFileException import java.nio.file.Path -private data class ModuleInfo(val descriptorFile: Path, val isPlugin: Boolean) { - val dependencies = mutableListOf() - val content = mutableListOf() -} - -private data class Reference(val moduleName: String, val packageName: String) - -private val homePath by lazy { Path.of(PathManager.getHomePath()) } - -private data class PluginTask(val pluginId: String, - val moduleName: String, - val descriptor: Element, - val pluginXml: Path) { - val moduleReferences = mutableListOf() -} - -private class ModuleDescriptorPath { - var pluginDescriptorFile: Path? = null - var moduleDescriptorFile: Path? = null - - var pluginDescriptor: Element? = null - var moduleDescriptor: Element? = null -} - class PluginModelTest { @Test fun check() { - val modules = IntelliJProjectConfiguration.loadIntelliJProject(homePath).modules - val checker = PluginModelChecker(modules) + val modules = IntelliJProjectConfiguration.loadIntelliJProject(PluginModelValidator.homePath).modules.map { module -> + object : PluginModelValidator.Module { + override val name: String + get() = module.name - // we cannot check everything in one pass - before checking we have to compute map of plugin to plugin modules - // to be able to correctly check `Docker` that in a new plugin model specified as - // - - val moduleNameToFileInfo = computeModuleSet(modules, checker) - l@ for ((moduleName, moduleMetaInfo) in moduleNameToFileInfo) { - val descriptor = moduleMetaInfo.pluginDescriptor ?: continue - val pluginXml = moduleMetaInfo.pluginDescriptorFile ?: continue - - val id = descriptor.getChild("id")?.text ?: descriptor.getChild("name")?.text - if (id == null) { - checker.errors.add(PluginValidationError("Plugin id is not specified in ${homePath.relativize(pluginXml)}")) - continue - } - - val task = PluginTask(pluginId = id, moduleName = moduleName, descriptor = descriptor, pluginXml = pluginXml) - checker.pluginIdToModules.put(id, task) - - val content = descriptor.getChild("content") - if (content != null) { - for (item in content.getChildren("module")) { - // ignore missed attributes - will be validated on second pass - val packageName = item.getAttributeValue("package") ?: continue - task.moduleReferences.add(Reference(moduleName = item.getAttributeValue("name") ?: continue, - packageName = packageName)) - - val old = checker.packageToPlugin.put(packageName, task) - if (old != null) { - checker.errors.add(PluginValidationError("Duplicated package $packageName (old=$old, new=$task)")) - continue@l - } + override fun getSourceRoots(): List { + return module.sourceRoots.asSequence() + .filter { !it.rootType.isForTests } + .map { Path.of(JpsPathUtil.urlToPath(it.url)) } + .toList() } } } - - for (task in checker.pluginIdToModules.values) { - checkModule(task, checker) + val validator = PluginModelValidator() + val errors = validator.validate(modules) + if (!errors.isEmpty()) { + System.err.println(getErrorsAsString(errors, includeStackTrace = false)) + Assert.fail() } - - throwIfNotEmpty(checker.errors) - printGraph(checker) + validator.printGraph() } - - private fun checkModule(info: PluginTask, checker: PluginModelChecker) { - val descriptor = info.descriptor - val dependencies = descriptor.getChild("dependencies") - if (dependencies != null) { - val pluginInfo = checker.graph.computeIfAbsent(info.moduleName) { ModuleInfo(info.pluginXml, isPlugin = true) } - try { - checker.checkDependencies(dependencies, descriptor, info.pluginXml, pluginInfo) - } - catch (e: PluginValidationError) { - checker.errors.add(e) - } - } - - val content = descriptor.getChild("content") - if (content != null) { - val pluginInfo = checker.graph.computeIfAbsent(info.moduleName) { ModuleInfo(info.pluginXml, isPlugin = true) } - try { - checker.checkContent(content, descriptor, info.pluginXml, pluginInfo) - } - catch (e: PluginValidationError) { - checker.errors.add(e) - } - } - - val aPackage = descriptor.getAttributeValue("package") - if (aPackage == null && checker.graph.containsKey(info.moduleName) /* something was added */) { - // some plugins cannot be yet fully migrated - System.err.println("Package is not specified for ${info.moduleName} " + - "(pluginId=${info.pluginId}, descriptor=${homePath.relativize(info.pluginXml)})") - } - } -} - -private fun computeModuleSet(modules: List, checker: PluginModelChecker): LinkedHashMap { - val moduleNameToFileInfo = LinkedHashMap() - for (module in modules) { - // platform/cwm-plugin/resources/META-INF/plugin.xml doesn't have id - ignore for now - if (module.name.startsWith("fleet.") || - module.name == "fleet" || - module.name == "intellij.idea.ultimate.resources" || - module.name == "intellij.lightEdit" || - module.name == "intellij.webstorm" || - module.name == "intellij.cwm.plugin") { - continue - } - - for (sourceRoot in module.sourceRoots) { - if (sourceRoot.rootType.isForTests) { - continue - } - - val root = Path.of(JpsPathUtil.urlToPath(sourceRoot.url)) - val metaInf = root.resolve("META-INF") - val pluginDescriptorFile = metaInf.resolve("plugin.xml") - val pluginDescriptor = try { - JDOMUtil.load(pluginDescriptorFile) - } - catch (ignore: NoSuchFileException) { - null - } - - val moduleDescriptorFile = root.resolve("${module.name}.xml") - val moduleDescriptor = try { - JDOMUtil.load(moduleDescriptorFile) - } - catch (ignore: NoSuchFileException) { - null - } - - if (Files.exists(metaInf.resolve("${module.name}.xml"))) { - checker.errors.add(PluginValidationError("Module descriptor must be in the root of module root", mapOf( - "module" to module.name, - "moduleDescriptor" to metaInf.resolve("${module.name}.xml"), - ))) - continue - } - - if (pluginDescriptor == null && moduleDescriptor == null) { - continue - } - - val item = moduleNameToFileInfo.computeIfAbsent(module.name) { ModuleDescriptorPath() } - if (item.pluginDescriptorFile != null && pluginDescriptor != null) { - checker.errors.add(PluginValidationError("Duplicated plugin.xml", mapOf( - "module" to module.name, - "firstPluginDescriptor" to item.pluginDescriptorFile, - "secondPluginDescriptor" to pluginDescriptorFile, - ))) - continue - } - if (item.pluginDescriptorFile != null && moduleDescriptor != null) { - checker.errors.add(PluginValidationError("Module cannot have both plugin.xml and module descriptor", mapOf( - "module" to module.name, - "pluginDescriptor" to item.pluginDescriptorFile, - "moduleDescriptor" to moduleDescriptorFile, - ))) - continue - } - if (item.moduleDescriptorFile != null && pluginDescriptor != null) { - checker.errors.add(PluginValidationError("Module cannot have both plugin.xml and module descriptor", mapOf( - "module" to module.name, - "pluginDescriptor" to pluginDescriptorFile, - "moduleDescriptor" to item.moduleDescriptorFile, - ))) - continue - } - - if (pluginDescriptor == null) { - item.moduleDescriptorFile = moduleDescriptorFile - item.moduleDescriptor = moduleDescriptor - } - else { - item.pluginDescriptorFile = pluginDescriptorFile - item.pluginDescriptor = pluginDescriptor - } - } - } - return moduleNameToFileInfo -} - -private fun printGraph(checker: PluginModelChecker) { - val stringWriter = StringWriter() - val writer = JsonFactory().createGenerator(stringWriter) - writer.useDefaultPrettyPrinter() - val graph = checker.graph - writer.use { - writer.obj { - val names = graph.keys.toTypedArray() - names.sort() - for (name in names) { - writer.obj(name) { - val item = graph.get(name)!! - writer.writeStringField("descriptor", homePath.relativize(item.descriptorFile).toString()) - writeEntries(item.dependencies, "dependencies", writer) - writeEntries(item.content, "content", writer) - } - } - } - } - - println(stringWriter.buffer) - Files.writeString(Path.of("/tmp/plugin-report.json"), stringWriter.buffer) -} - -private fun writeEntries(items: List, jsonName: String, writer: JsonGenerator) { - if (items.isNotEmpty()) { - writer.array(jsonName) { - for (entry in items) { - writer.obj { - writer.writeStringField("module", entry.moduleName) - writer.writeStringField("modulePackage", entry.packageName) - } - } - } - } -} - -private class PluginModelChecker(modules: List) { - val pluginIdToModules = LinkedHashMap() - val packageToPlugin = HashMap() - - val graph = HashMap() - - val errors = mutableListOf() - - private val nameToModule = modules.associateBy { it.name } - - fun checkDependencies(element: Element, referencingDescriptor: Element, referencingDescriptorFile: Path, pluginInfo: ModuleInfo) { - for (child in element.children) { - if (child.name != "module") { - if (child.name == "plugin") { - if (pluginInfo.isPlugin) { - throw PluginValidationError("Plugin cannot depend on plugin: ${JDOMUtil.write(child)}", - mapOf( - "entry" to child, - "descriptorFile" to referencingDescriptorFile, - )) - } - - // todo check that the referenced plugin exists - continue - } - - if (pluginInfo.isPlugin) { - throw PluginValidationError("Unsupported dependency type: ${child.name}", - mapOf( - "entry" to child, - "descriptorFile" to referencingDescriptorFile, - )) - } - } - - val moduleName = child.getAttributeValue("name") ?: throw PluginValidationError("Module name is not specified") - val module = nameToModule.get(moduleName) ?: throw PluginValidationError("Cannot find module $moduleName") - - if (moduleName == "intellij.platform.commercial.verifier") { - continue - } - - val (descriptor, referencedDescriptorFile) = loadFileInModule(module, - referencingDescriptorFile = referencingDescriptorFile, - child.getAttributeValue("package")!!) - val aPackage = checkPackage(descriptor, referencedDescriptorFile, child) - - pluginInfo.dependencies.add(Reference(moduleName, aPackage)) - - // check that also specified using depends tag - var pluginDependency: String? = null - for (dependsElement in referencingDescriptor.getChildren("depends")) { - if (dependsElement.getAttributeValue("config-file") == "${module.name}.xml") { - pluginDependency = dependsElement.text - break - } - } - - if (pluginDependency == null) { - // - // - // - // - val task = packageToPlugin.get(aPackage) - if (task != null) { - for (dependsElement in referencingDescriptor.getChildren("depends")) { - if (dependsElement.text == task.pluginId) { - pluginDependency = task.pluginId - break - } - } - } - } - - if (pluginDependency == null) { - throw PluginValidationError("Module, that used as dependency, must be also specified in `depends` (during transition period)." + - "\nPlease check that you use correct naming (not arbitrary file name, but exactly as module name plus `.xml` extension).", - mapOf( - "entry" to child, - "referencingDescriptorFile" to referencingDescriptorFile, - "referencedDescriptorFile" to referencedDescriptorFile - )) - } - - var isPluginDependencySpecified = false - for (entryElement in descriptor.getChild("dependencies")?.getChildren("plugin") ?: emptyList()) { - if (entryElement.getAttributeValue("id") == pluginDependency) { - isPluginDependencySpecified = true - break - } - } - - if (!isPluginDependencySpecified) { - throw PluginValidationError("Module, that used as dependency, must specify dependency on some plugin (`dependencies.plugin`)" + - "\n\nProposed fix (add to ${homePath.relativize(referencedDescriptorFile)}):\n\n" + """ - - - - """.trimIndent() + "\n\n", mapOf( - "entry" to child, - "referencingDescriptorFile" to referencingDescriptorFile, - "referencedDescriptorFile" to referencedDescriptorFile - )) - } - - descriptor.getChild("dependencies")?.let { - try { - checkDependencies(it, descriptor, referencedDescriptorFile, - graph.computeIfAbsent(moduleName) { ModuleInfo(referencedDescriptorFile, isPlugin = false) }) - } - catch (e: PluginValidationError) { - errors.add(e) - } - } - } - } - - // for plugin two variants: - // 1) depends + dependency on plugin in a referenced descriptor = optional descriptor. In old format: depends tag - // 2) no depends + no dependency on plugin in a referenced descriptor = directly injected into plugin (separate classloader is not created - // during transition period). In old format: xi:include (e.g. . - fun checkContent(content: Element, - referencingDescriptor: Element, - referencingDescriptorFile: Path, - pluginInfo: ModuleInfo) { - for (child in content.children) { - if (child.name != "module") { - throw PluginValidationError("Unexpected element: ${JDOMUtil.write(child)}") - } - - val moduleName = child.getAttributeValue("name") ?: throw PluginValidationError("Module name is not specified") - val module = nameToModule.get(moduleName) ?: throw PluginValidationError("Cannot find module $moduleName") - - if (moduleName == "intellij.platform.commercial.verifier") { - errors.add(PluginValidationError("intellij.platform.commercial.verifier is not supposed to be used as content of plugin", - mapOf( - "entry" to child, - "referencingDescriptorFile" to referencingDescriptorFile, - ))) - return - } - - val (descriptor, referencedDescriptorFile) = loadFileInModule(module, - referencingDescriptorFile, - child.getAttributeValue("package")!!) - val aPackage = checkPackage(descriptor, referencedDescriptorFile, child) - - pluginInfo.content.add(Reference(moduleName, aPackage)) - - // check that also specified using depends tag - var oldPluginDependencyId: String? = null - for (dependsElement in referencingDescriptor.getChildren("depends")) { - if (dependsElement.getAttributeValue("config-file") == "${module.name}.xml") { - oldPluginDependencyId = dependsElement.text - break - } - } - - var isPluginDependencyDeclared = false - for (entryElement in descriptor.getChild("dependencies")?.getChildren("plugin") ?: emptyList()) { - if (entryElement.getAttributeValue("id") == oldPluginDependencyId) { - isPluginDependencyDeclared = true - break - } - } - - // if dependency specified in a new format in the referenced descriptor, then must be also specified using old depends tag - if (oldPluginDependencyId == null && isPluginDependencyDeclared) { - throw PluginValidationError("Module, that used as plugin content and depends on a plugin, must be also specified in `depends` (during transition period)." + - "\nPlease check that you use correct naming (not arbitrary file name, but exactly as module name plus `.xml` extension).", - mapOf( - "entry" to child, - "referencingDescriptorFile" to referencingDescriptorFile, - "referencedDescriptorFile" to referencedDescriptorFile - )) - } - - // if there is old depends tag, then dependency in a new format in the referenced descriptor must be also declared - if (oldPluginDependencyId != null && !isPluginDependencyDeclared) { - throw PluginValidationError("Module, that used as plugin content and depends on a plugin, must specify dependency on the plugin (`dependencies.plugin`)" + - "\n\nProposed fix (add to ${homePath.relativize(referencedDescriptorFile)}):\n\n" + """ - - - - """.trimIndent() + "\n\n", mapOf( - "entry" to child, - "referencingDescriptorFile" to referencingDescriptorFile, - "referencedDescriptorFile" to referencedDescriptorFile - )) - } - - descriptor.getChild("content")?.let { - try { - checkContent(it, descriptor, referencedDescriptorFile, - graph.computeIfAbsent(moduleName) { ModuleInfo(referencedDescriptorFile, isPlugin = false) }) - } - catch (e: PluginValidationError) { - errors.add(e) - } - } - } - } -} - -private fun checkPackage(descriptor: Element, descriptorFile: Path, child: Element): String { - val aPackage = descriptor.getAttributeValue("package") - ?: throw PluginValidationError("package attribute is not specified", mapOf("descriptorFile" to descriptorFile)) - - if (aPackage != child.getAttributeValue("package")) { - throw PluginValidationError("package doesn't match", mapOf( - "entry" to child, - "referencedDescriptorFile" to descriptorFile, - "packageInDescriptor" to aPackage - )) - } - return aPackage -} - -private class PluginValidationError(message: String) : RuntimeException(message) { - constructor(message: String, params: Map) : this(message + " (\n ${params.entries.joinToString(separator = ",\n ") { "${it.key}=${paramValueToString(it.value)}" }}\n)") - - constructor(message: String, params: Map, fix: String) : this( - message + - " (\n ${params.entries.joinToString(separator = ",\n ") { "${it.key}=${paramValueToString(it.value)}" }}\n)" + - "\n\nProposed fix:\n\n" + fix.trimIndent() + "\n\n" - ) -} - -private fun paramValueToString(value: Any?): String { - return when (value) { - // reformat according to IJ XMl code style - is Element -> JDOMUtil.write(value).replace("\" />", "\"/>") - is Path -> homePath.relativize(value).toString() - else -> value.toString() - } -} - -private fun loadFileInModule(module: JpsModule, - referencingDescriptorFile: Path, - aPackage: String): Pair { - val fileName = "${module.name}.xml" - val roots = module.getSourceRoots(JavaResourceRootType.RESOURCE) + module.getSourceRoots(JavaSourceRootType.SOURCE) - for (sourceRoot in roots) { - try { - val file = Path.of(JpsPathUtil.urlToPath(sourceRoot.url), fileName) - return Pair(JDOMUtil.load(file), file) - } - catch (ignore: NoSuchFileException) { - } - } - - throw PluginValidationError("Module ${module.name} doesn't have descriptor file", - mapOf( - "expectedFile" to fileName, - "referencingDescriptorFile" to referencingDescriptorFile, - ), - """ - Create file $fileName in ${homePath.relativize(Path.of(JpsPathUtil.urlToPath(roots.first().url)))} - with content: - - - - """) -} - +} \ No newline at end of file diff --git a/platform/platform-tests/testSrc/com/intellij/ide/plugins/PluginModelValidator.kt b/platform/platform-tests/testSrc/com/intellij/ide/plugins/PluginModelValidator.kt new file mode 100644 index 000000000000..f928b54f6dab --- /dev/null +++ b/platform/platform-tests/testSrc/com/intellij/ide/plugins/PluginModelValidator.kt @@ -0,0 +1,545 @@ +// 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.fasterxml.jackson.core.JsonFactory +import com.fasterxml.jackson.core.JsonGenerator +import com.intellij.ide.plugins.PluginModelValidator.Companion.homePath +import com.intellij.openapi.application.PathManager +import com.intellij.util.XmlElement +import com.intellij.util.io.jackson.array +import com.intellij.util.io.jackson.obj +import com.intellij.util.readXmlAsModel +import java.io.StringWriter +import java.nio.file.Files +import java.nio.file.NoSuchFileException +import java.nio.file.Path + +internal class PluginModelValidator { + internal interface Module { + val name: String + + fun getSourceRoots(): List + } + + companion object { + internal val homePath by lazy { Path.of(PathManager.getHomePath()) } + } + + private val pluginIdToInfo = LinkedHashMap() + + private val errors = mutableListOf() + + fun getErrors(): List = java.util.List.copyOf(errors) + + fun validate(sourceModules: List): List { + // 1. collect plugin and module file info set + val sourceModuleNameToFileInfo = computeModuleSet(sourceModules, errors) + val moduleNameToInfo = HashMap() + // 2. process plugins - process content to collect modules + for ((sourceModuleName, moduleMetaInfo) in sourceModuleNameToFileInfo) { + // interested only in plugins + val descriptor = moduleMetaInfo.pluginDescriptor ?: continue + val descriptorFile = moduleMetaInfo.pluginDescriptorFile ?: continue + + val id = descriptor.getChild("id")?.content ?: descriptor.getChild("name")?.content + if (id == null) { + errors.add(PluginValidationError("Plugin id is not specified (descriptorFile=${pathToShortString(descriptorFile)})")) + continue + } + + + val moduleInfo = ModuleInfo(pluginId = id, + name = null, + sourceModuleName = sourceModuleName, + descriptorFile = descriptorFile, + packageName = descriptor.getAttributeValue("package"), + descriptor = descriptor) + val prev = pluginIdToInfo.put(id, moduleInfo) + if (prev != null) { + throw PluginValidationError("Duplicated plugin id: $id (prev=$prev, current=$moduleInfo)") + } + + descriptor.getChild("content")?.let { contentElement -> + checkContent(content = contentElement, + referencingModuleInfo = moduleInfo, + sourceModuleNameToFileInfo = sourceModuleNameToFileInfo, + moduleNameToInfo = moduleNameToInfo) + } + } + + // 3. check dependencies - we are aware about all modules now + for (moduleInfo in pluginIdToInfo.values) { + val descriptor = moduleInfo.descriptor + + descriptor.getChild("dependencies")?.let { dependencies -> + checkDependencies(dependencies, moduleInfo, moduleNameToInfo, sourceModuleNameToFileInfo) + } + + // in the end after processing content and dependencies + if (moduleInfo.packageName == null && hasContentOrDependenciesInV2Format(descriptor)) { + // some plugins cannot be yet fully migrated + System.err.println("Plugin ${moduleInfo.pluginId} is not fully migrated: package is not specified" + + " (pluginId=${moduleInfo.pluginId}, descriptor=${pathToShortString(moduleInfo.descriptorFile)})") + } + + for (contentModuleInfo in moduleInfo.content) { + contentModuleInfo.descriptor.getChild("dependencies")?.let { dependencies -> + checkDependencies(dependencies, contentModuleInfo, moduleNameToInfo, sourceModuleNameToFileInfo) + } + } + } + return getErrors() + } + + fun graphAsString(): CharSequence { + val stringWriter = StringWriter() + val writer = JsonFactory().createGenerator(stringWriter) + writer.useDefaultPrettyPrinter() + writer.use { + writer.obj { + val entries = pluginIdToInfo.entries.toMutableList() + entries.sortBy { it.value.sourceModuleName } + for (entry in entries) { + val item = entry.value + if (item.packageName == null && !hasContentOrDependenciesInV2Format(item.descriptor)) { + continue + } + + writer.writeFieldName(item.sourceModuleName) + writeModuleInfo(writer, item) + } + } + } + return stringWriter.buffer + } + + fun printGraph() { + val graphAsString = graphAsString() + println(graphAsString) + System.getProperty("plugin.graph.out")?.let { + val outFile = Path.of(it) + Files.writeString(outFile, "@startjson\n$graphAsString\n@endjson") + } + } + + private fun checkDependencies(element: XmlElement, + referencingModuleInfo: ModuleInfo, + moduleNameToInfo: Map, + sourceModuleNameToFileInfo: Map) { + if (referencingModuleInfo.packageName == null) { + errors.add(PluginValidationError( + "`dependencies` must be specified only for plugin in a new format: package prefix is not specified", + mapOf( + "referencingDescriptorFile" to referencingModuleInfo.descriptorFile, + ))) + } + + for (child in element.children) { + fun getErrorInfo(): Map { + return mapOf( + "entry" to child, + "referencingDescriptorFile" to referencingModuleInfo.descriptorFile, + ) + } + + if (child.name != "module") { + if (child.name == "plugin") { + // todo check that the referenced plugin exists + val id = child.getAttributeValue("id") + if (id == null) { + errors.add(PluginValidationError("Id is not specified for dependency on plugin", getErrorInfo())) + continue + } + + val dependency = pluginIdToInfo.get(id) + if (!id.startsWith("com.intellij.modules.") && dependency == null) { + errors.add(PluginValidationError("Plugin not found: $id", getErrorInfo())) + continue + } + + val ref = Reference(id, isPlugin = true) + assert(!referencingModuleInfo.dependencies.contains(ref)) + referencingModuleInfo.dependencies.add(ref) + continue + } + + if (referencingModuleInfo.isPlugin) { + errors.add(PluginValidationError("Unsupported dependency type: ${child.name}", getErrorInfo())) + continue + } + } + + val moduleName = child.getAttributeValue("name") + if (moduleName == null) { + errors.add(PluginValidationError("Module name is not specified", getErrorInfo())) + continue + } + + if (moduleName == "intellij.platform.commercial.verifier") { + continue + } + + if (child.attributes.size > 1) { + errors.add(PluginValidationError("Unknown attributes: ${child.attributes.entries.filter { it.key != "name" }}", getErrorInfo())) + continue + } + + val moduleInfo = moduleNameToInfo.get(moduleName) + if (moduleInfo == null) { + val moduleDescriptorFileInfo = sourceModuleNameToFileInfo.get(moduleName) + if (moduleDescriptorFileInfo != null) { + if (moduleDescriptorFileInfo.pluginDescriptor != null) { + errors.add(PluginValidationError( + message = "Dependency on plugin must be specified using `plugin` and not `module`", + params = getErrorInfo(), + fix = """ + Change dependency element to: + + + """ + )) + continue + } + } + errors.add(PluginValidationError("Module not found: $moduleName", getErrorInfo())) + continue + } + + referencingModuleInfo.dependencies.add(Reference(moduleName, isPlugin = false)) + + for (dependsElement in referencingModuleInfo.descriptor.children) { + if (dependsElement.name != "depends") { + continue + } + + if (dependsElement.getAttributeValue("config-file")?.removePrefix("/META-INF/") == moduleInfo.descriptorFile.fileName.toString()) { + errors.add(PluginValidationError("Module, that used as dependency, must be not specified in `depends`", getErrorInfo())) + break + } + } + } + } + + // for plugin two variants: + // 1) depends + dependency on plugin in a referenced descriptor = optional descriptor. In old format: depends tag + // 2) no depends + no dependency on plugin in a referenced descriptor = directly injected into plugin (separate classloader is not created + // during transition period). In old format: xi:include (e.g. . + private fun checkContent(content: XmlElement, + referencingModuleInfo: ModuleInfo, + sourceModuleNameToFileInfo: Map, + moduleNameToInfo: MutableMap) { + for (child in content.children) { + fun getErrorInfo(): Map { + return mapOf( + "entry" to child, + "referencingDescriptorFile" to referencingModuleInfo.descriptorFile, + ) + } + + if (child.name != "module") { + errors.add(PluginValidationError("Unexpected element: $child", getErrorInfo())) + continue + } + + val moduleName = child.getAttributeValue("name") + if (moduleName == null) { + errors.add(PluginValidationError("Module name is not specified", getErrorInfo())) + continue + } + if (child.attributes.size > 1) { + errors.add(PluginValidationError("Unknown attributes: ${child.attributes.entries.filter { it.key != "name" }}", getErrorInfo())) + continue + } + + if (moduleName == "intellij.platform.commercial.verifier") { + errors.add(PluginValidationError("intellij.platform.commercial.verifier is not supposed to be used as content of plugin", + getErrorInfo())) + continue + } + + // ignore null - getModule reports error + val moduleDescriptorFileInfo = getModuleDescriptorFileInfo(moduleName, referencingModuleInfo, sourceModuleNameToFileInfo) ?: continue + + val moduleDescriptor = moduleDescriptorFileInfo.moduleDescriptor!! + val aPackage = moduleDescriptor.getAttributeValue("package") + if (aPackage == null) { + errors.add(PluginValidationError("Module package is not specified", mapOf( + "descriptorFile" to moduleDescriptorFileInfo.moduleDescriptorFile!!, + ))) + continue + } + + val moduleInfo = ModuleInfo(pluginId = null, + name = moduleName, + sourceModuleName = moduleDescriptorFileInfo.sourceModule.name, + descriptorFile = moduleDescriptorFileInfo.moduleDescriptorFile!!, + packageName = aPackage, + descriptor = moduleDescriptor) + moduleNameToInfo.put(moduleName, moduleInfo) + referencingModuleInfo.content.add(moduleInfo) + + // check that not specified using depends tag + for (dependsElement in referencingModuleInfo.descriptor.children) { + if (dependsElement.name != "depends") { + continue + } + + if (dependsElement.getAttributeValue("config-file")?.removePrefix("/META-INF/") == moduleInfo.descriptorFile.fileName.toString()) { + errors.add(PluginValidationError( + "Module must be not specified in `depends`.", + getErrorInfo() + mapOf( + "referencedDescriptorFile" to moduleInfo.descriptorFile + ))) + continue + } + } + + moduleDescriptor.getChild("content")?.let { + errors.add(PluginValidationError("Module cannot define content", getErrorInfo() + mapOf( + "referencedDescriptorFile" to moduleInfo.descriptorFile + ))) + } + } + } + + private fun getModuleDescriptorFileInfo(moduleName: String, + referencingModuleInfo: ModuleInfo, + sourceModuleNameToFileInfo: Map): ModuleDescriptorFileInfo? { + var module = sourceModuleNameToFileInfo.get(moduleName) + if (module != null) { + return module + } + + fun getErrorInfo(): Map { + return mapOf( + "referencingDescriptorFile" to referencingModuleInfo.descriptorFile + ) + } + + val prefix = "${referencingModuleInfo.sourceModuleName}/" + if (!moduleName.startsWith(prefix)) { + errors.add(PluginValidationError("Cannot find module $moduleName", getErrorInfo())) + return null + } + + val slashIndex = prefix.length - 1 + val containingModuleName = moduleName.substring(0, slashIndex) + module = sourceModuleNameToFileInfo.get(containingModuleName) + if (module == null) { + errors.add(PluginValidationError("Cannot find module $containingModuleName", getErrorInfo())) + return null + } + + val fileName = "$containingModuleName.${moduleName.substring(slashIndex + 1)}.xml" + val result = loadFileInModule(sourceModule = module.sourceModule, fileName = fileName) + if (result == null) { + errors.add(PluginValidationError( + message = "Module ${module.sourceModule.name} doesn't have descriptor file", + params = mapOf( + "expectedFile" to fileName, + "referencingDescriptorFile" to referencingModuleInfo.descriptorFile, + ), + fix = """ + Create file $fileName in ${pathToShortString(module.sourceModule.getSourceRoots().first())} + with content: + + + + """ + )) + } + return result + } +} + +internal data class ModuleInfo( + val pluginId: String?, + val name: String?, + val sourceModuleName: String, + val descriptorFile: Path, + val packageName: String?, + + val descriptor: XmlElement, +) { + val content = mutableListOf() + val dependencies = mutableListOf() + + val isPlugin: Boolean + get() = pluginId != null +} + +internal data class Reference(val name: String, val isPlugin: Boolean) + +private data class PluginInfo(val pluginId: String, + val sourceModuleName: String, + val descriptor: XmlElement, + val descriptorFile: Path) + +private class ModuleDescriptorFileInfo(val sourceModule: PluginModelValidator.Module) { + var pluginDescriptorFile: Path? = null + var moduleDescriptorFile: Path? = null + + var pluginDescriptor: XmlElement? = null + var moduleDescriptor: XmlElement? = null +} + +private fun computeModuleSet(sourceModules: List, + errors: MutableList): LinkedHashMap { + val sourceModuleNameToFileInfo = LinkedHashMap() + for (module in sourceModules) { + // platform/cwm-plugin/resources/META-INF/plugin.xml doesn't have id - ignore for now + if (module.name.startsWith("fleet.") || + module.name == "fleet" || + module.name == "intellij.idea.ultimate.resources" || + // https://youtrack.jetbrains.com/issue/IDEA-261850 + module.name == "intellij.indexing.shared.ultimate.plugin.internal.generator" || + module.name == "intellij.indexing.shared.ultimate.plugin.public" || + module.name == "kotlin-ultimate.mobile-native.overrides" || + module.name == "kotlin-ultimate.appcode-with-mobile" || + module.name == "intellij.javaFX.community" || + module.name == "intellij.lightEdit" || + module.name == "intellij.webstorm" || + module.name == "intellij.cwm.plugin") { + continue + } + + for (sourceRoot in module.getSourceRoots()) { + val metaInf = sourceRoot.resolve("META-INF") + val pluginDescriptorFile = metaInf.resolve("plugin.xml") + val pluginDescriptor = try { + readXmlAsModel(Files.newInputStream(pluginDescriptorFile)) + } + catch (ignore: NoSuchFileException) { + null + } + + val moduleDescriptorFile = sourceRoot.resolve("${module.name}.xml") + val moduleDescriptor = try { + readXmlAsModel(Files.newInputStream(moduleDescriptorFile)) + } + catch (ignore: NoSuchFileException) { + null + } + + if (Files.exists(metaInf.resolve("${module.name}.xml"))) { + errors.add(PluginValidationError("Module descriptor must be in the root of module root", mapOf( + "module" to module.name, + "moduleDescriptor" to metaInf.resolve("${module.name}.xml"), + ))) + continue + } + + if (pluginDescriptor == null && moduleDescriptor == null) { + continue + } + + val item = sourceModuleNameToFileInfo.computeIfAbsent(module.name) { ModuleDescriptorFileInfo(module) } + if (item.pluginDescriptorFile != null && pluginDescriptor != null) { + errors.add(PluginValidationError("Duplicated plugin.xml", mapOf( + "module" to module.name, + "firstPluginDescriptor" to item.pluginDescriptorFile, + "secondPluginDescriptor" to pluginDescriptorFile, + ))) + continue + } + + if (item.pluginDescriptorFile != null && moduleDescriptor != null) { + errors.add(PluginValidationError("Module cannot have both plugin.xml and module descriptor", mapOf( + "module" to module.name, + "pluginDescriptor" to item.pluginDescriptorFile, + "moduleDescriptor" to moduleDescriptorFile, + ))) + continue + } + + if (item.moduleDescriptorFile != null && pluginDescriptor != null) { + errors.add(PluginValidationError("Module cannot have both plugin.xml and module descriptor", mapOf( + "module" to module.name, + "pluginDescriptor" to pluginDescriptorFile, + "moduleDescriptor" to item.moduleDescriptorFile, + ))) + continue + } + + if (pluginDescriptor == null) { + item.moduleDescriptorFile = moduleDescriptorFile + item.moduleDescriptor = moduleDescriptor + } + else { + item.pluginDescriptorFile = pluginDescriptorFile + item.pluginDescriptor = pluginDescriptor + } + } + } + return sourceModuleNameToFileInfo +} + +private fun writeModuleInfo(writer: JsonGenerator, item: ModuleInfo) { + writer.obj { + writer.writeStringField("name", item.name ?: item.sourceModuleName) + writer.writeStringField("package", item.packageName) + writer.writeStringField("descriptor", pathToShortString(item.descriptorFile)) + if (!item.content.isEmpty()) { + writer.array("content") { + for (child in item.content) { + writeModuleInfo(writer, child) + } + } + } + + if (!item.dependencies.isEmpty()) { + writer.array("dependencies") { + writeDependencies(item.dependencies, writer) + } + } + } +} + +private fun pathToShortString(file: Path): String { + return if (homePath.fileSystem === file.fileSystem) homePath.relativize(file).toString() else file.toString() +} + +private fun writeDependencies(items: List, writer: JsonGenerator) { + for (entry in items) { + writer.obj { + writer.writeStringField(if (entry.isPlugin) "plugin" else "module", entry.name) + } + } +} + +private class PluginValidationError(message: String) : RuntimeException(message) { + constructor(message: String, params: Map) : + this(message + " (\n ${params.entries.joinToString(separator = ",\n ") { "${it.key}=${paramValueToString(it.value)}" }}\n)") + + constructor(message: String, params: Map, fix: String) : this( + message + + " (\n ${params.entries.joinToString(separator = ",\n ") { "${it.key}=${paramValueToString(it.value)}" }}\n)" + + "\n\nProposed fix:\n\n" + fix.trimIndent() + "\n\n" + ) +} + +private fun paramValueToString(value: Any?): String { + return when (value) { + is Path -> pathToShortString(value) + else -> value.toString() + } +} + +private fun loadFileInModule(sourceModule: PluginModelValidator.Module, fileName: String): ModuleDescriptorFileInfo? { + for (sourceRoot in sourceModule.getSourceRoots()) { + try { + val file = sourceRoot.resolve(fileName) + val info = ModuleDescriptorFileInfo(sourceModule) + info.moduleDescriptor = readXmlAsModel(Files.newInputStream(file)) + info.moduleDescriptorFile = file + return info + } + catch (ignore: NoSuchFileException) { + } + } + return null +} + +private fun hasContentOrDependenciesInV2Format(descriptor: XmlElement): Boolean { + return descriptor.children.any { it.name == "content" || it.name == "dependencies" } +} \ No newline at end of file diff --git a/platform/platform-tests/testSrc/com/intellij/ide/plugins/PluginModelValidatorTest.kt b/platform/platform-tests/testSrc/com/intellij/ide/plugins/PluginModelValidatorTest.kt new file mode 100644 index 000000000000..7c5b8f7027b8 --- /dev/null +++ b/platform/platform-tests/testSrc/com/intellij/ide/plugins/PluginModelValidatorTest.kt @@ -0,0 +1,145 @@ +// 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.testFramework.PlatformTestUtil +import com.intellij.testFramework.TestDataPath +import com.intellij.testFramework.assertions.Assertions.assertThat +import com.intellij.testFramework.assertions.CleanupSnapshots +import com.intellij.testFramework.rules.InMemoryFsRule +import com.intellij.util.getErrorsAsString +import com.intellij.util.io.sanitizeFileName +import com.intellij.util.io.write +import org.intellij.lang.annotations.Language +import org.junit.ClassRule +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TestName +import java.nio.file.Path + +private val testSnapshotDir = Path.of(PlatformTestUtil.getCommunityPath(), "platform/platform-tests/testSnapshots/plugin-validator") + +@TestDataPath("\$CONTENT_ROOT/testSnapshots/plugin-validator") +class PluginModelValidatorTest { + @Rule @JvmField val inMemoryFs = InMemoryFsRule() + @Rule @JvmField val testName = TestName() + + private val snapshot: Path + get() = testSnapshotDir.resolve("${sanitizeFileName(testName.methodName)}.json") + + companion object { + @ClassRule @JvmField val cleanupSnapshots = CleanupSnapshots(testSnapshotDir) + } + + @Test + fun `dependency on a plugin in a new format must be in a plugin with package prefix`() { + val root = inMemoryFs.fs.getPath("/") + val modules = produceDependencyAndDependentPlugins(root) { it.replace(" package=\"dependentPackagePrefix\"", "") } + val validator = PluginModelValidator() + validator.validate(modules) + assertThatErrorsToMatchSnapshot(validator) + } + + @Test + fun `dependency on a plugin is specified as a plugin`() { + val root = inMemoryFs.fs.getPath("/") + val modules = produceDependencyAndDependentPlugins(root) { it } + val validator = PluginModelValidator() + assertThat(validator.validate(modules)).isEmpty() + assertThat(validator.graphAsString()).toMatchSnapshot(snapshot) + } + + @Test + fun `dependency on a plugin must be specified as a plugin`() { + val root = inMemoryFs.fs.getPath("/") + val modules = produceDependencyAndDependentPlugins(root) { it.replace("", "") } + + val validator = PluginModelValidator() + validator.validate(modules) + assertThatErrorsToMatchSnapshot(validator) + } + + @Test + fun `dependency on a plugin must be resolvable`() { + val root = inMemoryFs.fs.getPath("/") + val modules = produceDependencyAndDependentPlugins(root) { it.replace("", "") } + + val validator = PluginModelValidator() + validator.validate(modules) + assertThatErrorsToMatchSnapshot(validator) + } + + @Test + fun `content module in the same source module`() { + val modules = producePluginWithContentModuleInTheSameSourceModule(inMemoryFs.fs.getPath("/")) { it } + val validator = PluginModelValidator() + validator.validate(modules) + assertThat(validator.getErrors()).isEmpty() + assertThat(validator.graphAsString()).toMatchSnapshot(snapshot) + } + + @Test + fun `validate dependencies of content module in the same source module`() { + val modules = producePluginWithContentModuleInTheSameSourceModule(inMemoryFs.fs.getPath("/")) { + it.replace("", "") + } + + val validator = PluginModelValidator() + validator.validate(modules) + assertThatErrorsToMatchSnapshot(validator) + } + + private fun producePluginWithContentModuleInTheSameSourceModule(root: Path, + mutator: (String) -> String): List { + val moduleRoot = root.resolve("intellij.angularJs") + val module = writePluginXml("intellij.angularJs", moduleRoot, """ + + + AngularJs + + + + + """) + + moduleRoot.resolve("intellij.angularJs.diagram.xml").write(mutator(""" + + + + + """)) + return listOf(module) + } + + private fun produceDependencyAndDependentPlugins(root: Path, mutator: (String) -> String): List { + val modules = mutableListOf() + modules.add(writePluginXml("intellij.dependency", root.resolve("dependency"), """ + + + dependency + + """)) + + modules.add(writePluginXml("intellij.dependent", root.resolve("dependent"), mutator(""" + + dependent + + + + + """))) + return modules + } + + private fun assertThatErrorsToMatchSnapshot(validator: PluginModelValidator) { + assertThat(getErrorsAsString(validator.getErrors(), includeStackTrace = false)).toMatchSnapshot(snapshot) + } +} + +private fun writePluginXml(name: String, root: Path, @Language("xml") content: String): PluginModelValidator.Module { + root.resolve("META-INF/plugin.xml").write(content.trimIndent()) + return TestModule(name, listOf(root)) +} + +private class TestModule(override val name: String, private val sourceRoots: List) : PluginModelValidator.Module { + override fun getSourceRoots() = sourceRoots +} \ No newline at end of file diff --git a/platform/projectModel-api/src/com/intellij/openapi/project/ProjectType.java b/platform/projectModel-api/src/com/intellij/openapi/project/ProjectType.java index 9264a88d78ad..d3096e74957f 100644 --- a/platform/projectModel-api/src/com/intellij/openapi/project/ProjectType.java +++ b/platform/projectModel-api/src/com/intellij/openapi/project/ProjectType.java @@ -1,18 +1,4 @@ -/* - * Copyright 2000-2014 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +// 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.project; import org.jetbrains.annotations.NotNull; @@ -23,7 +9,7 @@ import java.util.Objects; /** * @author Dmitry Avdeev */ -public class ProjectType { +public final class ProjectType { private @Nullable String id; diff --git a/platform/service-container/src/com/intellij/serviceContainer/ComponentManagerImpl.kt b/platform/service-container/src/com/intellij/serviceContainer/ComponentManagerImpl.kt index bdf9354f3e17..7f64c4150e5e 100644 --- a/platform/service-container/src/com/intellij/serviceContainer/ComponentManagerImpl.kt +++ b/platform/service-container/src/com/intellij/serviceContainer/ComponentManagerImpl.kt @@ -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. @file:Suppress("DeprecatedCallableAddReplaceWith", "ReplaceNegatedIsEmptyWithIsNotEmpty") package com.intellij.serviceContainer @@ -110,7 +110,10 @@ abstract class ComponentManagerImpl @JvmOverloads constructor(internal val paren mainContainerDescriptor: ContainerDescriptor, componentManager: ComponentManagerImpl, crossinline task: (IdeaPluginDescriptorImpl, ContainerDescriptor) -> Unit) { - executeRegisterTask(mainPluginDescriptor, mainContainerDescriptor, componentManager::getContainerDescriptor, task) + task(mainPluginDescriptor, mainContainerDescriptor) + executeRegisterTaskForContent(mainPluginDescriptor) { + task(it, componentManager.getContainerDescriptor(it)) + } } } diff --git a/platform/service-container/src/com/intellij/serviceContainer/containerUtil.kt b/platform/service-container/src/com/intellij/serviceContainer/containerUtil.kt index 3922ebd8d3bf..9ee8b70d6473 100644 --- a/platform/service-container/src/com/intellij/serviceContainer/containerUtil.kt +++ b/platform/service-container/src/com/intellij/serviceContainer/containerUtil.kt @@ -1,7 +1,7 @@ -// 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. +@file:ApiStatus.Internal package com.intellij.serviceContainer -import com.intellij.ide.plugins.ContainerDescriptor import com.intellij.ide.plugins.IdeaPluginDescriptor import com.intellij.ide.plugins.IdeaPluginDescriptorImpl import com.intellij.ide.plugins.PluginManagerCore @@ -12,6 +12,7 @@ import com.intellij.openapi.extensions.PluginDescriptor import com.intellij.openapi.progress.ProcessCanceledException import com.intellij.openapi.progress.ProgressIndicator import com.intellij.openapi.progress.ProgressManager +import org.jetbrains.annotations.ApiStatus import java.lang.reflect.Modifier internal fun checkCanceledIfNotInClassInit() { @@ -39,31 +40,26 @@ fun precomputeExtensionModel(plugins: List): Precomput val extensionPointDescriptors = ArrayList>() val pluginDescriptors = ArrayList() var extensionPointTotalCount = 0 - val containerSelector: (pluginDescriptor: IdeaPluginDescriptorImpl) -> ContainerDescriptor = { it.moduleContainerDescriptor } val nameToExtensions = HashMap>>>() // step 1 - collect container level extension points - for (plugin in plugins) { - executeRegisterTask(plugin, containerSelector(plugin), containerSelector) { pluginDescriptor, containerDescriptor -> - containerDescriptor.extensionPoints?.let { - extensionPointDescriptors.add(it) - pluginDescriptors.add(pluginDescriptor) - extensionPointTotalCount += it.size + executeRegisterTask(plugins) { pluginDescriptor -> + pluginDescriptor.moduleContainerDescriptor.extensionPoints?.let { + extensionPointDescriptors.add(it) + pluginDescriptors.add(pluginDescriptor) + extensionPointTotalCount += it.size - for (descriptor in it) { - nameToExtensions.put(descriptor.getQualifiedName(pluginDescriptor), mutableListOf()) - } + for (descriptor in it) { + nameToExtensions.put(descriptor.getQualifiedName(pluginDescriptor), mutableListOf()) } } } // step 2 - collect container level extensions - for (plugin in plugins) { - executeRegisterTask(plugin, containerSelector(plugin), containerSelector) { pluginDescriptor, _ -> - val unsortedMap = pluginDescriptor.epNameToExtensions ?: return@executeRegisterTask - for ((name, list) in unsortedMap.entries) { - nameToExtensions.get(name)?.add(pluginDescriptor to list) - } + executeRegisterTask(plugins) { pluginDescriptor -> + val unsortedMap = pluginDescriptor.epNameToExtensions ?: return@executeRegisterTask + for ((name, list) in unsortedMap.entries) { + nameToExtensions.get(name)?.add(pluginDescriptor to list) } } @@ -76,29 +72,40 @@ fun precomputeExtensionModel(plugins: List): Precomput ) } -internal inline fun executeRegisterTask(mainPluginDescriptor: IdeaPluginDescriptorImpl, - mainContainerDescriptor: ContainerDescriptor, - containerSelector: (pluginDescriptor: IdeaPluginDescriptorImpl) -> ContainerDescriptor, - crossinline task: (IdeaPluginDescriptorImpl, ContainerDescriptor) -> Unit) { - task(mainPluginDescriptor, mainContainerDescriptor) +inline fun executeRegisterTask(plugins: List, crossinline task: (IdeaPluginDescriptorImpl) -> Unit) { + for (plugin in plugins) { + task(plugin) + executeRegisterTaskForContent(mainPluginDescriptor = plugin, task = task) + } +} + +@PublishedApi +internal inline fun executeRegisterTaskForContent(mainPluginDescriptor: IdeaPluginDescriptorImpl, + crossinline task: (IdeaPluginDescriptorImpl) -> Unit) { for (dep in mainPluginDescriptor.pluginDependencies) { - if (dep.isDisabledOrBroken) { + val subDescriptor = dep.subDescriptor + if (subDescriptor?.classLoader == null) { continue } - val subPluginDescriptor = dep.subDescriptor ?: continue - task(subPluginDescriptor, containerSelector(subPluginDescriptor)) + task(subDescriptor) - for (subDep in subPluginDescriptor.pluginDependencies) { - if (!subDep.isDisabledOrBroken) { - val d = subDep.subDescriptor ?: continue - task(d, containerSelector(d)) + for (subDep in subDescriptor.pluginDependencies) { + val d = subDep.subDescriptor + if (d?.classLoader != null) { + task(d) assert(d.pluginDependencies.isEmpty() || d.pluginDependencies.all { it.subDescriptor == null }) } } } -} + for (item in mainPluginDescriptor.content.modules) { + val module = item.requireDescriptor() + if (module.classLoader != null) { + task(module) + } + } +} internal fun isGettingServiceAllowedDuringPluginUnloading(descriptor: PluginDescriptor): Boolean { return descriptor.isRequireRestart || diff --git a/platform/testFramework/extensions/src/com/intellij/testFramework/assertions/Assertions.java b/platform/testFramework/extensions/src/com/intellij/testFramework/assertions/Assertions.java index 390aa695693f..1ac698e13c52 100644 --- a/platform/testFramework/extensions/src/com/intellij/testFramework/assertions/Assertions.java +++ b/platform/testFramework/extensions/src/com/intellij/testFramework/assertions/Assertions.java @@ -24,23 +24,23 @@ import java.util.List; @SuppressWarnings("MethodOverridesStaticMethodOfSuperclass") public final class Assertions extends org.assertj.core.api.Assertions { - @NotNull - public static JdomAssert assertThat(@Nullable Element element) { + public static @NotNull JdomAssert assertThat(@Nullable Element element) { return new JdomAssert(element); } - @NotNull - public static PathAssertEx assertThat(@Nullable Path actual) { + public static @NotNull PathAssertEx assertThat(@Nullable Path actual) { return new PathAssertEx(actual); } - @NotNull - public static StringAssertEx assertThat(@Nullable String actual) { + public static @NotNull StringAssertEx assertThat(@Nullable String actual) { return new StringAssertEx(actual); } - @NotNull - public static ListAssertEx assertThat(@Nullable List actual) { + public static @NotNull CharSequenceEx assertThat(@Nullable CharSequence actual) { + return new CharSequenceEx(actual); + } + + public static @NotNull ListAssertEx assertThat(@Nullable List actual) { return new ListAssertEx<>(actual); } } \ No newline at end of file diff --git a/platform/testFramework/extensions/src/com/intellij/testFramework/assertions/StringAssertEx.kt b/platform/testFramework/extensions/src/com/intellij/testFramework/assertions/StringAssertEx.kt index 8bf1b0ec64ca..5a35976d406e 100644 --- a/platform/testFramework/extensions/src/com/intellij/testFramework/assertions/StringAssertEx.kt +++ b/platform/testFramework/extensions/src/com/intellij/testFramework/assertions/StringAssertEx.kt @@ -1,6 +1,7 @@ // Copyright 2000-2019 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. package com.intellij.testFramework.assertions +import org.assertj.core.api.AbstractCharSequenceAssert import org.assertj.core.api.AbstractStringAssert import java.nio.file.Path @@ -10,4 +11,12 @@ class StringAssertEx(actual: String?) : AbstractStringAssert(act isNotNull compareFileContent(actual, snapshotFile) } +} + +class CharSequenceEx(actual: CharSequence?) : AbstractCharSequenceAssert(actual, CharSequenceEx::class.java) { + fun toMatchSnapshot(snapshotFile: Path) { + snapshotFileUsageListeners.forEach { it.beforeMatch(snapshotFile) } + isNotNull + compareFileContent(actual, snapshotFile) + } } \ No newline at end of file diff --git a/platform/testFramework/extensions/src/com/intellij/testFramework/assertions/snapshot.kt b/platform/testFramework/extensions/src/com/intellij/testFramework/assertions/snapshot.kt index 3af227d81cd5..dae172ace9b9 100644 --- a/platform/testFramework/extensions/src/com/intellij/testFramework/assertions/snapshot.kt +++ b/platform/testFramework/extensions/src/com/intellij/testFramework/assertions/snapshot.kt @@ -74,7 +74,7 @@ fun compareFileContent(actual: Any, snapshotFile: Path, updateIfMismatch: Boolea throw e } - println("Write a new snapshot ${snapshotFile.fileName}") + println("Write a new snapshot: ${snapshotFile.fileName}") snapshotFile.write(actualContent) return } @@ -84,8 +84,8 @@ fun compareFileContent(actual: Any, snapshotFile: Path, updateIfMismatch: Boolea } if (updateIfMismatch) { - System.out.println("UPDATED snapshot ${snapshotFile.fileName}") - snapshotFile.write(StringBuilder(actualContent)) + println("UPDATED snapshot ${snapshotFile.fileName}") + snapshotFile.write(actualContent) } else { val firstMismatch = StringUtil.commonPrefixLength(actualContent, expected) diff --git a/platform/testFramework/src/com/intellij/testFramework/TestApplicationManager.kt b/platform/testFramework/src/com/intellij/testFramework/TestApplicationManager.kt index c8e0ce1aaf2a..53d83288220b 100644 --- a/platform/testFramework/src/com/intellij/testFramework/TestApplicationManager.kt +++ b/platform/testFramework/src/com/intellij/testFramework/TestApplicationManager.kt @@ -17,6 +17,7 @@ import com.intellij.ide.impl.HeadlessDataManager import com.intellij.ide.startup.impl.StartupManagerImpl import com.intellij.ide.structureView.StructureViewFactory import com.intellij.ide.structureView.impl.StructureViewFactoryImpl +import com.intellij.idea.StartupUtil import com.intellij.openapi.Disposable import com.intellij.openapi.actionSystem.DataProvider import com.intellij.openapi.application.Application @@ -49,6 +50,7 @@ import com.intellij.util.MemoryDumpHelper import com.intellij.util.ReflectionUtil import com.intellij.util.concurrency.AppExecutorUtil import com.intellij.util.concurrency.AppScheduledExecutorService +import com.intellij.util.lang.Java11Shim import com.intellij.util.ref.GCUtil import com.intellij.util.throwIfNotEmpty import com.intellij.util.ui.UIUtil @@ -68,6 +70,10 @@ import javax.swing.Timer class TestApplicationManager private constructor() { companion object { + init { + Java11Shim.INSTANCE = StartupUtil.Java11ShimImpl() + } + @Volatile private var ourInstance: TestApplicationManager? = null @Volatile @@ -174,7 +180,10 @@ private var testCounter = 0 // Kotlin allows to easily debug code and to get clear and short stack traces @ApiStatus.Internal fun tearDownProjectAndApp(project: Project) { - if (project.isDisposed) return; + if (project.isDisposed) { + return + } + val isLightProject = ProjectManagerImpl.isLight(project) val l = mutableListOf() val app = ApplicationManager.getApplication() diff --git a/platform/util-class-loader/src/com/intellij/util/lang/Java11Shim.java b/platform/util-class-loader/src/com/intellij/util/lang/Java11Shim.java new file mode 100644 index 000000000000..ee37ef8f66b7 --- /dev/null +++ b/platform/util-class-loader/src/com/intellij/util/lang/Java11Shim.java @@ -0,0 +1,36 @@ +// 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. +package com.intellij.util.lang; + +import com.intellij.ReviseWhenPortedToJDK; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; + +import java.util.*; + +@ReviseWhenPortedToJDK("11") +@ApiStatus.Internal +// implementation of `copyOf` is allowed to not do copy - it can return the same map, read `copyOf` as `immutable` +public abstract class Java11Shim { + @SuppressWarnings("StaticNonFinalField") + public static @NotNull Java11Shim INSTANCE = new Java11Shim() { + @Override + public Map copyOf(Map map) { + return Collections.unmodifiableMap(map); + } + + @Override + public Set copyOf(Set collection) { + return Collections.unmodifiableSet(collection); + } + + @Override + public List copyOf(List collection) { + return Collections.unmodifiableList(new ArrayList<>(collection)); + } + }; + + public abstract <@NotNull K, @NotNull V> Map copyOf(Map map); + + public abstract <@NotNull E> Set copyOf(Set collection); + public abstract <@NotNull E> List copyOf(List collection); +} diff --git a/platform/util-ex/src/com/intellij/util/error.kt b/platform/util-ex/src/com/intellij/util/error.kt index 421238a91bfe..cccfe18173b4 100644 --- a/platform/util-ex/src/com/intellij/util/error.kt +++ b/platform/util-ex/src/com/intellij/util/error.kt @@ -2,6 +2,7 @@ package com.intellij.util import com.intellij.util.lang.CompoundRuntimeException +import org.jetbrains.annotations.ApiStatus fun throwIfNotEmpty(errors: List) { val size = errors.size @@ -11,4 +12,22 @@ fun throwIfNotEmpty(errors: List) { else if (size != 0) { throw CompoundRuntimeException(errors) } +} + +@ApiStatus.Internal +fun getErrorsAsString(errors: List, includeStackTrace: Boolean = true): CharSequence { + val sb = StringBuilder() + sb.append("${errors.size} errors:\n") + for (i in errors.indices) { + sb.append("[").append(i + 1).append("]: ------------------------------\n") + val error = errors[i] + val line = if (includeStackTrace) ExceptionUtil.getThrowableText(error) else error.message!! + sb.append(line) + if (!line.endsWith('\n')) { + sb.append('\n') + } + } + sb.append("-".repeat(5)) + sb.append("------------------------------\n") + return sb } \ No newline at end of file diff --git a/platform/util-rt/src/com/intellij/util/io/Murmur3_32Hash.java b/platform/util-rt/src/com/intellij/util/io/Murmur3_32Hash.java index 7d711c3bb842..0394acb0a370 100644 --- a/platform/util-rt/src/com/intellij/util/io/Murmur3_32Hash.java +++ b/platform/util-rt/src/com/intellij/util/io/Murmur3_32Hash.java @@ -81,6 +81,10 @@ public final class Murmur3_32Hash { return fMix(h1, 2 * input.length()); } + public int hashString(CharSequence input) { + return hashString(input, 0, input.length()); + } + public int hashString(CharSequence input, int start, int end) { int h1 = seed; int i = start; diff --git a/platform/util/src/com/intellij/openapi/util/ObjectTree.java b/platform/util/src/com/intellij/openapi/util/ObjectTree.java index cfdcac6b8583..f5ab9c00837c 100644 --- a/platform/util/src/com/intellij/openapi/util/ObjectTree.java +++ b/platform/util/src/com/intellij/openapi/util/ObjectTree.java @@ -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.openapi.util; import com.intellij.openapi.Disposable; @@ -8,7 +8,6 @@ import com.intellij.openapi.util.objectTree.ThrowableInterner; import com.intellij.util.IncorrectOperationException; import com.intellij.util.SmartList; import com.intellij.util.containers.CollectionFactory; -import com.intellij.util.containers.ContainerUtil; import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; import org.jetbrains.annotations.NotNull; @@ -184,17 +183,22 @@ final class ObjectTree { } private static void handleExceptions(@NotNull List exceptions) { - if (!exceptions.isEmpty()) { - for (Throwable exception : exceptions) { - if (!(exception instanceof ProcessCanceledException)) { - getLogger().error(exception); - } - } + if (exceptions.isEmpty()) { + return; + } - ProcessCanceledException pce = ContainerUtil.findInstance(exceptions, ProcessCanceledException.class); - if (pce != null) { - throw pce; + ProcessCanceledException processCanceledException = null; + for (Throwable exception : exceptions) { + if (!(exception instanceof ProcessCanceledException)) { + getLogger().error(exception); } + else if (processCanceledException == null) { + processCanceledException = (ProcessCanceledException)exception; + } + } + + if (processCanceledException != null) { + throw processCanceledException; } } diff --git a/platform/util/src/com/intellij/platform/util/plugins/dataLoader.kt b/platform/util/src/com/intellij/platform/util/plugins/dataLoader.kt index e6a134299415..f4b0ef7f214b 100644 --- a/platform/util/src/com/intellij/platform/util/plugins/dataLoader.kt +++ b/platform/util/src/com/intellij/platform/util/plugins/dataLoader.kt @@ -21,7 +21,7 @@ interface DataLoader { } @ApiStatus.Internal -class LocalFsDataLoader(private val basePath: Path) : DataLoader { +class LocalFsDataLoader(val basePath: Path) : DataLoader { override val pool: ZipFilePool? get() = ZipFilePool.POOL diff --git a/platform/util/src/com/intellij/util/XmlElement.kt b/platform/util/src/com/intellij/util/XmlElement.kt new file mode 100644 index 000000000000..0599ce18e878 --- /dev/null +++ b/platform/util/src/com/intellij/util/XmlElement.kt @@ -0,0 +1,25 @@ +// 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. +package com.intellij.util + +import org.jetbrains.annotations.ApiStatus +import org.jetbrains.annotations.TestOnly + +@ApiStatus.Internal +data class XmlElement( + @JvmField val name: String, + @JvmField val attributes: Map, + @JvmField val children: List, + @JvmField val content: String?, +) { + fun count(name: String): Int = children.count { it.name == name } + + fun getAttributeValue(name: String): String? = attributes.get(name) + + fun getAttributeValue(name: String, defaultValue: String?): String? = attributes.get(name) ?: defaultValue + + fun getChild(name: String): XmlElement? = children.firstOrNull { it.name == name } + + // should not be used - uncomment for migration + @TestOnly + fun getChildren(name: String): List = children.filter { it.name == name } +} \ No newline at end of file diff --git a/platform/util/src/com/intellij/util/lang/CompoundRuntimeException.java b/platform/util/src/com/intellij/util/lang/CompoundRuntimeException.java index dd12289f727d..77c6d0681e55 100644 --- a/platform/util/src/com/intellij/util/lang/CompoundRuntimeException.java +++ b/platform/util/src/com/intellij/util/lang/CompoundRuntimeException.java @@ -13,19 +13,19 @@ import java.util.function.Consumer; import java.util.function.Function; public final class CompoundRuntimeException extends RuntimeException { - private final List myExceptions; + private final List exceptions; public CompoundRuntimeException(@NotNull List throwables) { - myExceptions = throwables; + exceptions = throwables; } @Override public synchronized Throwable getCause() { - return myExceptions.isEmpty() ? null : myExceptions.get(0); + return exceptions.isEmpty() ? null : exceptions.get(0); } public List getExceptions() { - return new ArrayList<>(myExceptions); + return new ArrayList<>(exceptions); } @Override @@ -60,8 +60,8 @@ public final class CompoundRuntimeException extends RuntimeException { } private @NotNull CharSequence processAll(@NotNull Function exceptionProcessor, @Nullable Consumer stringProcessor) { - if (myExceptions.size() == 1) { - Throwable throwable = myExceptions.get(0); + if (exceptions.size() == 1) { + Throwable throwable = exceptions.get(0); String s = exceptionProcessor.apply(throwable); if (stringProcessor != null) { stringProcessor.accept(s); @@ -70,16 +70,16 @@ public final class CompoundRuntimeException extends RuntimeException { } StringBuilder sb = new StringBuilder(); - String line = "CompositeException (" + myExceptions.size() + " nested):\n------------------------------\n"; + String line = "CompositeException (" + exceptions.size() + " nested):\n------------------------------\n"; if (stringProcessor != null) { stringProcessor.accept(line); } + sb.append(line); + for (int i = 0; i < exceptions.size(); i++) { + Throwable exception = exceptions.get(i); - for (int i = 0; i < myExceptions.size(); i++) { - Throwable exception = myExceptions.get(i); - - line = "[" + i + "]: "; + line = "[" + (i + 1) + "]: "; if (stringProcessor != null) { stringProcessor.accept(line); } @@ -103,7 +103,6 @@ public final class CompoundRuntimeException extends RuntimeException { stringProcessor.accept(line); } sb.append(line); - return sb; } diff --git a/platform/util/zip/src/com/intellij/util/lang/ImmutableZipFile.java b/platform/util/zip/src/com/intellij/util/lang/ImmutableZipFile.java index c14e9c72f7fe..ffc476fb58a9 100644 --- a/platform/util/zip/src/com/intellij/util/lang/ImmutableZipFile.java +++ b/platform/util/zip/src/com/intellij/util/lang/ImmutableZipFile.java @@ -89,12 +89,13 @@ public final class ImmutableZipFile implements Closeable { */ @Override public void close() throws IOException { - if (mappedBuffer != null) { + ByteBuffer buffer = mappedBuffer; + if (buffer != null) { + mappedBuffer = null; // we need to unmap buffer immediately without waiting until GC does this job; otherwise further modifications of the created file - // will fail with Acce - unmapBuffer(mappedBuffer); + // will fail with AccessDeniedException + unmapBuffer(buffer); } - mappedBuffer = null; } /** @@ -254,8 +255,10 @@ public final class ImmutableZipFile implements Closeable { /** * This method repeats logic from {@link com.intellij.util.io.ByteBufferUtil#cleanBuffer} which isn't accessible from this module */ - private static void unmapBuffer(ByteBuffer buffer) throws IOException { - if (!buffer.isDirect()) return; + private static void unmapBuffer(@NotNull ByteBuffer buffer) throws IOException { + if (!buffer.isDirect()) { + return; + } try { Field unsafeField = Class.forName("sun.misc.Unsafe").getDeclaredField("theUnsafe"); diff --git a/plugins/devkit/devkit-core/src/dom/ContentDescriptor.java b/plugins/devkit/devkit-core/src/dom/ContentDescriptor.java index c6392c9cb31c..c94184219bee 100644 --- a/plugins/devkit/devkit-core/src/dom/ContentDescriptor.java +++ b/plugins/devkit/devkit-core/src/dom/ContentDescriptor.java @@ -1,19 +1,16 @@ -// 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.idea.devkit.dom; import com.intellij.ide.presentation.Presentation; -import com.intellij.psi.PsiPackage; import com.intellij.util.xml.*; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.idea.devkit.dom.impl.ModuleDescriptorNameConverter; -import org.jetbrains.idea.devkit.dom.impl.ModuleDescriptorPackageConverter; import java.util.List; @ApiStatus.Experimental public interface ContentDescriptor extends DomElement { - @NotNull @Stubbed @SubTagList("module") @@ -24,19 +21,11 @@ public interface ContentDescriptor extends DomElement { @Presentation(icon = "AllIcons.Nodes.Module") interface ModuleDescriptor extends DomElement { - @NotNull @Required @Stubbed @NameValue(referencable = false) @Convert(ModuleDescriptorNameConverter.class) GenericAttributeValue getName(); - - @NotNull - @Required - @Stubbed - @NameValue(referencable = false) - @Convert(ModuleDescriptorPackageConverter.class) - GenericAttributeValue getPackage(); } } diff --git a/plugins/devkit/devkit-core/src/inspections/PluginXmlDomInspection.java b/plugins/devkit/devkit-core/src/inspections/PluginXmlDomInspection.java index e711f0f493fa..9db3ebd6705b 100644 --- a/plugins/devkit/devkit-core/src/inspections/PluginXmlDomInspection.java +++ b/plugins/devkit/devkit-core/src/inspections/PluginXmlDomInspection.java @@ -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.idea.devkit.inspections; import com.intellij.ExtensionPoints; @@ -274,14 +274,6 @@ public final class PluginXmlDomInspection extends DevKitPluginXmlInspectionBase private static void annotateModuleDescriptor(ContentDescriptor.ModuleDescriptor descriptor, DomElementAnnotationHolder holder) { final IdeaPlugin ideaPlugin = descriptor.getName().getValue(); if (ideaPlugin == null) return; - - final String moduleDescriptorPackage = ideaPlugin.getPackage().getStringValue(); - final String descriptorPackage = descriptor.getPackage().getStringValue(); - if (!Comparing.strEqual(moduleDescriptorPackage, descriptorPackage)) { - holder.createProblem(descriptor.getPackage(), - DevKitBundle.message("inspections.plugin.xml.module.descriptor.package.does.not.match", - descriptorPackage, moduleDescriptorPackage, descriptor.getName().getStringValue())); - } } private static boolean isIdeaProjectOrJetBrains(DomElement element) { diff --git a/plugins/grazie/java/src/main/resources/intellij.grazie.java.xml b/plugins/grazie/java/src/main/resources/intellij.grazie.java.xml index 4e94d3fa7e63..96dee7e78e0c 100644 --- a/plugins/grazie/java/src/main/resources/intellij.grazie.java.xml +++ b/plugins/grazie/java/src/main/resources/intellij.grazie.java.xml @@ -1,6 +1,6 @@ - + diff --git a/plugins/grazie/resources/META-INF/plugin.xml b/plugins/grazie/resources/META-INF/plugin.xml index a04fbb2cdc22..be195ab785b9 100644 --- a/plugins/grazie/resources/META-INF/plugin.xml +++ b/plugins/grazie/resources/META-INF/plugin.xml @@ -20,21 +20,14 @@ ]]> - - - - - - + + + + + + - com.intellij.modules.java - com.intellij.modules.json - org.intellij.plugins.markdown - com.intellij.properties - com.intellij.modules.xml - org.jetbrains.plugins.yaml - diff --git a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/PluginStartupActivity.kt b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/PluginStartupActivity.kt index 8431e5086d8e..7b12ea62a741 100644 --- a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/PluginStartupActivity.kt +++ b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/PluginStartupActivity.kt @@ -24,7 +24,7 @@ import org.jetbrains.kotlin.resolve.jvm.diagnostics.ErrorsJvm import org.jetbrains.kotlin.resolve.konan.diagnostics.ErrorsNative import java.util.concurrent.Callable -class PluginStartupActivity : StartupActivity.Background { +internal class PluginStartupActivity : StartupActivity.Background { override fun runActivity(project: Project) { val startupService = PluginStartupService.getInstance(project) diff --git a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/configuration/ui/notifications/NewCodeStyleNotification.kt b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/configuration/ui/notifications/NewCodeStyleNotification.kt index 1cec415dc32c..6d7c26db1b55 100644 --- a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/configuration/ui/notifications/NewCodeStyleNotification.kt +++ b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/configuration/ui/notifications/NewCodeStyleNotification.kt @@ -11,11 +11,10 @@ import org.jetbrains.kotlin.idea.KotlinBundle import org.jetbrains.kotlin.idea.formatter.KotlinStyleGuideCodeStyle import org.jetbrains.kotlin.idea.formatter.ProjectCodeStyleImporter import org.jetbrains.kotlin.idea.formatter.kotlinCodeStyleDefaults -import org.jetbrains.kotlin.idea.search.containsKotlinFile private const val KOTLIN_UPDATE_CODE_STYLE_GROUP_ID = "Update Kotlin code style" -fun notifyKotlinStyleUpdateIfNeeded(project: Project) { +internal fun notifyKotlinStyleUpdateIfNeeded(project: Project) { if (CodeStyle.getSettings(project).kotlinCodeStyleDefaults() == KotlinStyleGuideCodeStyle.CODE_STYLE_ID) return if (SuppressKotlinCodeStyleComponent.getInstance(project).state.disableForAll) { return @@ -58,8 +57,8 @@ class SuppressKotlinCodeStyleState : BaseState() { var disableForAll by property(false) } -@Service -@State(name = "SuppressKotlinCodeStyleNotification") +@Service(Service.Level.PROJECT) +@State(name = "SuppressKotlinCodeStyleNotification", storages = [Storage(StoragePathMacros.CACHE_FILE)]) class SuppressKotlinCodeStyleComponent : SimplePersistentStateComponent(SuppressKotlinCodeStyleState()) { companion object { fun getInstance(project: Project): SuppressKotlinCodeStyleComponent = project.service() diff --git a/plugins/markdown/src/org/intellij/plugins/markdown/fileActions/export/MarkdownHtmlExportProvider.kt b/plugins/markdown/src/org/intellij/plugins/markdown/fileActions/export/MarkdownHtmlExportProvider.kt index 6a554d6cc611..8b1261fcaf13 100644 --- a/plugins/markdown/src/org/intellij/plugins/markdown/fileActions/export/MarkdownHtmlExportProvider.kt +++ b/plugins/markdown/src/org/intellij/plugins/markdown/fileActions/export/MarkdownHtmlExportProvider.kt @@ -1,6 +1,7 @@ // 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. package org.intellij.plugins.markdown.fileActions.export +import com.intellij.openapi.components.service import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory import com.intellij.openapi.project.Project import com.intellij.openapi.ui.TextComponentAccessor @@ -42,7 +43,7 @@ internal class MarkdownHtmlExportProvider : MarkdownExportProvider { val htmlPanel = preview.getUserData(MarkdownPreviewFileEditor.PREVIEW_BROWSER) ?: return if (htmlPanel is MarkdownJCEFHtmlPanel) { - htmlPanel.saveHtml(outputFile, exportSettings.getResourceSavingSettings(), project) { path, ok -> + htmlPanel.saveHtml(outputFile, service().getResourceSavingSettings(), project) { path, ok -> if (ok) { notifyAndRefreshIfExportSuccess(File(path), project) } @@ -83,7 +84,7 @@ internal class MarkdownHtmlExportProvider : MarkdownExportProvider { .component .apply { toolTipText = MarkdownBundle.message("markdown.export.dialog.checkbox.tooltip") - isSelected = exportSettings.getResourceSavingSettings().isSaved + isSelected = service().getResourceSavingSettings().isSaved } } @@ -96,7 +97,7 @@ internal class MarkdownHtmlExportProvider : MarkdownExportProvider { childComponent.history = resDirRecent } - val savedSettings = exportSettings.getResourceSavingSettings() + val savedSettings = service().getResourceSavingSettings() childComponent.text = savedSettings.resourceDir.ifEmpty { FileUtil.join(suggestedTargetFile.parent, suggestedTargetFile.nameWithoutExtension, "images") } @@ -124,10 +125,9 @@ internal class MarkdownHtmlExportProvider : MarkdownExportProvider { private fun saveSettings(project: Project) { val imageDir = resourceDirField.childComponent.text - exportSettings.apply { - saveResources = saveImagesCheckbox.isSelected - resourceDirectory = imageDir - } + val exportSettings = service() + exportSettings.saveResources = saveImagesCheckbox.isSelected + exportSettings.resourceDirectory = imageDir RecentsManager.getInstance(project).registerRecentEntry(IMAGE_DIR_RESENT_KEYS, imageDir) } @@ -160,7 +160,6 @@ internal class MarkdownHtmlExportProvider : MarkdownExportProvider { companion object { private const val IMAGE_DIR_RESENT_KEYS: @NonNls String = "ImportExportFile.ImageDir.RECENT_KEYS" - private val exportSettings = MarkdownHtmlExportSettings.INSTANCE @JvmStatic val format = MarkdownFileActionFormat("HTML", "html") diff --git a/plugins/markdown/src/org/intellij/plugins/markdown/fileActions/export/MarkdownHtmlExportSettings.kt b/plugins/markdown/src/org/intellij/plugins/markdown/fileActions/export/MarkdownHtmlExportSettings.kt index 65b3db06e58c..f3c6af1bcc37 100644 --- a/plugins/markdown/src/org/intellij/plugins/markdown/fileActions/export/MarkdownHtmlExportSettings.kt +++ b/plugins/markdown/src/org/intellij/plugins/markdown/fileActions/export/MarkdownHtmlExportSettings.kt @@ -1,14 +1,14 @@ // 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. package org.intellij.plugins.markdown.fileActions.export -import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.components.PersistentStateComponent import com.intellij.openapi.components.State import com.intellij.openapi.components.Storage +import com.intellij.openapi.components.StoragePathMacros import com.intellij.util.xmlb.XmlSerializerUtil import org.intellij.plugins.markdown.ui.preview.jcef.HtmlResourceSavingSettings -@State(name = "HtmlExportSettings", storages = [Storage(value = "markdown-export.xml")]) +@State(name = "HtmlExportSettings", storages = [Storage(value = StoragePathMacros.NON_ROAMABLE_FILE)]) internal class MarkdownHtmlExportSettings : PersistentStateComponent { var saveResources: Boolean = false var resourceDirectory: String = "" @@ -20,8 +20,4 @@ internal class MarkdownHtmlExportSettings : PersistentStateComponente.getValue() == connector); + myMultimoduleDirToConnectorMap.entrySet().removeIf(e -> e.getValue() == connector); } } @@ -364,7 +364,7 @@ public final class MavenServerManager implements Disposable { classpath.add(new File(root, "maven-server-api.jar")); if (StringUtil.compareVersionNumbers(mavenVersion, "3") < 0) { - classpath.add(new File(root, "maven2-server-impl.jar")); + classpath.add(new File(root, "maven2-server.jar")); addDir(classpath, new File(root, "maven2-server-lib"), f -> true); } else { @@ -372,12 +372,12 @@ public final class MavenServerManager implements Disposable { addDir(classpath, new File(root, "maven3-server-lib"), f -> true); if (StringUtil.compareVersionNumbers(mavenVersion, "3.1") < 0) { - classpath.add(new File(root, "maven30-server-impl.jar")); + classpath.add(new File(root, "maven30-server.jar")); } else { - classpath.add(new File(root, "maven3-server-impl.jar")); + classpath.add(new File(root, "maven3-server.jar")); if (StringUtil.compareVersionNumbers(mavenVersion, "3.6") >= 0) { - classpath.add(new File(root, "maven36-server-impl.jar")); + classpath.add(new File(root, "maven36-server.jar")); } } } @@ -407,7 +407,6 @@ public final class MavenServerManager implements Disposable { } } - private static void addMavenLibs(List classpath, File mavenHome) { addDir(classpath, new File(mavenHome, "lib"), f -> !f.getName().contains("maven-slf4j-provider")); File bootFolder = new File(mavenHome, "boot"); diff --git a/plugins/yaml/resources/META-INF/plugin.xml b/plugins/yaml/resources/META-INF/plugin.xml index e595e1893d7b..23ac3613a8a0 100644 --- a/plugins/yaml/resources/META-INF/plugin.xml +++ b/plugins/yaml/resources/META-INF/plugin.xml @@ -7,7 +7,9 @@ Adds support for the YAML language. ]]> - com.intellij.modules.lang + + +