Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[jb] add inspections & quickfixes on .gitpod.yml vmoptions config #11089

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
feat(jb): add inspections & quickfixes on .gitpod.yml vmoptions config
  • Loading branch information
yaohui-wyh committed Aug 12, 2022
commit ca44ee0f1685b36ce508cdda6ea2063ffef489a6
Original file line number Diff line number Diff line change
@@ -10,7 +10,7 @@ platformType=IU
platformDownloadSources=true
# Plugin Dependencies -> https://plugins.jetbrains.com/docs/intellij/plugin-dependencies.html
# Example: platformPlugins = com.intellij.java, com.jetbrains.php:203.4449.22
platformPlugins=Git4Idea, org.jetbrains.plugins.terminal, com.jetbrains.codeWithMe
platformPlugins=Git4Idea, org.jetbrains.plugins.terminal, com.jetbrains.codeWithMe, org.jetbrains.plugins.yaml
# Opt-out flag for bundling Kotlin standard library.
# See https://plugins.jetbrains.com/docs/intellij/kotlin.html#kotlin-standard-library for details.
kotlin.stdlib.default.dependency=false
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// Copyright (c) 2022 Gitpod GmbH. All rights reserved.
// Licensed under the GNU Affero General Public License (AGPL).
// See License-AGPL.txt in the project root for license information.

package io.gitpod.jetbrains.remote.inspections

import com.intellij.codeInspection.LocalInspectionTool
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.diagnostic.VMOptions
import com.intellij.openapi.util.BuildNumber
import com.intellij.psi.PsiElementVisitor
import com.intellij.psi.PsiFile
import io.gitpod.jetbrains.remote.quickfixes.AddVMOptionsQuickFix
import io.gitpod.jetbrains.remote.quickfixes.ApplyVMOptionsQuickFix
import io.gitpod.jetbrains.remote.quickfixes.ReplaceVMOptionsQuickFix
import io.gitpod.jetbrains.remote.utils.GitpodConfig.YamlKey
import io.gitpod.jetbrains.remote.utils.GitpodConfig.defaultXmxMiB
import io.gitpod.jetbrains.remote.utils.GitpodConfig.getJetBrainsProductName
import io.gitpod.jetbrains.remote.utils.GitpodConfig.gitpodYamlFile
import org.jetbrains.yaml.YAMLUtil
import org.jetbrains.yaml.psi.YAMLFile
import org.jetbrains.yaml.psi.YAMLKeyValue

class GitpodConfigInspection : LocalInspectionTool() {

private val runtimeXmxMiB = Runtime.getRuntime().maxMemory().shr(20)

override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
return object : PsiElementVisitor() {
override fun visitFile(file: PsiFile) {
if (file.name != gitpodYamlFile || file !is YAMLFile) return
val productCode = BuildNumber.currentVersion().productCode
val productName = getJetBrainsProductName(productCode) ?: return
val keyValue = YAMLUtil.getQualifiedKeyInFile(file, YamlKey.jetbrains, productName, YamlKey.vmOptions)
if (keyValue == null) {
holder.registerProblem(
file,
"IDE's max heap size (-Xmx) is ${runtimeXmxMiB}m, but not configured in $gitpodYamlFile",
AddVMOptionsQuickFix(productName, runtimeXmxMiB)
)
return
}
val configuredXmxMiB = getUserConfiguredXmxValue(keyValue)
if (configuredXmxMiB == null && runtimeXmxMiB != defaultXmxMiB) {
val description = "IDE's max heap size (-Xmx) is ${runtimeXmxMiB}m, but not configured in $gitpodYamlFile"
val fix = if (VMOptions.canWriteOptions()) {
ApplyVMOptionsQuickFix("Apply default -Xmx${defaultXmxMiB}m", defaultXmxMiB)
} else {
ReplaceVMOptionsQuickFix(runtimeXmxMiB)
}
holder.registerProblem(file, description, fix)
return
}
if (configuredXmxMiB != null && configuredXmxMiB != runtimeXmxMiB) {
val description = "IDE's max heap size (-Xmx) is ${runtimeXmxMiB}m, but -Xmx${configuredXmxMiB}m configured in $gitpodYamlFile"
val fix = if (VMOptions.canWriteOptions()) {
ApplyVMOptionsQuickFix("Apply -Xmx${configuredXmxMiB}m configured in $gitpodYamlFile", runtimeXmxMiB)
} else {
ReplaceVMOptionsQuickFix(runtimeXmxMiB)
}
holder.registerProblem(file, description, fix)
}
}
}
}

