[warmup] IJPL-29: Add stub of API for warm-up

GitOrigin-RevId: f5cae5ea02e0f21d2fdf82a3fbdcc9f6bea6c291
This commit is contained in:
Konstantin Nisht
2022-12-21 14:58:30 +03:00
committed by intellij-monorepo-bot
parent 33c4ae779d
commit 6e8005776f
16 changed files with 264 additions and 44 deletions

View File

@@ -0,0 +1,6 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.openapi.projectRoots.impl
import com.intellij.ide.warmup.WarmupConfigurationOfCLIConfigurator
class UnknownSdkInspectionWarmupConfiguration : WarmupConfigurationOfCLIConfigurator(UnknownSdkInspectionCommandLineConfigurator())

View File

@@ -0,0 +1,47 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.ide.warmup
import com.intellij.openapi.extensions.ExtensionPointName
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.NlsSafe
import org.jetbrains.annotations.ApiStatus
import java.nio.file.Path
/**
* Project warm-up is a set of activities that help to prepare an IDE for being opened in a "ready-to-work" state.
* This class allows to configure different processes that the IDE needs in order to successfully and completely open the required projects.
* Typical examples of the clients of this API are build systems (Maven, Gradle) and SDK detectors (Python SDK, JDK).
*/
@ApiStatus.Experimental
interface WarmupConfiguration {
companion object {
val EP_NAME: ExtensionPointName<WarmupConfiguration> = ExtensionPointName("com.intellij.warmupConfiguration")
}
/**
* A name of the configuration.
* The configurations can be disabled by the user, so they must be referable by some name.
*/
val name: @NlsSafe String
/**
* Called **before** opening the project.
*
* This method helps to configure the behavior of some startup activities,
* i.e. disable auto import of build systems or specify the needed system properties.
*
* @param projectPath a path to the directory passed to warm-up process.
*/
suspend fun prepareEnvironment(projectPath: Path, logger: WarmupEventsLogger) {}
/**
* Called **after** opening the project.
*
* This method actually performs the setting up of the project model.
*
* @return `true` if some globally visible changes to the project have been performed
* or `false` otherwise
*/
suspend fun runWarmup(project: Project, logger: WarmupEventsLogger): Boolean
}

View File

@@ -0,0 +1,12 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.ide.warmup
import com.intellij.openapi.util.NlsSafe
/**
* Helps to pass diagnostic messages to the user of warm-up.
*/
interface WarmupEventsLogger {
fun logError(message: @NlsSafe String)
fun logMessage(verbosityLevel: Int, message: @NlsSafe String)
}

View File

