diff --git a/community-resources/intellij.idea.community.customization.iml b/community-resources/intellij.idea.community.customization.iml index a0a5da7aebcc..a73828cc90ad 100644 --- a/community-resources/intellij.idea.community.customization.iml +++ b/community-resources/intellij.idea.community.customization.iml @@ -26,6 +26,5 @@ - \ No newline at end of file diff --git a/platform/core-impl/src/com/intellij/openapi/application/ex/ApplicationManagerEx.java b/platform/core-impl/src/com/intellij/openapi/application/ex/ApplicationManagerEx.java index 0c93ba2a0247..0c8878eaa9b5 100644 --- a/platform/core-impl/src/com/intellij/openapi/application/ex/ApplicationManagerEx.java +++ b/platform/core-impl/src/com/intellij/openapi/application/ex/ApplicationManagerEx.java @@ -3,16 +3,12 @@ package com.intellij.openapi.application.ex; import com.intellij.openapi.application.ApplicationManager; import com.intellij.util.indexing.impl.IndexDebugProperties; -import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.TestOnly; -import java.util.concurrent.CountDownLatch; - public final class ApplicationManagerEx extends ApplicationManager { public static final String IS_INTERNAL_PROPERTY = "idea.is.internal"; private static volatile boolean inStressTest; - private static volatile CountDownLatch isInitialStart; public static ApplicationEx getApplicationEx() { return (ApplicationEx)ourApplication; @@ -31,19 +27,4 @@ public final class ApplicationManagerEx extends ApplicationManager { inStressTest = value; IndexDebugProperties.IS_IN_STRESS_TESTS = value; } - - public static void setInitialStart() { - isInitialStart = new CountDownLatch(1); - } - - public static boolean isInitialStart() { - return getInitialStartState() != null; - } - - @Nullable - public static CountDownLatch getInitialStartState() { - return isInitialStart; - } - - } diff --git a/platform/platform-impl/src/com/intellij/openapi/application/ConfigImportHelper.java b/platform/platform-impl/src/com/intellij/openapi/application/ConfigImportHelper.java index 3ba62691b732..d23e8d55e55b 100644 --- a/platform/platform-impl/src/com/intellij/openapi/application/ConfigImportHelper.java +++ b/platform/platform-impl/src/com/intellij/openapi/application/ConfigImportHelper.java @@ -146,7 +146,7 @@ public final class ConfigImportHelper { log.error("Couldn't backup current config or delete current config directory", e); } } - else if (!IdeStartupWizardKt.isStartupWizardEnabled()) { + else if (!Boolean.getBoolean("intellij.startup.wizard")) { if (shouldAskForConfig()) { oldConfigDirAndOldIdePath = showDialogAndGetOldConfigPath(guessedOldConfigDirs.getPaths()); importScenarioStatistics = SHOW_DIALOG_REQUESTED_BY_PROPERTY; diff --git a/platform/platform-impl/src/com/intellij/openapi/application/IdeStartupWizard.kt b/platform/platform-impl/src/com/intellij/openapi/application/IdeStartupWizard.kt deleted file mode 100644 index 6ae02239262f..000000000000 --- a/platform/platform-impl/src/com/intellij/openapi/application/IdeStartupWizard.kt +++ /dev/null @@ -1,47 +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.openapi.application - -import com.intellij.openapi.application.ex.ApplicationManagerEx -import com.intellij.openapi.diagnostic.Logger -import com.intellij.openapi.diagnostic.logger -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.runInterruptible -import kotlinx.coroutines.withContext - -private const val INTELLIJ_STARTUP_WIZARD_CLASS_PROPERTY = "intellij.startup-wizard.class" - -private val LOG: Logger - get() = logger() - -internal val isStartupWizardEnabled: Boolean = - !System.getProperty(INTELLIJ_STARTUP_WIZARD_CLASS_PROPERTY).isNullOrBlank() - -internal suspend fun runStartupWizard() { - if (!isStartupWizardEnabled) return - if (!ConfigImportHelper.isNewUser()) return - - LOG.info("Entering startup wizard workflow.") - - waitForAppManagerInitialState() - - val className = System.getProperty(INTELLIJ_STARTUP_WIZARD_CLASS_PROPERTY) - LOG.info("Passing execution control to $className.") - val wizardClass = Class.forName(className) - val instance = wizardClass.getDeclaredConstructor().newInstance() as IdeStartupWizard - instance.run() -} - -private suspend fun waitForAppManagerInitialState() { - val latch = ApplicationManagerEx.getInitialStartState() - if (latch == null) error("Cannot get initial startup state") - LOG.info("Waiting for app manager initial state.") - withContext(Dispatchers.IO) { - runInterruptible { - latch.await() - } - } -} - -interface IdeStartupWizard { - suspend fun run() -} diff --git a/platform/platform-impl/src/com/intellij/platform/ide/bootstrap/ApplicationLoader.kt b/platform/platform-impl/src/com/intellij/platform/ide/bootstrap/ApplicationLoader.kt index 65f65a9cb7d1..1778fca17761 100644 --- a/platform/platform-impl/src/com/intellij/platform/ide/bootstrap/ApplicationLoader.kt +++ b/platform/platform-impl/src/com/intellij/platform/ide/bootstrap/ApplicationLoader.kt @@ -17,7 +17,10 @@ import com.intellij.ide.ui.IconMapLoader import com.intellij.ide.ui.LafManager import com.intellij.ide.ui.UISettings import com.intellij.ide.ui.laf.LafManagerImpl -import com.intellij.idea.* +import com.intellij.idea.AppExitCodes +import com.intellij.idea.AppMode +import com.intellij.idea.IdeStarter +import com.intellij.idea.StartupErrorReporter import com.intellij.internal.statistic.collectors.fus.actions.persistence.ActionsEventLogGroup import com.intellij.openapi.application.* import com.intellij.openapi.application.ex.ApplicationEx @@ -160,6 +163,7 @@ internal suspend fun loadApp(app: ApplicationImpl, appRegisteredJob.join() initConfigurationStoreJob.join() + val appInitializedListenerJob = launch { val appInitializedListeners = appInitListeners.await() span("app initialized callback") { @@ -167,6 +171,7 @@ internal suspend fun loadApp(app: ApplicationImpl, callAppInitialized(listeners = appInitializedListeners, asyncScope = app.coroutineScope) } } + asyncScope.launch { launch(CoroutineName("checkThirdPartyPluginsAllowed")) { checkThirdPartyPluginsAllowed() diff --git a/platform/platform-impl/src/com/intellij/platform/ide/bootstrap/IdeStartupWizard.kt b/platform/platform-impl/src/com/intellij/platform/ide/bootstrap/IdeStartupWizard.kt new file mode 100644 index 000000000000..a745045b0073 --- /dev/null +++ b/platform/platform-impl/src/com/intellij/platform/ide/bootstrap/IdeStartupWizard.kt @@ -0,0 +1,47 @@ +// 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.platform.ide.bootstrap + +import com.intellij.diagnostic.PluginException +import com.intellij.openapi.application.Application +import com.intellij.openapi.diagnostic.logger +import com.intellij.openapi.extensions.impl.ExtensionPointImpl +import com.intellij.platform.diagnostic.telemetry.impl.span +import kotlinx.coroutines.Job +import org.jetbrains.annotations.ApiStatus.Internal + +internal suspend fun runStartupWizard(isInitialStart: Job, app: Application) { + val log = logger() + + log.info("Entering startup wizard workflow.") + + span("app manager initial state waiting") { + isInitialStart.join() + } + + val point = app.extensionArea + .getExtensionPoint("com.intellij.ideStartupWizard") as ExtensionPointImpl + for (adapter in point.sortedAdapters) { + val pluginDescriptor = adapter.pluginDescriptor + if (!pluginDescriptor.isBundled) { + log.error(PluginException("ideStartupWizard extension can be implemented only by a bundled plugin", pluginDescriptor.pluginId)) + continue + } + + try { + log.info("Passing execution control to $adapter.") + span("${adapter.assignableToClassName}.run") { + adapter.createInstance(app)?.run() + } + break + } + catch (e: Throwable) { + log.error(PluginException(e, pluginDescriptor.pluginId)) + } + } + point.reset() +} + +@Internal +interface IdeStartupWizard { + suspend fun run() +} diff --git a/platform/platform-impl/src/com/intellij/platform/ide/bootstrap/main.kt b/platform/platform-impl/src/com/intellij/platform/ide/bootstrap/main.kt index 9587b751f181..c3812164cda9 100644 --- a/platform/platform-impl/src/com/intellij/platform/ide/bootstrap/main.kt +++ b/platform/platform-impl/src/com/intellij/platform/ide/bootstrap/main.kt @@ -22,7 +22,6 @@ import com.intellij.openapi.application.impl.* import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.diagnostic.getOrLogException import com.intellij.openapi.diagnostic.logger -import com.intellij.openapi.diagnostic.runAndLogException import com.intellij.openapi.util.Disposer import com.intellij.openapi.util.IconLoader import com.intellij.openapi.util.ShutDownTracker @@ -64,6 +63,7 @@ import java.util.function.BiFunction import java.util.logging.ConsoleHandler import java.util.logging.Level import javax.swing.* +import kotlin.concurrent.Volatile import kotlin.system.exitProcess internal const val IDE_STARTED: String = "------------------------------------------------------ IDE STARTED ------------------------------------------------------" @@ -187,34 +187,41 @@ fun CoroutineScope.startApplication(args: List, val euaDocumentDeferred = async { loadEuaDocument(appInfoDeferred) } - val configImportDeferred = async { - if (!isHeadless && configImportNeededDeferred.await()) { - initLafJob.join() - val log = logDeferred.await() - importConfig( - args = args, - targetDirectoryToImportConfig = targetDirectoryToImportConfig ?: PathManager.getConfigDir(), - log = log, - appStarter = appStarterDeferred.await(), - euaDocumentDeferred = euaDocumentDeferred, - ) + val configImportDeferred: Deferred = async { + if (isHeadless || !configImportNeededDeferred.await()) { + return@async null + } - if (ConfigImportHelper.isNewUser() && System.getProperty("ide.experimental.ui") == null) { + initLafJob.join() + val log = logDeferred.await() + importConfig( + args = args, + targetDirectoryToImportConfig = targetDirectoryToImportConfig ?: PathManager.getConfigDir(), + log = log, + appStarter = appStarterDeferred.await(), + euaDocumentDeferred = euaDocumentDeferred, + ) + + if (ConfigImportHelper.isNewUser()) { + if (System.getProperty("ide.experimental.ui") == null) { runCatching { EarlyAccessRegistryManager.setAndFlush(mapOf("ide.experimental.ui" to "true")) }.getOrLogException(log) } - if (isStartupWizardEnabled && ConfigImportHelper.isNewUser()) { - log.info("Will enter initial app wizard flow.") - ApplicationManagerEx.setInitialStart() - } + log.info("Will enter initial app wizard flow.") + val result = CompletableDeferred() + isInitialStart = result + result + } + else { + null } } val pluginSetDeferred = async { // plugins cannot be loaded when a config import is needed, because plugins may be added after importing - configImportDeferred.await() + configImportDeferred.join() PluginManagerCore.scheduleDescriptorLoading(coroutineScope = this@startApplication, zipFilePoolDeferred = zipFilePoolDeferred, @@ -245,7 +252,7 @@ fun CoroutineScope.startApplication(args: List, Class.forName(OpenTelemetrySdkBuilder::class.java.name, true, classLoader) } - val appLoaded = async { + val appLoaded = launch { checkSystemDirJob.join() val classBeforeAppProperty = System.getProperty(IDEA_CLASS_BEFORE_APPLICATION_PROPERTY) @@ -262,30 +269,26 @@ fun CoroutineScope.startApplication(args: List, val starter = loadApp(app = app, initAwtToolkitAndEventQueueJob = initEventQueueJob, pluginSetDeferred = pluginSetDeferred, - appInfoDeferred = appInfoDeferred, + appInfoDeferred = appInfoDeferred, euaDocumentDeferred = euaDocumentDeferred, asyncScope = this@startApplication, initLafJob = initLafJob, logDeferred = logDeferred, appRegisteredJob = appRegisteredJob, args = args.filterNot { CommandLineArgs.isKnownArgument(it) }) - if (isStartupWizardEnabled) { - // Initial startup wizard relies on config import being performed before this async ends. - configImportDeferred.await() - } - else { - // In case of startup wizard, this will be executed at a stage separated from app loading. - executeApplicationStarter(starter, args) - } - return@async starter - } - - if (isStartupWizardEnabled) { - launch { - val starter = appLoaded.await() - val log = logDeferred.await() - log.runAndLogException { runStartupWizard() } - executeApplicationStarter(starter, args) + // out of appLoaded scope + this@startApplication.launch { + val isInitialStart = configImportDeferred.await() + // appLoaded not only provides starter, but also loads app, that's why it is here + if (isInitialStart != null) { + val log = logDeferred.await() + runCatching { + span("startup wizard run") { + runStartupWizard(isInitialStart = isInitialStart, app = ApplicationManager.getApplication()) + } + }.getOrLogException(log) + } + executeApplicationStarter(starter = starter, args = args) } } @@ -311,6 +314,10 @@ fun CoroutineScope.startApplication(args: List, } } +@Volatile +@JvmField +internal var isInitialStart: CompletableDeferred? = null + private fun CoroutineScope.scheduleEnableCoroutineDumpAndJstack() { if (!System.getProperty("idea.enable.coroutine.dump", "true").toBoolean()) { return diff --git a/platform/platform-resources/src/META-INF/PlatformExtensionPoints.xml b/platform/platform-resources/src/META-INF/PlatformExtensionPoints.xml index baf8b5e72711..b66f1b861b00 100644 --- a/platform/platform-resources/src/META-INF/PlatformExtensionPoints.xml +++ b/platform/platform-resources/src/META-INF/PlatformExtensionPoints.xml @@ -141,6 +141,7 @@ + diff --git a/plugins/ide-startup/importSettings/intellij.ide.startup.importSettings.iml b/plugins/ide-startup/importSettings/intellij.ide.startup.importSettings.iml index 5b71b579933a..b58eb2c688c4 100644 --- a/plugins/ide-startup/importSettings/intellij.ide.startup.importSettings.iml +++ b/plugins/ide-startup/importSettings/intellij.ide.startup.importSettings.iml @@ -3,12 +3,12 @@ + - \ No newline at end of file diff --git a/plugins/ide-startup/importSettings/resources/intellij.ide.startup.importSettings.xml b/plugins/ide-startup/importSettings/resources/intellij.ide.startup.importSettings.xml new file mode 100644 index 000000000000..00cc4c20e46b --- /dev/null +++ b/plugins/ide-startup/importSettings/resources/intellij.ide.startup.importSettings.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/plugins/ide-startup/importSettings/src/intellij/ide/startup/importSettings/IdeStartupWizardImpl.kt b/plugins/ide-startup/importSettings/src/com/intellij/ide/startup/importSettings/IdeStartupWizardImpl.kt similarity index 55% rename from plugins/ide-startup/importSettings/src/intellij/ide/startup/importSettings/IdeStartupWizardImpl.kt rename to plugins/ide-startup/importSettings/src/com/intellij/ide/startup/importSettings/IdeStartupWizardImpl.kt index e63d0f7ed51d..3d30762e0790 100644 --- a/plugins/ide-startup/importSettings/src/intellij/ide/startup/importSettings/IdeStartupWizardImpl.kt +++ b/plugins/ide-startup/importSettings/src/com/intellij/ide/startup/importSettings/IdeStartupWizardImpl.kt @@ -1,14 +1,19 @@ -package intellij.ide.startup.importSettings +package com.intellij.ide.startup.importSettings import com.intellij.openapi.application.EDT -import com.intellij.openapi.application.IdeStartupWizard +import com.intellij.openapi.extensions.ExtensionNotApplicableException +import com.intellij.platform.ide.bootstrap.IdeStartupWizard import com.intellij.ui.components.dialog import com.intellij.ui.dsl.builder.panel import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext -@Suppress("unused") // instantiated by reflection -class IdeStartupWizardImpl : IdeStartupWizard { +private class IdeStartupWizardImpl : IdeStartupWizard { + init { + if (!System.getProperty("intellij.startup.wizard", "false").toBoolean()) { + throw ExtensionNotApplicableException.create() + } + } @Suppress("HardCodedStringLiteral") // temporary override suspend fun run() { diff --git a/plugins/ide-startup/importSettings/vscode/src/readme.md b/plugins/ide-startup/importSettings/vscode/src/readme.md new file mode 100644 index 000000000000..e69de29bb2d1