private fun getUserConfiguredXmxValue(vmOptionsKeyValue: YAMLKeyValue): Long? {
val vmOptions = vmOptionsKeyValue.valueText.trim().split("\\s".toRegex())
// the rightmost option is the one to take effect
val finalXmx = vmOptions.lastOrNull { it.startsWith("-Xmx") } ?: return null
val xmxValue = finalXmx.substringAfter("-Xmx")
return try {
VMOptions.parseMemoryOption(xmxValue).shr(20)
} catch (e: IllegalArgumentException) {
// ignore invalid user configuration
null
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// Copyright (c) 2022 Gitpod GmbH. All rights reserved.
// Licensed under the GNU Affero General Public License (AGPL).
// See License-AGPL.txt in the project root for license information.

package io.gitpod.jetbrains.remote.quickfixes

import com.intellij.codeInspection.LocalQuickFix
import com.intellij.codeInspection.ProblemDescriptor
import com.intellij.openapi.diagnostic.thisLogger
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiDocumentManager
import com.intellij.psi.PsiElement
import com.intellij.psi.codeStyle.CodeStyleManager
import com.intellij.psi.util.PsiTreeUtil
import com.intellij.util.IncorrectOperationException
import io.gitpod.jetbrains.remote.utils.GitpodConfig.YamlKey
import io.gitpod.jetbrains.remote.utils.GitpodConfig.gitpodYamlFile
import org.jetbrains.yaml.YAMLElementGenerator
import org.jetbrains.yaml.psi.YAMLFile
import org.jetbrains.yaml.psi.YAMLKeyValue

class AddVMOptionsQuickFix(private val productName: String, private val xmxValueMiB: Long) : LocalQuickFix {

override fun getName() = "Add -Xmx${xmxValueMiB}m to $gitpodYamlFile"

override fun getFamilyName() = name

override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
val psiFile = descriptor.psiElement as? YAMLFile ?: return
val document = psiFile.viewProvider.document ?: return
val generator = YAMLElementGenerator.getInstance(project)
val jetbrainsKeyValue = findOrCreateYamlKeyValue(psiFile, YamlKey.jetbrains, "", generator) ?: return
val productKeyValue = findOrCreateYamlKeyValue(jetbrainsKeyValue, productName, "", generator) ?: return
findOrCreateYamlKeyValue(productKeyValue, YamlKey.vmOptions, "-Xmx${xmxValueMiB}m", generator)
PsiDocumentManager.getInstance(project).doPostponedOperationsAndUnblockDocument(document)
try {
CodeStyleManager.getInstance(project).reformat(jetbrainsKeyValue)
} catch (e: IncorrectOperationException) {
thisLogger().warn("AddVMOptionsQuickFix reformat failed", e)
}
}

private fun findOrCreateYamlKeyValue(
parent: PsiElement,
keyText: String,
valueText: String,
generator: YAMLElementGenerator
): PsiElement? {
var element = findElementByYamlKeyText(parent, keyText)
return if (element == null) {
element = generator.createYamlKeyValue(keyText, valueText)
parent.add(generator.createEol())
parent.add(element) ?: return null
} else {
element
}
}

private fun findElementByYamlKeyText(rootElement: PsiElement, keyText: String): PsiElement? {
return PsiTreeUtil.collectElements(rootElement) {
it is YAMLKeyValue && it.keyText == keyText
}.firstOrNull()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright (c) 2022 Gitpod GmbH. All rights reserved.
// Licensed under the GNU Affero General Public License (AGPL).
// See License-AGPL.txt in the project root for license information.

package io.gitpod.jetbrains.remote.quickfixes

import com.intellij.codeInspection.LocalQuickFix
import com.intellij.codeInspection.ProblemDescriptor
import com.intellij.diagnostic.VMOptions
import com.intellij.openapi.project.Project

class ApplyVMOptionsQuickFix(private val quickFixName: String, private val xmxValueMiB: Long) : LocalQuickFix {

override fun getName() = quickFixName

override fun getFamilyName() = name

override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
VMOptions.setOption(VMOptions.MemoryKind.HEAP, xmxValueMiB.toInt())
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Copyright (c) 2022 Gitpod GmbH. All rights reserved.
// Licensed under the GNU Affero General Public License (AGPL).
// See License-AGPL.txt in the project root for license information.

package io.gitpod.jetbrains.remote.quickfixes

import com.intellij.codeInspection.LocalQuickFix
import com.intellij.codeInspection.ProblemDescriptor
import com.intellij.openapi.project.Project
import io.gitpod.jetbrains.remote.utils.GitpodConfig.YamlKey
import org.jetbrains.yaml.YAMLElementGenerator
import org.jetbrains.yaml.psi.YAMLKeyValue

class ReplaceVMOptionsQuickFix(private val xmxValueMiB: Long) : LocalQuickFix {

override fun getName() = "Set Xmx to $xmxValueMiB MiB"

override fun getFamilyName() = name

override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
val vmOptionsKeyValue = descriptor.psiElement as? YAMLKeyValue ?: return
if (vmOptionsKeyValue.keyText != YamlKey.vmOptions) return
val vmOptions = vmOptionsKeyValue.valueText.trim().split("\\s".toRegex())
val xmxUpdated = "-Xmx${xmxValueMiB}m"
val xmxOptions = vmOptions
.filter { it.startsWith("-Xmx") }
.map { xmxUpdated }
.ifEmpty { listOf(xmxUpdated) }
val nonXmxOptions = vmOptions
.filter { !it.startsWith("-Xmx") }
val newVmOptions = (xmxOptions + nonXmxOptions).toSortedSet().joinToString(" ")
val generator = YAMLElementGenerator.getInstance(project)
val psiElementUpdated = generator.createYamlKeyValue(YamlKey.vmOptions, newVmOptions)
vmOptionsKeyValue.replace(psiElementUpdated)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Copyright (c) 2022 Gitpod GmbH. All rights reserved.
// Licensed under the GNU Affero General Public License (AGPL).
// See License-AGPL.txt in the project root for license information.

package io.gitpod.jetbrains.remote.utils

/**
* Constants and util functions for Gitpod config spec
*/
object GitpodConfig {

// FIXME: get from env var
const val defaultXmxMiB = 2048L
const val gitpodYamlFile = ".gitpod.yml"

object YamlKey {
const val jetbrains = "jetbrains"
const val vmOptions = "vmoptions"
}

/**
* map JetBrains IDE productCode to YAML key for .gitpod.yml
*/
fun getJetBrainsProductName(productCode: String): String? {
return when (productCode) {
"IC" -> "intellij"
"IU" -> "intellij"
"PS" -> "phpstorm"
"PY" -> "pycharm"
"GO" -> "goland"
else -> null
}
}
}
Original file line number Diff line number Diff line change
@@ -19,6 +19,7 @@
<plugin id="Git4Idea"/>
<plugin id="org.jetbrains.plugins.terminal"/>
<plugin id="com.jetbrains.codeWithMe"/>
<plugin id="org.jetbrains.plugins.yaml"/>
</dependencies>

<extensions defaultExtensionNs="com.intellij">
@@ -30,6 +31,12 @@
<projectService serviceImplementation="io.gitpod.jetbrains.remote.GitpodClientProjectSessionTracker" client="guest" preload="true"/>
<projectService serviceImplementation="io.gitpod.jetbrains.remote.GitpodProjectManager" preload="true"/>
<gateway.customization.name implementation="io.gitpod.jetbrains.remote.GitpodGatewayClientCustomizationProvider"/>
<localInspection language="yaml" bundle="messages.GitpodBundle"
groupKey="inspections.group.name"
key="inspections.gitpod.schema.validation.name"
level="WARNING" enabledByDefault="true"
implementationClass="io.gitpod.jetbrains.remote.inspections.GitpodConfigInspection"
/>
</extensions>

</idea-plugin>
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<html>
<body>
Reports invalid or missing -Xmx configs in the Gitpod configuration.
<p><b>Example configuration:</b></p>
<pre><code>
jetbrains:
intellij:
vmoptions: -Xmx4g
</code></pre>
<p>More information: <a href="https://www.gitpod.io/docs/references/gitpod-yml#jetbrainsproductvmoptions">https://www.gitpod.io/docs/references/gitpod-yml#jetbrainsproductvmoptions</a></p>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
inspections.group.name=Gitpod
inspections.gitpod.schema.validation.name=Incorrect -Xmx config