@@ -0,0 +1,95 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.ide.warmup
import com.intellij.ide.CommandLineInspectionProgressReporter
import com.intellij.ide.CommandLineInspectionProjectConfigurator
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.progress.*
import com.intellij.openapi.project.Project
import com.intellij.openapi.project.guessProjectDir
import com.intellij.openapi.vfs.VirtualFile
import org.jetbrains.annotations.ApiStatus
import java.nio.file.Path
import java.util.function.Predicate
import kotlin.coroutines.coroutineContext
/**
* This class is a temporary bridge between old [CommandLineInspectionProjectConfigurator] and new [WarmupConfiguration].
* Please don't use it if you want to configure warmup.
*/
@ApiStatus.Internal
@ApiStatus.Obsolete
abstract class WarmupConfigurationOfCLIConfigurator(val delegate: CommandLineInspectionProjectConfigurator) : WarmupConfiguration {
override suspend fun prepareEnvironment(projectPath: Path, logger: WarmupEventsLogger) =
withRawProgressReporter {
val context = produceConfigurationContext(projectPath, logger)
blockingContext {
delegate.configureEnvironment(context)
}
}
override suspend fun runWarmup(project: Project, logger: WarmupEventsLogger): Boolean =
withRawProgressReporter {
val context = produceConfigurationContext(project.guessProjectDir()?.path?.let(Path::of), logger)
blockingContext {
delegate.configureProject(project, context)
false
}
}
override val name: String
get() = delegate.name
private suspend fun produceConfigurationContext(projectDir: Path?,
logger: WarmupEventsLogger): CommandLineInspectionProjectConfigurator.ConfiguratorContext {
val reporter = coroutineContext.rawProgressReporter
if (reporter == null) {
LOG.warn("No ProgressReporter installed to the coroutine context. Message reporting is disabled")
}
return object : CommandLineInspectionProjectConfigurator.ConfiguratorContext {
override fun getLogger(): CommandLineInspectionProgressReporter = object : CommandLineInspectionProgressReporter {
override fun reportError(message: String?) = message?.let(logger::logError) ?: Unit
override fun reportMessage(minVerboseLevel: Int, message: String?) = message?.let { logger.logMessage(minVerboseLevel, it) } ?: Unit
}
/**
* Copy-pasted from [RawProgressReporterIndicator]. ProgressIndicator will be deprecated,
* so this code should not be here for long (famous last words...).
*/
override fun getProgressIndicator(): ProgressIndicator = object : EmptyProgressIndicator() {
override fun setText(text: String?) {
reporter?.text(text)
}
override fun setText2(text: String?) {
reporter?.details(text)
}
override fun setFraction(fraction: Double) {
reporter?.fraction(fraction)
}
override fun setIndeterminate(indeterminate: Boolean) {
if (indeterminate) {
reporter?.fraction(-1.0)
}
else {
reporter?.fraction(0.0)
}
}
}
override fun getProjectPath(): Path = projectDir ?: error("Something wrong with this project")
override fun getFilesFilter(): Predicate<Path> = Predicate { true }
override fun getVirtualFilesFilter(): Predicate<VirtualFile> = Predicate { true }
}
}
}
private val LOG: Logger = logger<WarmupConfiguration>()

View File

@@ -495,6 +495,7 @@
<extensionPoint name="projectIndexesWarmupSupport" interface="com.intellij.warmup.ProjectIndexesWarmupSupport" area="IDEA_PROJECT" />
<extensionPoint name="projectBuildWarmupSupport" interface="com.intellij.warmup.ProjectBuildWarmupSupport" area="IDEA_PROJECT" />
<extensionPoint name="warmupConfiguration" interface="com.intellij.ide.warmup.WarmupConfiguration" dynamic="true" />
<extensionPoint name="internal.ml.featureProvider" beanClass="com.intellij.lang.LanguageExtensionPoint" dynamic="true">
<with attribute="implementationClass" implements="com.intellij.internal.ml.MLFeatureProvider"/>

View File

@@ -60,6 +60,7 @@
<postStartupActivity implementation="com.intellij.openapi.projectRoots.impl.UnknownSdkStartupChecker"/>
<commandLineInspectionProjectConfigurator implementation="com.intellij.openapi.projectRoots.impl.UnknownSdkInspectionCommandLineConfigurator"/>
<warmupConfiguration implementation="com.intellij.openapi.projectRoots.impl.UnknownSdkInspectionWarmupConfiguration"/>
<registryKey key="unknown.sdk" defaultValue="true" description="Check for unknown SDKs and provide automatic fixes or smart suggestions"/>
<registryKey key="unknown.sdk.auto" defaultValue="true" description="Checks and resolves unknown SDKs automatically on start"/>
<registryKey key="unknown.sdk.modal.jps" defaultValue="true" description="Run unknown JDK test before JPS build is started"/>

View File

@@ -11,6 +11,8 @@
<orderEntry type="module" module-name="intellij.platform.util" />
<orderEntry type="module" module-name="intellij.platform.indexing" />
<orderEntry type="module" module-name="intellij.platform.lang.impl" />
<orderEntry type="module" module-name="intellij.platform.ide" />
<orderEntry type="module" module-name="intellij.platform.core.impl" />
<orderEntry type="library" name="kotlin-stdlib-jdk8" level="project" />
<orderEntry type="library" name="kotlinx-coroutines-jdk8" level="project" />
</component>

View File

