diff --git a/platform/ide-core-impl/intellij.platform.ide.core.impl.iml b/platform/ide-core-impl/intellij.platform.ide.core.impl.iml index 9278634c725f..7544bcabdd97 100644 --- a/platform/ide-core-impl/intellij.platform.ide.core.impl.iml +++ b/platform/ide-core-impl/intellij.platform.ide.core.impl.iml @@ -26,5 +26,6 @@ + \ No newline at end of file diff --git a/platform/lang-impl/api-dump-unreviewed.txt b/platform/lang-impl/api-dump-unreviewed.txt index ca00927a7387..bf4f78f6d440 100644 --- a/platform/lang-impl/api-dump-unreviewed.txt +++ b/platform/lang-impl/api-dump-unreviewed.txt @@ -28883,8 +28883,6 @@ f:com.intellij.util.PatternValuesIndex - ():V - s:buildStringIndex(java.util.Collection):java.util.Set - s:processStringValues(java.util.Collection,com.intellij.util.PairProcessor):Z -f:com.intellij.util.ProjectConfigurationUtil -- sf:awaitCompleteProjectConfiguration(com.intellij.openapi.project.Project,kotlin.jvm.functions.Function1,kotlin.coroutines.Continuation):java.lang.Object a:com.intellij.util.TextFieldCompletionProvider - com.intellij.openapi.project.PossiblyDumbAware - com.intellij.util.textCompletion.TextCompletionProvider diff --git a/platform/platform-api/api-dump-unreviewed.txt b/platform/platform-api/api-dump-unreviewed.txt index 304d92600afc..5bfd8b36276d 100644 --- a/platform/platform-api/api-dump-unreviewed.txt +++ b/platform/platform-api/api-dump-unreviewed.txt @@ -1411,17 +1411,6 @@ com.intellij.ide.warmup.WarmupConfigurator - a:runWarmup(com.intellij.openapi.project.Project,kotlin.coroutines.Continuation):java.lang.Object f:com.intellij.ide.warmup.WarmupConfigurator$Companion - f:getEP_NAME():com.intellij.openapi.extensions.ExtensionPointName -com.intellij.ide.warmup.WarmupLogger -- sf:Companion:com.intellij.ide.warmup.WarmupLogger$Companion -- a:logError(java.lang.String,java.lang.Throwable):V -- a:logFatalError(java.lang.String,java.lang.Throwable):V -- a:logInfo(java.lang.String):V -f:com.intellij.ide.warmup.WarmupLogger$Companion -- f:error(java.lang.String,java.lang.Throwable):V -- bs:error$default(com.intellij.ide.warmup.WarmupLogger$Companion,java.lang.String,java.lang.Throwable,I,java.lang.Object):V -- f:fatalError(java.lang.String,java.lang.Throwable):V -- bs:fatalError$default(com.intellij.ide.warmup.WarmupLogger$Companion,java.lang.String,java.lang.Throwable,I,java.lang.Object):V -- f:message(java.lang.String):V com.intellij.ide.warmup.WarmupStatus - sf:Companion:com.intellij.ide.warmup.WarmupStatus$Companion f:com.intellij.ide.warmup.WarmupStatus$Companion @@ -3494,6 +3483,71 @@ a:com.intellij.openapi.project.ProjectReloadState - s:getInstance(com.intellij.openapi.project.Project):com.intellij.openapi.project.ProjectReloadState - a:isAfterAutomaticReload():Z - a:onBeforeAutomaticProjectReload():V +f:com.intellij.openapi.project.configuration.HeadlessLogging +- sf:INSTANCE:com.intellij.openapi.project.configuration.HeadlessLogging +- f:logFatalError(java.lang.String):V +- f:logFatalError(java.lang.Throwable):V +- f:logMessage(java.lang.String):V +- f:logWarning(java.lang.String):V +- f:logWarning(java.lang.Throwable):V +- f:loggingFlow():kotlinx.coroutines.flow.SharedFlow +com.intellij.openapi.project.configuration.HeadlessLogging$HeadlessLoggingService +- sf:Companion:com.intellij.openapi.project.configuration.HeadlessLogging$HeadlessLoggingService$Companion +- a:logEntry(com.intellij.openapi.project.configuration.HeadlessLogging$LogEntry):V +- a:loggingFlow():kotlinx.coroutines.flow.SharedFlow +f:com.intellij.openapi.project.configuration.HeadlessLogging$HeadlessLoggingService$Companion +- f:getInstance():com.intellij.openapi.project.configuration.HeadlessLogging$HeadlessLoggingService +f:com.intellij.openapi.project.configuration.HeadlessLogging$LogEntry +- (com.intellij.openapi.project.configuration.HeadlessLogging$SeverityKind,com.intellij.openapi.project.configuration.HeadlessLogging$Message):V +- f:component1():com.intellij.openapi.project.configuration.HeadlessLogging$SeverityKind +- f:component2():com.intellij.openapi.project.configuration.HeadlessLogging$Message +- f:copy(com.intellij.openapi.project.configuration.HeadlessLogging$SeverityKind,com.intellij.openapi.project.configuration.HeadlessLogging$Message):com.intellij.openapi.project.configuration.HeadlessLogging$LogEntry +- bs:copy$default(com.intellij.openapi.project.configuration.HeadlessLogging$LogEntry,com.intellij.openapi.project.configuration.HeadlessLogging$SeverityKind,com.intellij.openapi.project.configuration.HeadlessLogging$Message,I,java.lang.Object):com.intellij.openapi.project.configuration.HeadlessLogging$LogEntry +- equals(java.lang.Object):Z +- f:getMessage():com.intellij.openapi.project.configuration.HeadlessLogging$Message +- f:getSeverity():com.intellij.openapi.project.configuration.HeadlessLogging$SeverityKind +- hashCode():I +- toString():java.lang.String +com.intellij.openapi.project.configuration.HeadlessLogging$Message +- a:representation():java.lang.String +f:com.intellij.openapi.project.configuration.HeadlessLogging$Message$Exception +- com.intellij.openapi.project.configuration.HeadlessLogging$Message +- bsf:box-impl(java.lang.Throwable):com.intellij.openapi.project.configuration.HeadlessLogging$Message$Exception +- s:constructor-impl(java.lang.Throwable):java.lang.Throwable +- equals(java.lang.Object):Z +- s:equals-impl(java.lang.Throwable,java.lang.Object):Z +- sf:equals-impl0(java.lang.Throwable,java.lang.Throwable):Z +- f:getException():java.lang.Throwable +- hashCode():I +- s:hashCode-impl(java.lang.Throwable):I +- representation():java.lang.String +- s:representation-impl(java.lang.Throwable):java.lang.String +- toString():java.lang.String +- s:toString-impl(java.lang.Throwable):java.lang.String +- bf:unbox-impl():java.lang.Throwable +f:com.intellij.openapi.project.configuration.HeadlessLogging$Message$Plain +- com.intellij.openapi.project.configuration.HeadlessLogging$Message +- bsf:box-impl(java.lang.String):com.intellij.openapi.project.configuration.HeadlessLogging$Message$Plain +- s:constructor-impl(java.lang.String):java.lang.String +- equals(java.lang.Object):Z +- s:equals-impl(java.lang.String,java.lang.Object):Z +- sf:equals-impl0(java.lang.String,java.lang.String):Z +- f:getMessage():java.lang.String +- hashCode():I +- s:hashCode-impl(java.lang.String):I +- representation():java.lang.String +- s:representation-impl(java.lang.String):java.lang.String +- toString():java.lang.String +- s:toString-impl(java.lang.String):java.lang.String +- bf:unbox-impl():java.lang.String +e:com.intellij.openapi.project.configuration.HeadlessLogging$SeverityKind +- java.lang.Enum +- sf:Fatal:com.intellij.openapi.project.configuration.HeadlessLogging$SeverityKind +- sf:Info:com.intellij.openapi.project.configuration.HeadlessLogging$SeverityKind +- sf:Warning:com.intellij.openapi.project.configuration.HeadlessLogging$SeverityKind +- s:getEntries():kotlin.enums.EnumEntries +- s:valueOf(java.lang.String):com.intellij.openapi.project.configuration.HeadlessLogging$SeverityKind +- s:values():com.intellij.openapi.project.configuration.HeadlessLogging$SeverityKind[] com.intellij.openapi.project.impl.ProjectLifecycleListener - sf:TOPIC:com.intellij.util.messages.Topic - afterProjectClosed(com.intellij.openapi.project.Project):V diff --git a/platform/platform-api/src/com/intellij/ide/warmup/WarmupLogger.kt b/platform/platform-api/src/com/intellij/ide/warmup/WarmupLogger.kt deleted file mode 100644 index b11a63d21230..000000000000 --- a/platform/platform-api/src/com/intellij/ide/warmup/WarmupLogger.kt +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright 2000-2023 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 - -/** - * This class is used when warmup decides to relay important information to the user. - */ -interface WarmupLogger { - - /** - * Allows reporting information messages. - */ - fun logInfo(message: String) - - /** - * Allows reporting error messages to the user. - * Use this if there is an important error, which is still recoverable. - */ - fun logError(message: String, throwable: Throwable?) - - /** - * Allows aborting the process of warmup if something non-recoverable happens. - */ - fun logFatalError(message: String, throwable: Throwable?) - - companion object { - private val EP_NAME: ExtensionPointName = ExtensionPointName("com.intellij.warmupLogger") - - fun message(message: String) { - for (warmupLogger in EP_NAME.extensionList) { - warmupLogger.logInfo(message) - } - } - - fun error(message: String, throwable: Throwable? = null) { - for (warmupLogger in EP_NAME.extensionList) { - warmupLogger.logError(message, throwable) - } - } - - fun fatalError(message: String, throwable: Throwable? = null) { - for (warmupLogger in EP_NAME.extensionList) { - warmupLogger.logFatalError(message, throwable) - } - } - } -} \ No newline at end of file diff --git a/platform/platform-api/src/com/intellij/ide/warmup/WarmupStatus.kt b/platform/platform-api/src/com/intellij/ide/warmup/WarmupStatus.kt index 3fc3ae7023c2..81f2e4d46893 100644 --- a/platform/platform-api/src/com/intellij/ide/warmup/WarmupStatus.kt +++ b/platform/platform-api/src/com/intellij/ide/warmup/WarmupStatus.kt @@ -1,7 +1,6 @@ // Copyright 2000-2023 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.application.Application import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.util.Key import org.jetbrains.annotations.ApiStatus.Internal diff --git a/platform/platform-api/src/com/intellij/openapi/project/configuration/HeadlessLogging.kt b/platform/platform-api/src/com/intellij/openapi/project/configuration/HeadlessLogging.kt new file mode 100644 index 000000000000..e0be22f0e2db --- /dev/null +++ b/platform/platform-api/src/com/intellij/openapi/project/configuration/HeadlessLogging.kt @@ -0,0 +1,89 @@ +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.intellij.openapi.project.configuration + +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.project.configuration.HeadlessLogging.Message.Plain +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharedFlow +import javax.print.attribute.standard.Severity + +/** + * A sink for information that may be useful during command-line execution of the IDE. + * + * Each subsystem can use the methods in this class to send some information that may be of value to the end user. + * + * We do not provide a distinction between the severity levels here, as all the reported messages are of interest for the user. + * Severity differentiation makes sense for investigation of internal problems, and for this reason we have internal logging. + */ +object HeadlessLogging { + + /** + * Reports an informational message about the IDE. + * + * This kind of messages is important to keep track of progress and to assure the user that the execution is not stuck. + */ + fun logMessage(message: String) = HeadlessLoggingService.getInstance().logEntry(LogEntry(SeverityKind.Info, Plain(message))) + + /** + * Reports a warning message. + * + * The collector of the message can decide to react differently to warnings, i.e., print them in different colors in the console, + * or collect and report them batched at the end of the configuration. + */ + fun logWarning(message: String) = HeadlessLoggingService.getInstance().logEntry(LogEntry(SeverityKind.Warning, Plain(message))) + + /** + * Reports a non-fatal exception. An exception is non-fatal if the IDE can recover from it. + * + * This kind of error does not stop the execution of the IDE. + */ + fun logWarning(exception: Throwable) = HeadlessLoggingService.getInstance().logEntry(LogEntry(SeverityKind.Warning, Message.Exception(exception))) + + /** + * Reports a fatal error during the execution. An error is fatal if the user's actions are required in order to fix the problem. + * + * This kind of errors **has influence on control flow**: once the IDE reports a fatal error, + * the headless execution may stop. An example of this error is a failure in the build system import. + * This + */ + fun logFatalError(exception: Throwable) = HeadlessLoggingService.getInstance().logEntry(LogEntry(SeverityKind.Fatal, Message.Exception(exception))) + fun logFatalError(message: String) = HeadlessLoggingService.getInstance().logEntry(LogEntry(SeverityKind.Fatal, Plain(message))) + + /** + * Retrieves a hot flow with messages about the IDE. + */ + fun loggingFlow(): SharedFlow = HeadlessLoggingService.getInstance().loggingFlow() + + enum class SeverityKind { + Info, + Warning, + Fatal + } + + sealed interface Message { + fun representation(): String + + @JvmInline + value class Plain(val message: String) : Message { + override fun representation(): String = message + } + + @JvmInline + value class Exception(val exception: Throwable) : Message { + override fun representation(): String = exception.toString() + } + } + + data class LogEntry(val severity: SeverityKind, val message: Message) + + interface HeadlessLoggingService { + companion object { + fun getInstance(): HeadlessLoggingService { + return ApplicationManager.getApplication().getService(HeadlessLoggingService::class.java) + } + } + + fun logEntry(logEntry: LogEntry) + fun loggingFlow(): SharedFlow + } +} \ No newline at end of file diff --git a/platform/platform-impl/api-dump-unreviewed.txt b/platform/platform-impl/api-dump-unreviewed.txt index 09c1470f6a76..834ac7864cbb 100644 --- a/platform/platform-impl/api-dump-unreviewed.txt +++ b/platform/platform-impl/api-dump-unreviewed.txt @@ -18065,6 +18065,29 @@ f:com.intellij.openapi.project.ScanningTracker - awaitConfiguration(com.intellij.openapi.project.Project,kotlin.coroutines.Continuation):java.lang.Object - getPresentableName():java.lang.String - isInProgress(com.intellij.openapi.project.Project,kotlin.coroutines.Continuation):java.lang.Object +f:com.intellij.openapi.project.configuration.ChannelingProgressIndicator +- com.intellij.openapi.progress.util.ProgressIndicatorBase +- (java.lang.String):V +- setFraction(D):V +- setIndeterminate(Z):V +- setText(java.lang.String):V +- setText2(java.lang.String):V +f:com.intellij.openapi.project.configuration.EmptyLoggingService +- com.intellij.openapi.project.configuration.HeadlessLogging$HeadlessLoggingService +- ():V +- logEntry(com.intellij.openapi.project.configuration.HeadlessLogging$LogEntry):V +- loggingFlow():kotlinx.coroutines.flow.SharedFlow +f:com.intellij.openapi.project.configuration.HeadlessLoggingServiceImpl +- com.intellij.openapi.project.configuration.HeadlessLogging$HeadlessLoggingService +- ():V +- logEntry(com.intellij.openapi.project.configuration.HeadlessLogging$LogEntry):V +- loggingFlow():kotlinx.coroutines.flow.SharedFlow +f:com.intellij.openapi.project.configuration.HeadlessProgressListener +- ():V +- afterTaskFinished(com.intellij.openapi.progress.Task):V +- beforeTaskStart(com.intellij.openapi.progress.Task,com.intellij.openapi.progress.ProgressIndicator):V +f:com.intellij.openapi.project.configuration.ProjectConfigurationUtil +- sf:awaitCompleteProjectConfiguration(com.intellij.openapi.project.Project,kotlin.jvm.functions.Function1,kotlin.coroutines.Continuation):java.lang.Object a:com.intellij.openapi.project.impl.DefaultProjectTimed - com.intellij.util.TimedReference - dispose():V diff --git a/platform/platform-impl/src/com/intellij/ide/CommandLineInspectionProjectConfigurator.java b/platform/platform-impl/src/com/intellij/ide/CommandLineInspectionProjectConfigurator.java index eb98f03e8baf..129e8e88990c 100644 --- a/platform/platform-impl/src/com/intellij/ide/CommandLineInspectionProjectConfigurator.java +++ b/platform/platform-impl/src/com/intellij/ide/CommandLineInspectionProjectConfigurator.java @@ -7,6 +7,7 @@ import com.intellij.openapi.extensions.ExtensionPointName; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.project.Project; import com.intellij.openapi.vfs.VirtualFile; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -17,7 +18,9 @@ import java.util.function.Predicate; /** * Extension point that helps prepare project for opening in headless or automated environments. * Implementation must be stateless. + * Consider using com.intellij.platform.backend.observation.ActivityTracker */ +@ApiStatus.Obsolete(since = "2024.1") public interface CommandLineInspectionProjectConfigurator { ExtensionPointName EP_NAME = ExtensionPointName.create("com.intellij.commandLineInspectionProjectConfigurator"); diff --git a/platform/platform-impl/src/com/intellij/ide/environment/impl/HeadlessEnvironmentService.kt b/platform/platform-impl/src/com/intellij/ide/environment/impl/HeadlessEnvironmentService.kt index a1789a4809a1..4bdfe6fcb665 100644 --- a/platform/platform-impl/src/com/intellij/ide/environment/impl/HeadlessEnvironmentService.kt +++ b/platform/platform-impl/src/com/intellij/ide/environment/impl/HeadlessEnvironmentService.kt @@ -6,9 +6,9 @@ import com.fasterxml.jackson.databind.type.CollectionType import com.intellij.ide.environment.EnvironmentKey import com.intellij.ide.environment.EnvironmentService import com.intellij.ide.environment.description -import com.intellij.ide.warmup.WarmupLogger import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.diagnostic.logger +import com.intellij.openapi.project.configuration.HeadlessLogging import kotlinx.coroutines.* import java.io.IOException @@ -22,7 +22,8 @@ class HeadlessEnvironmentService(scope: CoroutineScope) : BaseEnvironmentService return getEnvironmentValueOrNull(key) ?: run { val throwable = MissingEnvironmentKeyException(key) - WarmupLogger.fatalError("Insufficient project configuration", MissingEnvironmentKeyException(key)) + LOG.error(throwable) + HeadlessLogging.logFatalError(MissingEnvironmentKeyException(key)) throw throwable } } diff --git a/platform/platform-impl/src/com/intellij/openapi/project/configuration/ChannelingProgressIndicator.kt b/platform/platform-impl/src/com/intellij/openapi/project/configuration/ChannelingProgressIndicator.kt new file mode 100644 index 000000000000..a0ce5996c2cf --- /dev/null +++ b/platform/platform-impl/src/com/intellij/openapi/project/configuration/ChannelingProgressIndicator.kt @@ -0,0 +1,67 @@ +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.intellij.openapi.project.configuration + +import com.intellij.openapi.progress.util.ProgressIndicatorBase +import java.util.concurrent.atomic.AtomicReference + +class ChannelingProgressIndicator(private val prefix: String) : ProgressIndicatorBase() { + override fun setIndeterminate(indeterminate: Boolean) { + super.setIndeterminate(indeterminate) + } + + private val lastState = AtomicReference(0.0) + + override fun setFraction(fraction: Double) { + super.setFraction(fraction) + val lastStateValue = lastState.get() + if (fraction - lastStateValue > 0.2) { + // we debounce the messages by 20% + if (lastState.compareAndSet(lastStateValue, fraction)) { + offerState() + } + } + } + + override fun setText(text: String?) { + super.setText(text) + super.setText2("") + offerState() + } + + override fun setText2(text: String?) { + super.setText2(text) + } + + private fun trimProgressTextAndNullize(s: String?) = s?.trim()?.trimEnd('.', '\u2026', ' ')?.takeIf { it.isNotBlank() } + + private fun progressStateText(fraction: Double?, text: String?, details: String?): String? { + val text = trimProgressTextAndNullize(text) + val text2 = trimProgressTextAndNullize(details) + if (text.isNullOrBlank() && text2.isNullOrBlank()) { + return null + } + + val shortText = text ?: "" + val verboseText = shortText + (text2?.let { " ($it)" } ?: "") + if (shortText.isBlank() || fraction == null) { + return verboseText + } + + val v = (100.0 * fraction).toInt() + val total = 18 + val completed = (total * fraction).toInt().coerceAtLeast(0) + val d = ".".repeat(completed).padEnd(total, ' ') + val verboseReport = verboseText.take(100).padEnd(105) + "$d $v%" + return verboseReport + } + + private fun offerState() { + val progressState = progressStateText( + fraction = if (isIndeterminate) null else fraction, + text = text, + details = text2, + ) ?: return + val actualPrefix = if (prefix.isEmpty()) "" else "[$prefix]: " + HeadlessLogging.logMessage(actualPrefix + progressState) + } +} \ No newline at end of file diff --git a/platform/platform-impl/src/com/intellij/openapi/project/configuration/EmptyLoggingService.kt b/platform/platform-impl/src/com/intellij/openapi/project/configuration/EmptyLoggingService.kt new file mode 100644 index 000000000000..43f1bb90b275 --- /dev/null +++ b/platform/platform-impl/src/com/intellij/openapi/project/configuration/EmptyLoggingService.kt @@ -0,0 +1,15 @@ +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.intellij.openapi.project.configuration + +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.SharedFlow + +class EmptyLoggingService : HeadlessLogging.HeadlessLoggingService { + private val emptyFlow = MutableSharedFlow() + override fun logEntry(exception: HeadlessLogging.LogEntry) { + } + + override fun loggingFlow(): SharedFlow { + return emptyFlow + } +} \ No newline at end of file diff --git a/platform/platform-impl/src/com/intellij/openapi/project/configuration/HeadlessLoggingServiceImpl.kt b/platform/platform-impl/src/com/intellij/openapi/project/configuration/HeadlessLoggingServiceImpl.kt new file mode 100644 index 000000000000..57b28b6ad682 --- /dev/null +++ b/platform/platform-impl/src/com/intellij/openapi/project/configuration/HeadlessLoggingServiceImpl.kt @@ -0,0 +1,36 @@ +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.intellij.openapi.project.configuration + +import com.intellij.openapi.diagnostic.thisLogger +import kotlinx.coroutines.channels.BufferOverflow +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.SharedFlow + +class HeadlessLoggingServiceImpl : HeadlessLogging.HeadlessLoggingService { + + private val flow: MutableSharedFlow = MutableSharedFlow(replay = 0, extraBufferCapacity = 1024, onBufferOverflow = BufferOverflow.SUSPEND) + + + override fun logEntry(logEntry: HeadlessLogging.LogEntry) { + emitLogEntry(logEntry) + } + + private fun emitLogEntry(entry: HeadlessLogging.LogEntry) { + var internalLoggingPerformed = false + while (!flow.tryEmit(entry)) { + // we have some slow collectors + // there is nothing the platform can do, other than report this incident + if (!internalLoggingPerformed) { + thisLogger().warn("Cannot log message: ${entry}. \n" + + "Headless logger has exhausted the buffer of messages. Please speed up the listeners of the Headless logger.") + internalLoggingPerformed = true + } + // Nevertheless, it is important to deliver complete information to the user. + Thread.sleep(100) + } + } + + override fun loggingFlow(): SharedFlow { + return flow + } +} \ No newline at end of file diff --git a/platform/platform-impl/src/com/intellij/openapi/project/configuration/HeadlessProgressListener.kt b/platform/platform-impl/src/com/intellij/openapi/project/configuration/HeadlessProgressListener.kt new file mode 100644 index 000000000000..accc5fc47ed9 --- /dev/null +++ b/platform/platform-impl/src/com/intellij/openapi/project/configuration/HeadlessProgressListener.kt @@ -0,0 +1,32 @@ +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.intellij.openapi.project.configuration + +import com.intellij.openapi.progress.ProgressIndicator +import com.intellij.openapi.progress.ProgressManagerListener +import com.intellij.openapi.progress.Task +import com.intellij.openapi.progress.util.ProgressIndicatorBase +import com.intellij.openapi.util.text.Formats +import com.intellij.openapi.wm.ex.ProgressIndicatorEx +import java.util.concurrent.ConcurrentHashMap + +class HeadlessProgressListener : ProgressManagerListener { + + private val taskDurationMap = ConcurrentHashMap() + + override fun beforeTaskStart(task: Task, indicator: ProgressIndicator) { + if (indicator !is ProgressIndicatorEx) { + return + } + HeadlessLogging.logMessage("[IDE]: Task '${task.title}' started") + taskDurationMap[System.identityHashCode(task)] = System.currentTimeMillis() + indicator.addStateDelegate(ChannelingProgressIndicator("IDE")) + super.beforeTaskStart(task, indicator) + } + + override fun afterTaskFinished(task: Task) { + val currentTime = System.currentTimeMillis() + val startTime = taskDurationMap.remove(System.identityHashCode(task)) + val elapsedTimeSuffix = if (startTime == null) "" else " in ${Formats.formatDuration(currentTime - startTime)}" + HeadlessLogging.logMessage("[IDE]: Task '${task.title}' ended" + elapsedTimeSuffix) + } +} \ No newline at end of file diff --git a/platform/lang-impl/src/com/intellij/util/ProjectConfiguration.kt b/platform/platform-impl/src/com/intellij/openapi/project/configuration/ProjectConfiguration.kt similarity index 92% rename from platform/lang-impl/src/com/intellij/util/ProjectConfiguration.kt rename to platform/platform-impl/src/com/intellij/openapi/project/configuration/ProjectConfiguration.kt index 9b72000b9017..f8f9dd604bfa 100644 --- a/platform/lang-impl/src/com/intellij/util/ProjectConfiguration.kt +++ b/platform/platform-impl/src/com/intellij/openapi/project/configuration/ProjectConfiguration.kt @@ -1,9 +1,10 @@ -@file:JvmName("ProjectConfigurationUtil") // Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -package com.intellij.util +@file:JvmName("ProjectConfigurationUtil") +package com.intellij.openapi.project.configuration + +import com.intellij.configurationStore.StoreUtil.saveSettings import com.intellij.configurationStore.saveProjectsAndApp -import com.intellij.configurationStore.saveSettings import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.project.Project import com.intellij.platform.backend.observation.Observation diff --git a/platform/platform-resources/src/META-INF/PlatformExtensionPoints.xml b/platform/platform-resources/src/META-INF/PlatformExtensionPoints.xml index e1d092747e44..c7b6a158e41e 100644 --- a/platform/platform-resources/src/META-INF/PlatformExtensionPoints.xml +++ b/platform/platform-resources/src/META-INF/PlatformExtensionPoints.xml @@ -511,7 +511,6 @@ - diff --git a/platform/platform-resources/src/META-INF/PlatformLangComponents.xml b/platform/platform-resources/src/META-INF/PlatformLangComponents.xml index 35c327831520..cc6ba85d13d4 100644 --- a/platform/platform-resources/src/META-INF/PlatformLangComponents.xml +++ b/platform/platform-resources/src/META-INF/PlatformLangComponents.xml @@ -109,6 +109,10 @@ + + + + diff --git a/platform/warmup/api-dump-unreviewed.txt b/platform/warmup/api-dump-unreviewed.txt index 44e08c5033fe..af26052ecade 100644 --- a/platform/warmup/api-dump-unreviewed.txt +++ b/platform/warmup/api-dump-unreviewed.txt @@ -25,12 +25,6 @@ f:com.intellij.warmup.util.ConsoleLog - bs:error$default(com.intellij.warmup.util.ConsoleLog,java.lang.String,java.lang.Throwable,I,java.lang.Object):V - f:info(java.lang.String):V - f:warn(java.lang.String):V -f:com.intellij.warmup.util.DefaultWarmupLogger -- com.intellij.ide.warmup.WarmupLogger -- ():V -- logError(java.lang.String,java.lang.Throwable):V -- logFatalError(java.lang.String,java.lang.Throwable):V -- logInfo(java.lang.String):V com.intellij.warmup.util.HeadlessConfigurableArgs - a:getPathToConfigurationFile():java.nio.file.Path c:com.intellij.warmup.util.HeadlessConfigurableArgsImpl @@ -74,10 +68,6 @@ f:com.intellij.warmup.util.WarmupLogger - f:logError(java.lang.String,java.lang.Throwable):V - bs:logError$default(com.intellij.warmup.util.WarmupLogger,java.lang.String,java.lang.Throwable,I,java.lang.Object):V - f:logInfo(java.lang.String):V -f:com.intellij.warmup.util.WarmupProgressListener -- ():V -- afterTaskFinished(com.intellij.openapi.progress.Task):V -- beforeTaskStart(com.intellij.openapi.progress.Task,com.intellij.openapi.progress.ProgressIndicator):V com.intellij.warmup.util.WarmupProjectArgs - com.intellij.warmup.util.OpenProjectArgs - a:getBuild():Z diff --git a/platform/warmup/resources/META-INF/PlatformWarmup.xml b/platform/warmup/resources/META-INF/PlatformWarmup.xml index 978a74de02a0..fc867bfa0a11 100644 --- a/platform/warmup/resources/META-INF/PlatformWarmup.xml +++ b/platform/warmup/resources/META-INF/PlatformWarmup.xml @@ -6,7 +6,6 @@ - \ No newline at end of file diff --git a/platform/warmup/src/com/intellij/warmup/ProjectCachesWarmup.kt b/platform/warmup/src/com/intellij/warmup/ProjectCachesWarmup.kt index 46a868e87671..075be8489ffc 100644 --- a/platform/warmup/src/com/intellij/warmup/ProjectCachesWarmup.kt +++ b/platform/warmup/src/com/intellij/warmup/ProjectCachesWarmup.kt @@ -58,7 +58,7 @@ internal class ProjectCachesWarmup : ModernApplicationStarter() { configureVcsIndexing(commandArgs) runWarmupActivity { - initLogger(args) + val loggingJob = initLogger(args) waitIndexInitialization() val project = try { importOrOpenProjectAsync(commandArgs) @@ -79,6 +79,7 @@ internal class ProjectCachesWarmup : ModernApplicationStarter() { waitForRefreshQueue() } ProjectManagerEx.getInstanceEx().forceCloseProjectAsync(project, save = true) + loggingJob.cancel() } exitApplication() @@ -225,7 +226,7 @@ private suspend fun buildProject(project: Project, commandArgs: WarmupProjectArg } } -private suspend fun runWarmupActivity(action: suspend () -> Unit) { +private suspend fun runWarmupActivity(action: suspend CoroutineScope.() -> Unit) { val indexedFiles = installStatisticsCollector() WarmupStatus.statusChanged(WarmupStatus.InProgress) try { diff --git a/platform/warmup/src/com/intellij/warmup/util/ActivityBasedWarmup.kt b/platform/warmup/src/com/intellij/warmup/util/ActivityBasedWarmup.kt index d0e875217549..c6ccb25c28f3 100644 --- a/platform/warmup/src/com/intellij/warmup/util/ActivityBasedWarmup.kt +++ b/platform/warmup/src/com/intellij/warmup/util/ActivityBasedWarmup.kt @@ -4,14 +4,12 @@ package com.intellij.warmup.util import com.intellij.ide.impl.OpenProjectTask import com.intellij.ide.impl.ProjectUtil import com.intellij.openapi.project.Project -import com.intellij.util.awaitCompleteProjectConfiguration +import com.intellij.openapi.project.configuration.HeadlessLogging +import com.intellij.openapi.project.configuration.awaitCompleteProjectConfiguration import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.CoroutineStart import kotlinx.coroutines.Deferred import kotlinx.coroutines.async -import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.job +import kotlinx.coroutines.flow.first import kotlinx.coroutines.selects.select suspend fun configureProjectByActivities(args: OpenProjectArgs) : Project { @@ -39,24 +37,15 @@ suspend fun configureProjectByActivities(args: OpenProjectArgs) : Project { return project } - -internal val abortFlow : MutableStateFlow = MutableStateFlow(null) - private fun CoroutineScope.getFailureDeferred() : Deferred { return async { - while (coroutineContext.job.isActive) { - val message = abortFlow.value - if (message != null) { - return@async message - } - delay(500) - } - error("unreachable") + val firstFatal = HeadlessLogging.loggingFlow().first { (level, _) -> level == HeadlessLogging.SeverityKind.Fatal } + firstFatal.message.representation() } } private fun CoroutineScope.getConfigurationDeferred(project : Project) : Deferred { - return async(start = CoroutineStart.UNDISPATCHED) { + return async { withLoggingProgressReporter { project.awaitCompleteProjectConfiguration(WarmupLogger::logInfo) } diff --git a/platform/warmup/src/com/intellij/warmup/util/DefaultWarmupLogger.kt b/platform/warmup/src/com/intellij/warmup/util/DefaultWarmupLogger.kt deleted file mode 100644 index 847de564c9f3..000000000000 --- a/platform/warmup/src/com/intellij/warmup/util/DefaultWarmupLogger.kt +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright 2000-2023 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.ide.warmup.WarmupLogger - -class DefaultWarmupLogger : WarmupLogger { - override fun logInfo(message: String) { - com.intellij.warmup.util.WarmupLogger.logInfo(message) - } - - override fun logError(message: String, throwable: Throwable?) { - com.intellij.warmup.util.WarmupLogger.logError(message, throwable) - } - - override fun logFatalError(message: String, throwable: Throwable?) { - logError(message, throwable) - abortFlow.tryEmit(message) - } -} \ No newline at end of file diff --git a/platform/warmup/src/com/intellij/warmup/util/logging.kt b/platform/warmup/src/com/intellij/warmup/util/logging.kt index 64b95ba29613..b91a692ff499 100644 --- a/platform/warmup/src/com/intellij/warmup/util/logging.kt +++ b/platform/warmup/src/com/intellij/warmup/util/logging.kt @@ -2,26 +2,19 @@ package com.intellij.warmup.util import com.intellij.diagnostic.ThreadDumper -import com.intellij.ide.warmup.WarmupStatus import com.intellij.openapi.application.ApplicationInfo -import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.ApplicationNamesInfo import com.intellij.openapi.application.PathManager import com.intellij.openapi.components.Service -import com.intellij.openapi.components.service import com.intellij.openapi.diagnostic.JulLogger import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.diagnostic.RollingFileHandler import com.intellij.openapi.progress.ProgressIndicator -import com.intellij.openapi.progress.ProgressManagerListener -import com.intellij.openapi.progress.Task -import com.intellij.openapi.progress.util.ProgressIndicatorBase +import com.intellij.openapi.project.configuration.ChannelingProgressIndicator +import com.intellij.openapi.project.configuration.HeadlessLogging import com.intellij.openapi.util.io.findOrCreateFile -import com.intellij.openapi.util.text.Formats -import com.intellij.openapi.wm.ex.ProgressIndicatorEx import com.intellij.platform.ide.bootstrap.logEssentialInfoAboutIde import com.intellij.platform.util.progress.createProgressPipe -import com.intellij.util.application import com.intellij.util.lazyPub import kotlinx.coroutines.* import kotlinx.coroutines.channels.BufferOverflow @@ -42,28 +35,33 @@ import kotlin.time.Duration.Companion.milliseconds object WarmupLogger { fun logInfo(message: String) { ConsoleLog.info(message) - warmupLogger?.info(message) } fun logError(message: String, t: Throwable? = null) { ConsoleLog.error(message, t) - warmupLogger?.error(message, t) } internal fun logStructured(message: StructuredMessage) { ConsoleLog.info(message.fullMessage) - warmupLogger?.info(message.contractedMessage) } } -internal fun initLogger(args: List) { - val logger = warmupLogger ?: return +internal fun CoroutineScope.initLogger(args: List): Job { + val logger = warmupLogger ?: return Job() val info = ApplicationInfo.getInstance() val buildDate = SimpleDateFormat("dd MMM yyyy HH:mm", Locale.US).format(info.buildDate.time) logEssentialInfoAboutIde(log = logger, appInfo = info, args = args) - val connection = ApplicationManager.getApplication().messageBus.connect() - connection.subscribe(ProgressManagerListener.TOPIC, WarmupProgressListener()) logger.info("IDE: ${ApplicationNamesInfo.getInstance().fullProductName} (build #${info.build.asString()}, ${buildDate})") + return launch { + HeadlessLogging.loggingFlow().collect { (level, message) -> + val messageRepresentation = message.representation() + when (level) { + HeadlessLogging.SeverityKind.Info -> ConsoleLog.info(messageRepresentation) + HeadlessLogging.SeverityKind.Warning -> ConsoleLog.warn(messageRepresentation) + HeadlessLogging.SeverityKind.Fatal -> ConsoleLog.error(messageRepresentation) + } + } + } } @@ -99,42 +97,7 @@ internal fun progressStateText(fraction: Double?, text: String?, details: String return StructuredMessage(verboseReport, shortReport) } -private class ChannelingProgressIndicator(private val prefix: String) : ProgressIndicatorBase() { - override fun setIndeterminate(indeterminate: Boolean) { - super.setIndeterminate(indeterminate) - offerState() - } - override fun setFraction(fraction: Double) { - super.setFraction(fraction) - offerState() - } - - override fun setText(text: String?) { - super.setText(text) - super.setText2("") - offerState() - } - - override fun setText2(text: String?) { - super.setText2(text) - offerState() - } - - private fun offerState() { - val messages = ApplicationManager.getApplication().service().messages - val progressState = progressStateText( - fraction = if (isIndeterminate) null else fraction, - text = text, - details = text2, - ) ?: return - val actualPrefix = if (prefix.isEmpty()) "" else "[$prefix]: " - messages.tryEmit(progressState.copy( - contractedMessage = actualPrefix + progressState.contractedMessage, - fullMessage = actualPrefix + progressState.fullMessage, - )) - } -} /** * Installs a progress reporter that sends the information about progress to the stdout instead of UI. @@ -191,13 +154,8 @@ private val loggerFactory: WarmupLoggerFactory? by lazyPub { } private val warmupLogger: Logger? by lazyPub { - if (WarmupStatus.currentStatus() != WarmupStatus.InProgress) { - null - } - else { - val instance = loggerFactory?.getLoggerInstance("Warmup") ?: return@lazyPub null - instance - } + val instance = loggerFactory?.getLoggerInstance("Warmup") ?: return@lazyPub null + instance } internal data class StructuredMessage( @@ -222,28 +180,6 @@ private class WarmupLoggingService(scope: CoroutineScope) { } } -class WarmupProgressListener : ProgressManagerListener { - - private val taskDurationMap = ConcurrentHashMap() - - - override fun beforeTaskStart(task: Task, indicator: ProgressIndicator) { - if (indicator !is ProgressIndicatorEx) { - return - } - WarmupLogger.logInfo("[IDE]: Task '${task.title}' started") - taskDurationMap[System.identityHashCode(task)] = System.currentTimeMillis() - indicator.addStateDelegate(ChannelingProgressIndicator("IDE")) - super.beforeTaskStart(task, indicator) - } - - override fun afterTaskFinished(task: Task) { - val currentTime = System.currentTimeMillis() - val startTime = taskDurationMap.remove(System.identityHashCode(task)) - val elapsedTimeSuffix = if (startTime == null) "" else " in ${Formats.formatDuration(currentTime - startTime)}" - WarmupLogger.logInfo("[IDE]: Task '${task.title}' ended" + elapsedTimeSuffix) - } -} internal fun dumpThreadsAfterConfiguration() { val dump = ThreadDumper.getThreadDumpInfo(ThreadDumper.getThreadInfos(), false) diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/GradleWarmupConfigurator.kt b/plugins/gradle/src/org/jetbrains/plugins/gradle/GradleWarmupConfigurator.kt index 48123d419a60..2cd042dda40f 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/GradleWarmupConfigurator.kt +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/GradleWarmupConfigurator.kt @@ -6,26 +6,23 @@ import com.intellij.ide.CommandLineProgressReporterElement import com.intellij.ide.environment.EnvironmentService import com.intellij.ide.impl.ProjectOpenKeyProvider import com.intellij.ide.warmup.WarmupConfigurator -import com.intellij.ide.warmup.WarmupLogger -import com.intellij.ide.warmup.WarmupStatus -import com.intellij.openapi.application.PathManager import com.intellij.openapi.components.service import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.externalSystem.autoimport.AutoImportProjectTracker import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskId import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskNotificationListener -import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskType import com.intellij.openapi.externalSystem.service.notification.ExternalSystemProgressNotificationManager import com.intellij.openapi.externalSystem.service.project.manage.ProjectDataImportListener import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil import com.intellij.openapi.progress.blockingContextScope import com.intellij.openapi.project.Project import com.intellij.openapi.util.io.FileUtil -import com.intellij.util.io.createParentDirectories import kotlinx.coroutines.CoroutineScope -import org.jetbrains.plugins.gradle.service.notification.ExternalAnnotationsProgressNotificationListener import org.jetbrains.plugins.gradle.service.notification.ExternalAnnotationsProgressNotificationManager -import org.jetbrains.plugins.gradle.service.notification.ExternalAnnotationsTaskId +import org.jetbrains.plugins.gradle.service.project.GradleHeadlessLoggingProjectActivity.LoggingNotificationListener +import org.jetbrains.plugins.gradle.service.project.GradleHeadlessLoggingProjectActivity.StateExternalAnnotationNotificationListener +import org.jetbrains.plugins.gradle.service.project.GradleHeadlessLoggingProjectActivity.StateNotificationListener +import org.jetbrains.plugins.gradle.service.project.isGradleProjectResolveTask import org.jetbrains.plugins.gradle.service.project.open.createLinkSettings import org.jetbrains.plugins.gradle.settings.GradleImportHintService import org.jetbrains.plugins.gradle.settings.GradleSettings @@ -37,14 +34,10 @@ import java.nio.file.Paths import java.util.concurrent.TimeUnit import java.util.concurrent.atomic.AtomicReference import kotlin.coroutines.coroutineContext -import kotlin.io.path.appendText -import kotlin.io.path.createFile -import kotlin.io.path.div private val LOG = logger() -private val gradleLogWriterPath = Path.of(PathManager.getLogPath()) / "gradle-import.log" private const val DISABLE_GRADLE_AUTO_IMPORT = "external.system.auto.import.disabled" private const val DISABLE_GRADLE_JDK_FIX = "gradle.auto.auto.jdk.fix.disabled" @@ -156,66 +149,9 @@ class GradleWarmupConfigurator : WarmupConfigurator { return false } - class StateExternalAnnotationNotificationListener : ExternalAnnotationsProgressNotificationListener { - override fun onStartResolve(id: ExternalAnnotationsTaskId) { - LOG.info("Gradle resolving external annotations started ${id.projectId}") - } - override fun onFinishResolve(id: ExternalAnnotationsTaskId) { - LOG.info("Gradle resolving external annotations completed ${id.projectId}") - } - } - class StateNotificationListener( - private val project: Project, private val scope: CoroutineScope - ) : ExternalSystemTaskNotificationListener { - - override fun onSuccess(id: ExternalSystemTaskId) { - if (!id.isGradleProjectResolveTask()) return - LOG.info("Gradle resolve stage finished with success: ${id.ideProjectId}") - - project.messageBus.connect(scope) - .subscribe(ProjectDataImportListener.TOPIC, object : ProjectDataImportListener { - override fun onImportStarted(projectPath: String?) { - LOG.info("Gradle data import stage started: ${id.ideProjectId}") - } - - override fun onImportFinished(projectPath: String?) { - LOG.info("Gradle data import stage finished with success: ${id.ideProjectId}") - } - - override fun onFinalTasksFinished(projectPath: String?) { - LOG.info("Gradle data import(final tasks) stage finished: ${id.ideProjectId}") - } - - override fun onFinalTasksStarted(projectPath: String?) { - LOG.info("Gradle data import(final tasks) stage started: ${id.ideProjectId}") - } - - override fun onImportFailed(projectPath: String?, t: Throwable) { - WarmupLogger.fatalError(t.message ?: "Gradle import finished with error", t) - LOG.info("Gradle data import stage finished with failure: ${id.ideProjectId}") - } - }) - } - - override fun onFailure(id: ExternalSystemTaskId, e: Exception) { - if (!id.isGradleProjectResolveTask()) return - WarmupLogger.fatalError(e.message ?: "Gradle import finished with error", e) - LOG.error("Gradle resolve stage finished with failure ${id.ideProjectId}", e) - } - - override fun onCancel(id: ExternalSystemTaskId) { - if (!id.isGradleProjectResolveTask()) return - LOG.error("Gradle resolve stage canceled ${id.ideProjectId}") - } - - override fun onStart(id: ExternalSystemTaskId, workingDir: String) { - if (!id.isGradleProjectResolveTask()) return - LOG.info("Gradle resolve stage started ${id.ideProjectId}, working dir: $workingDir") - } - } class ImportErrorListener( @@ -246,39 +182,6 @@ class GradleWarmupConfigurator : WarmupConfigurator { } } - class LoggingNotificationListener(val logger: CommandLineInspectionProgressReporter?) : ExternalSystemTaskNotificationListener { - - private val logPath = try { - gradleLogWriterPath.createParentDirectories().createFile() - } - catch (e: java.nio.file.FileAlreadyExistsException) { - gradleLogWriterPath - } - - override fun onTaskOutput(id: ExternalSystemTaskId, text: String, stdOut: Boolean) { - val gradleText = (if (stdOut) "" else "STDERR: ") + text - logPath.appendText(gradleText) - val croppedMessage = processMessage(gradleText) - if (croppedMessage != null) { - logger?.reportMessage(1, croppedMessage) - } - } - - private fun processMessage(gradleText: String): String? { - if (WarmupStatus.currentStatus() != WarmupStatus.InProgress) { - return gradleText - } - val cropped = gradleText.trimStart('\r').trimEnd('\n') - if (cropped.startsWith("Download")) { - // we don't want to be flooded by a ton of download messages, so we'll print only final message - if (cropped.contains(" took ")) { - return cropped - } - return null - } - return cropped - } - } } internal fun prepareGradleConfiguratorEnvironment(logger: CommandLineInspectionProgressReporter?) { @@ -288,11 +191,9 @@ internal fun prepareGradleConfiguratorEnvironment(logger: CommandLineInspectionP System.setProperty(DISABLE_UPDATE_ANDROID_SDK_LOCAL_PROPERTIES, true.toString()) val progressManager = ExternalSystemProgressNotificationManager.getInstance() if (logger != null) { - progressManager.addNotificationListener(GradleWarmupConfigurator.LoggingNotificationListener(logger)) + progressManager.addNotificationListener(LoggingNotificationListener()) } Unit } -private fun ExternalSystemTaskId.isGradleProjectResolveTask() = this.projectSystemId == GradleConstants.SYSTEM_ID && - this.type == ExternalSystemTaskType.RESOLVE_PROJECT \ No newline at end of file diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/service/project/GradleHeadlessLoggingProjectActivity.kt b/plugins/gradle/src/org/jetbrains/plugins/gradle/service/project/GradleHeadlessLoggingProjectActivity.kt index 6cf6d9aed6dd..f9041087dba3 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/service/project/GradleHeadlessLoggingProjectActivity.kt +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/service/project/GradleHeadlessLoggingProjectActivity.kt @@ -1,22 +1,32 @@ // Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package org.jetbrains.plugins.gradle.service.project -import com.intellij.ide.CommandLineInspectionProgressReporter -import com.intellij.ide.warmup.WarmupLogger -import com.intellij.ide.warmup.WarmupStatus -import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.application.PathManager +import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskId +import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskNotificationListener +import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskType import com.intellij.openapi.externalSystem.service.notification.ExternalSystemProgressNotificationManager +import com.intellij.openapi.externalSystem.service.project.manage.ProjectDataImportListener import com.intellij.openapi.project.Project +import com.intellij.openapi.project.configuration.HeadlessLogging import com.intellij.openapi.startup.ProjectActivity -import com.intellij.openapi.util.registry.Registry +import com.intellij.util.application import com.intellij.util.awaitCancellationAndInvoke +import com.intellij.util.io.createParentDirectories import kotlinx.coroutines.CoroutineScope -import org.jetbrains.plugins.gradle.GradleWarmupConfigurator +import kotlinx.coroutines.launch +import org.jetbrains.plugins.gradle.service.notification.ExternalAnnotationsProgressNotificationListener import org.jetbrains.plugins.gradle.service.notification.ExternalAnnotationsProgressNotificationManager +import org.jetbrains.plugins.gradle.service.notification.ExternalAnnotationsTaskId +import org.jetbrains.plugins.gradle.util.GradleConstants +import java.nio.file.Path +import kotlin.io.path.appendText +import kotlin.io.path.createFile +import kotlin.io.path.div class GradleHeadlessLoggingProjectActivity(val scope: CoroutineScope) : ProjectActivity { override suspend fun execute(project: Project) { - if (WarmupStatus.currentStatus() != WarmupStatus.InProgress || !Registry.`is`("ide.warmup.use.predicates")) { + if (!application.isHeadlessEnvironment || application.isUnitTestMode) { return } val progressManager = ExternalSystemProgressNotificationManager.getInstance() @@ -26,42 +36,132 @@ class GradleHeadlessLoggingProjectActivity(val scope: CoroutineScope) : ProjectA } private fun addTaskNotificationListener(progressManager: ExternalSystemProgressNotificationManager) { - val listener = GradleWarmupConfigurator.LoggingNotificationListener(object : CommandLineInspectionProgressReporter { - override fun reportError(message: String?) { - if (message == null) { - return - } - WarmupLogger.fatalError(message, null) - } - - override fun reportMessage(minVerboseLevel: Int, message: String?) { - if (message == null) { - return - } - WarmupLogger.message(message) - } - }) + val listener = LoggingNotificationListener() progressManager.addNotificationListener(listener) - scope.awaitCancellationAndInvoke { - progressManager.removeNotificationListener(listener) + scope.launch { + awaitCancellationAndInvoke { + progressManager.removeNotificationListener(listener) + } } } private fun addStateNotificationListener(project: Project, progressManager: ExternalSystemProgressNotificationManager) { - val notificationListener = GradleWarmupConfigurator.StateNotificationListener(project, scope) + val notificationListener = StateNotificationListener(project, scope) progressManager.addNotificationListener(notificationListener) - scope.awaitCancellationAndInvoke { - progressManager.removeNotificationListener(notificationListener) + scope.launch { + awaitCancellationAndInvoke { + progressManager.removeNotificationListener(notificationListener) + } } } private fun addAnnotationListener() { val externalAnnotationsNotificationManager = ExternalAnnotationsProgressNotificationManager.getInstance() - val externalAnnotationsProgressListener = GradleWarmupConfigurator.StateExternalAnnotationNotificationListener() + val externalAnnotationsProgressListener = StateExternalAnnotationNotificationListener() externalAnnotationsNotificationManager.addNotificationListener(externalAnnotationsProgressListener) - scope.awaitCancellationAndInvoke { - externalAnnotationsNotificationManager.removeNotificationListener(externalAnnotationsProgressListener) + scope.launch { + awaitCancellationAndInvoke { + externalAnnotationsNotificationManager.removeNotificationListener(externalAnnotationsProgressListener) + } } } -} \ No newline at end of file + + class StateExternalAnnotationNotificationListener : ExternalAnnotationsProgressNotificationListener { + + override fun onStartResolve(id: ExternalAnnotationsTaskId) { + HeadlessLogging.logMessage(gradlePrefix + "Gradle resolving external annotations started ${id.projectId}") + } + + override fun onFinishResolve(id: ExternalAnnotationsTaskId) { + HeadlessLogging.logMessage(gradlePrefix + "Gradle resolving external annotations finished ${id.projectId}") + } + } + + class StateNotificationListener( + private val project: Project, private val scope: CoroutineScope + ) : ExternalSystemTaskNotificationListener { + + override fun onSuccess(id: ExternalSystemTaskId) { + if (!id.isGradleProjectResolveTask()) return + HeadlessLogging.logMessage(gradlePrefix + "Gradle resolve stage finished with success: ${id.ideProjectId}") + + project.messageBus.connect(scope) + .subscribe(ProjectDataImportListener.TOPIC, object : ProjectDataImportListener { + override fun onImportStarted(projectPath: String?) { + HeadlessLogging.logMessage(gradlePrefix + "Gradle data import stage started: ${id.ideProjectId}") + } + + override fun onImportFinished(projectPath: String?) { + HeadlessLogging.logMessage(gradlePrefix + "Gradle data import stage finished with success: ${id.ideProjectId}") + } + + override fun onFinalTasksFinished(projectPath: String?) { + HeadlessLogging.logMessage(gradlePrefix + "Gradle data import(final tasks) stage finished: ${id.ideProjectId}") + } + + override fun onFinalTasksStarted(projectPath: String?) { + HeadlessLogging.logMessage(gradlePrefix + "Gradle data import(final tasks) stage started: ${id.ideProjectId}") + } + + override fun onImportFailed(projectPath: String?, t: Throwable) { + HeadlessLogging.logFatalError(t) + } + }) + } + + override fun onFailure(id: ExternalSystemTaskId, e: Exception) { + if (!id.isGradleProjectResolveTask()) return + HeadlessLogging.logFatalError(e) + } + + override fun onCancel(id: ExternalSystemTaskId) { + if (!id.isGradleProjectResolveTask()) return + HeadlessLogging.logWarning(gradlePrefix + "Gradle resolve stage canceled ${id.ideProjectId}") + } + + override fun onStart(id: ExternalSystemTaskId, workingDir: String) { + if (!id.isGradleProjectResolveTask()) return + HeadlessLogging.logMessage(gradlePrefix + "Gradle resolve stage started ${id.ideProjectId}, working dir: $workingDir") + } + } + + class LoggingNotificationListener : ExternalSystemTaskNotificationListener { + + private val logPath = try { + gradleLogWriterPath.createParentDirectories().createFile() + } + catch (e: java.nio.file.FileAlreadyExistsException) { + gradleLogWriterPath + } + + override fun onTaskOutput(id: ExternalSystemTaskId, text: String, stdOut: Boolean) { + val gradleText = (if (stdOut) "" else "STDERR: ") + text + logPath.appendText(gradleText) + val croppedMessage = processMessage(gradleText) + if (croppedMessage != null) { + HeadlessLogging.logMessage(gradlePrefix + croppedMessage) + } + } + + private fun processMessage(gradleText: String): String? { + val cropped = gradleText.trimStart('\r').trimEnd('\n') + if (cropped.startsWith("Download")) { + // we don't want to be flooded by a ton of download messages, so we'll print only final message + if (cropped.contains(" took ")) { + return cropped + } + return null + } + return cropped + } + } + +} + +internal fun ExternalSystemTaskId.isGradleProjectResolveTask() = this.projectSystemId == GradleConstants.SYSTEM_ID && + this.type == ExternalSystemTaskType.RESOLVE_PROJECT + +private val gradleLogWriterPath = Path.of(PathManager.getLogPath()) / "gradle-import.log" + +private val gradlePrefix = "[Gradle]: " \ No newline at end of file diff --git a/plugins/performanceTesting/core/src/com/jetbrains/performancePlugin/commands/AwaitCompleteProjectConfigurationCommand.kt b/plugins/performanceTesting/core/src/com/jetbrains/performancePlugin/commands/AwaitCompleteProjectConfigurationCommand.kt index b8929facaef8..7aa642f6a79a 100644 --- a/plugins/performanceTesting/core/src/com/jetbrains/performancePlugin/commands/AwaitCompleteProjectConfigurationCommand.kt +++ b/plugins/performanceTesting/core/src/com/jetbrains/performancePlugin/commands/AwaitCompleteProjectConfigurationCommand.kt @@ -2,8 +2,8 @@ package com.jetbrains.performancePlugin.commands import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.diagnostic.logger +import com.intellij.openapi.project.configuration.awaitCompleteProjectConfiguration import com.intellij.openapi.ui.playback.PlaybackContext -import com.intellij.util.awaitCompleteProjectConfiguration private val LOG: Logger