@@ -3,6 +3,7 @@ package com.intellij.warmup.util
import com.intellij.application.subscribe
import com.intellij.openapi.progress.ProgressIndicator
import com.intellij.openapi.progress.impl.ProgressState
import com.intellij.openapi.progress.util.ProgressIndicatorBase
import com.intellij.openapi.progress.util.ProgressWindow
import com.intellij.openapi.util.Disposer
@@ -48,21 +49,21 @@ suspend fun <Y> withLoggingProgresses(action: suspend (ProgressIndicator) -> Y):
private fun trimProgressTextAndNullize(s: String?) = s?.trim()?.trimEnd('.', '\u2026', ' ')?.takeIf { it.isNotBlank() }
private fun progressIndicatorText(progressIndicator: ProgressIndicator): String? {
val text = trimProgressTextAndNullize(progressIndicator.text)
val text2 = trimProgressTextAndNullize(progressIndicator.text2)
internal fun progressStateText(state: ProgressState): String? {
val text = trimProgressTextAndNullize(state.text)
val text2 = trimProgressTextAndNullize(state.details)
if (text.isNullOrBlank() && text2.isNullOrBlank()) {
return null
}
val message = (text ?: "") + (text2?.let { " ($it)" } ?: "")
if (message.isBlank() || progressIndicator.isIndeterminate) {
if (message.isBlank() || state.fraction < 0.0) {
return message.takeIf { it.isNotBlank() }
}
val v = (100.0 * progressIndicator.fraction).toInt()
val v = (100.0 * state.fraction).toInt()
val total = 18
val completed = (total * progressIndicator.fraction).toInt().coerceAtLeast(0)
val completed = (total * state.fraction).toInt().coerceAtLeast(0)
val d = ".".repeat(completed).padEnd(total, ' ')
return message.take(75).padEnd(79) + "$d $v%"
}
@@ -90,8 +91,11 @@ private class LoggingProgressIndicator(private val messages: SendChannel<String>
}
private fun offerState() {
messages.trySend(progressIndicatorText(this@LoggingProgressIndicator) ?: return).onClosed {
messages.trySend(progressStateText(this@LoggingProgressIndicator.dumpProgressState()) ?: return).onClosed {
throw IllegalStateException(it)
}
}
}
private fun ProgressIndicator.dumpProgressState() : ProgressState =
ProgressState(text = text, details = text2, fraction = if (isIndeterminate) -1.0 else fraction)

View File

@@ -3,29 +3,26 @@ package com.intellij.warmup.util
import com.intellij.conversion.ConversionListener
import com.intellij.conversion.ConversionService
import com.intellij.ide.CommandLineInspectionProgressReporter
import com.intellij.ide.CommandLineInspectionProjectConfigurator
import com.intellij.ide.CommandLineInspectionProjectConfigurator.ConfiguratorContext
import com.intellij.ide.impl.OpenProjectTask
import com.intellij.ide.impl.PatchProjectUtil
import com.intellij.ide.impl.ProjectUtil
import com.intellij.ide.impl.runUnderModalProgressIfIsEdt
import com.intellij.ide.warmup.WarmupConfiguration
import com.intellij.ide.warmup.WarmupEventsLogger
import com.intellij.openapi.application.EDT
import com.intellij.openapi.application.readAction
import com.intellij.openapi.module.ModuleManager
import com.intellij.openapi.progress.ProgressIndicator
import com.intellij.openapi.progress.durationStep
import com.intellij.openapi.project.Project
import com.intellij.openapi.roots.JdkOrderEntry
import com.intellij.openapi.roots.OrderRootType
import com.intellij.openapi.roots.ProjectRootManager
import com.intellij.openapi.vfs.VfsUtil
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.openapi.vfs.VirtualFileManager
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.nio.file.Path
import java.util.*
import java.util.function.Predicate
private val LOG = ConsoleLog
@@ -34,7 +31,7 @@ fun importOrOpenProject(args: OpenProjectArgs, indicator: ProgressIndicator): Pr
// most of the sensible operations would run in the same thread
return runUnderModalProgressIfIsEdt {
runTaskAndLogTime("open project") {
importOrOpenProjectImpl(args, indicator)
importOrOpenProjectImpl(args)
}
}
}
@@ -43,11 +40,11 @@ suspend fun importOrOpenProjectAsync(args: OpenProjectArgs, indicator: ProgressI
LOG.info("Opening project from ${args.projectDir}...")
// most of the sensible operations would run in the same thread
return runTaskAndLogTime("open project") {
importOrOpenProjectImpl(args, indicator)
importOrOpenProjectImpl(args)
}
}
private suspend fun importOrOpenProjectImpl(args: OpenProjectArgs, indicator: ProgressIndicator): Project {
private suspend fun importOrOpenProjectImpl(args: OpenProjectArgs): Project {
val vfsProject = VirtualFileManager.getInstance().refreshAndFindFileByNioPath(args.projectDir)
?: throw RuntimeException("Project path ${args.projectDir} is not found")
@@ -59,8 +56,8 @@ private suspend fun importOrOpenProjectImpl(args: OpenProjectArgs, indicator: Pr
callProjectConversion(args)
callProjectConfigurators(args, indicator) {
this.configureEnvironment(it)
callProjectConfigurators(args) {
this.prepareEnvironment(args.projectDir, listener)
}
val project = runTaskAndLogTime("open project") {
@@ -77,8 +74,8 @@ private suspend fun importOrOpenProjectImpl(args: OpenProjectArgs, indicator: Pr
yieldAndWaitForDumbModeEnd(project)
callProjectConfigurators(args, indicator) {
this.configureProject(project, it)
callProjectConfigurators(args) {
this.runWarmup(project, listener)
//the configuration may add more dumb tasks to complete
//we flush the queue to avoid a deadlock between a modal progress & invokeLater
@@ -118,12 +115,12 @@ private suspend fun importOrOpenProjectImpl(args: OpenProjectArgs, indicator: Pr
return project
}
private val listener = object : ConversionListener, CommandLineInspectionProgressReporter {
override fun reportError(message: String) {
private val listener = object : ConversionListener, WarmupEventsLogger {
override fun logError(message: String) {
LOG.warn("PROGRESS: $message")
}
override fun reportMessage(minVerboseLevel: Int, message: String) {
override fun logMessage(verbosityLevel: Int, message: String) {
LOG.info("PROGRESS: $message")
}
@@ -170,34 +167,30 @@ private suspend fun callProjectConversion(projectArgs: OpenProjectArgs) {
private suspend fun callProjectConfigurators(
projectArgs: OpenProjectArgs,
indicator: ProgressIndicator,
action: suspend CommandLineInspectionProjectConfigurator.(ConfiguratorContext) -> Unit
action: suspend WarmupConfiguration.() -> Unit
) {
if (!projectArgs.configureProject) return
CommandLineInspectionProjectConfigurator.EP_NAME.extensionList.forEach { configurator ->
indicator.pushState()
try {
val context = object : ConfiguratorContext {
override fun getProgressIndicator() = indicator
override fun getLogger() = listener
override fun getProjectPath() = projectArgs.projectDir
override fun getFilesFilter(): Predicate<Path> = Predicate { true }
override fun getVirtualFilesFilter(): Predicate<VirtualFile> = Predicate { true }
}
if (configurator.name in projectArgs.disabledConfigurators) {
listener.reportMessage(1, "Configurator ${configurator.name} is disabled in the settings")
}
else if (configurator.isApplicable(context)) {
runTaskAndLogTime("configure " + configurator.name) {
action(configurator, context)
val activeConfigurators = WarmupConfiguration.EP_NAME.extensionList.filter {
if (it.name in projectArgs.disabledConfigurators) {
listener.logMessage(1, "Configurator ${it.name} is disabled in the settings")
false
} else {
true
}
}
val fraction = 1.0 / activeConfigurators.size.toDouble()
withLoggingProgressReporter {
for (configuration in activeConfigurators) {
durationStep(fraction, "Configurator ${configuration.name} is in action..." /* NON-NLS */) {
runTaskAndLogTime("Configure " + configuration.name) {
action(configuration)
}
}
}
finally {
indicator.popState()
}
}
yieldThroughInvokeLater()

View File

@@ -0,0 +1,37 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.warmup.util
import com.intellij.openapi.progress.asContextElement
import com.intellij.openapi.progress.impl.ProgressState
import com.intellij.openapi.progress.impl.TextDetailsProgressReporter
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.sample
import kotlin.time.Duration.Companion.milliseconds
/**
* Installs a progress reporter that sends the information about progress to the stdout instead of UI.
*/
suspend fun <T> withLoggingProgressReporter(action: suspend CoroutineScope.() -> T): T = coroutineScope {
TextDetailsProgressReporter(this).use { reporter ->
val reportToCommandLineJob = reportToStdout(reporter.progressState)
try {
withContext(reporter.asContextElement(), action)
}
finally {
reportToCommandLineJob.cancel()
}
}
}
@OptIn(FlowPreview::class)
private fun CoroutineScope.reportToStdout(
stateFlow: Flow<ProgressState>
): Job {
return launch(Dispatchers.IO) {
stateFlow.sample(300.milliseconds).distinctUntilChanged().collect {
progressStateText(it)?.let(ConsoleLog::info)
}
}
}

View File

@@ -181,6 +181,7 @@
<registryKey key="gradle.version.catalogs.dynamic.support" defaultValue="true"
description="Enable experimental support of version catalogs based on after-sync gradle models"/>
<commandLineInspectionProjectConfigurator implementation="org.jetbrains.plugins.gradle.GradleCommandLineProjectConfigurator"/>
<warmupConfiguration implementation="org.jetbrains.plugins.gradle.GradleWarmupConfiguration"/>
<notificationGroup id="Gradle Notification Group" displayType="STICKY_BALLOON" bundle="messages.GradleBundle" key="notification.group.gradle"/>
<statistics.notificationIdsHolder implementation="org.jetbrains.plugins.gradle.service.project.GradleNotificationIdsHolder"/>
<iconMapper mappingFile="GradleIconMappings.json"/>

View File

@@ -0,0 +1,6 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.plugins.gradle
import com.intellij.ide.warmup.WarmupConfigurationOfCLIConfigurator
class GradleWarmupConfiguration : WarmupConfigurationOfCLIConfigurator(GradleCommandLineProjectConfigurator())

View File

@@ -0,0 +1,6 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.idea.maven
import com.intellij.ide.warmup.WarmupConfigurationOfCLIConfigurator
class MavenWarmupConfiguration : WarmupConfigurationOfCLIConfigurator(MavenCommandLineInspectionProjectConfigurator())

View File

@@ -369,6 +369,7 @@
<newProjectWizard.java.buildSystem implementation="org.jetbrains.idea.maven.wizards.MavenJavaNewProjectWizard"/>
<commandLineInspectionProjectConfigurator implementation="org.jetbrains.idea.maven.MavenCommandLineInspectionProjectConfigurator"/>
<warmupConfiguration implementation="org.jetbrains.idea.maven.MavenWarmupConfiguration"/>
<externalSystem.dependencyModifier implementation="org.jetbrains.idea.maven.dsl.MavenDependencyModificator"/>
<iconMapper mappingFile="MavenIconMappings.json"/>

View File

@@ -17,6 +17,8 @@
<sdkEditorAdditionalOptionsProvider implementation="com.jetbrains.python.PythonSdkEditorAdditionalOptionsProvider"/>
<commandLineInspectionProjectConfigurator implementation="com.jetbrains.python.inspections.PythonPluginCommandLineInspectionProjectConfigurator"/>
<warmupConfiguration implementation="com.jetbrains.python.inspections.PythonPluginWarmupConfiguration"/>
</extensions>
<extensions defaultExtensionNs="Pythonid">

View File

@@ -0,0 +1,6 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.jetbrains.python.inspections
import com.intellij.ide.warmup.WarmupConfigurationOfCLIConfigurator
class PythonPluginWarmupConfiguration : WarmupConfigurationOfCLIConfigurator(PythonPluginCommandLineInspectionProjectConfigurator())