diff --git a/src/main/kotlin/com/jetbrains/rider/aspire/MigrateBuildSolutionTaskActivity.kt b/src/main/kotlin/com/jetbrains/rider/aspire/MigrateBuildSolutionTaskActivity.kt new file mode 100644 index 00000000..dc197740 --- /dev/null +++ b/src/main/kotlin/com/jetbrains/rider/aspire/MigrateBuildSolutionTaskActivity.kt @@ -0,0 +1,27 @@ +package com.jetbrains.rider.aspire + +import com.intellij.execution.RunManager +import com.intellij.openapi.project.Project +import com.intellij.openapi.startup.ProjectActivity +import com.jetbrains.rider.build.tasks.BuildProjectBeforeRunTask +import com.jetbrains.rider.build.tasks.BuildSolutionBeforeRunTaskProvider + +class MigrateBuildSolutionTaskActivity : ProjectActivity { + override suspend fun execute(project: Project) { + RunManager.getInstance(project).allSettings.forEach { setting -> + if (setting.type.id == "AspireHostConfiguration") { + val buildSolutionTasks = setting.configuration.beforeRunTasks.filter { + it.providerId == BuildSolutionBeforeRunTaskProvider.providerId + } + if (buildSolutionTasks.isNotEmpty()) { + buildSolutionTasks.forEach { + setting.configuration.beforeRunTasks.remove(it) + } + val buildSolutionTask = BuildProjectBeforeRunTask() + buildSolutionTask.isEnabled = true + setting.configuration.beforeRunTasks.add(0, buildSolutionTask) + } + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/jetbrains/rider/aspire/actions/dashboard/AspireManifestAction.kt b/src/main/kotlin/com/jetbrains/rider/aspire/actions/dashboard/AspireManifestAction.kt index f8df78f2..bc92deef 100644 --- a/src/main/kotlin/com/jetbrains/rider/aspire/actions/dashboard/AspireManifestAction.kt +++ b/src/main/kotlin/com/jetbrains/rider/aspire/actions/dashboard/AspireManifestAction.kt @@ -4,18 +4,18 @@ import com.intellij.openapi.actionSystem.ActionUpdateThread import com.intellij.openapi.actionSystem.AnAction import com.intellij.openapi.actionSystem.AnActionEvent import com.jetbrains.rider.aspire.manifest.ManifestService -import com.jetbrains.rider.aspire.services.AspireServiceManager +import com.jetbrains.rider.aspire.services.AspireHostManager import com.jetbrains.rider.aspire.util.ASPIRE_HOST_PATH class AspireManifestAction : AnAction() { override fun actionPerformed(event: AnActionEvent) { val project = event.project ?: return val hostPath = event.getData(ASPIRE_HOST_PATH) ?: return - val hostService = AspireServiceManager + val hostService = AspireHostManager .getInstance(project) - .getHostService(hostPath) + .getAspireHost(hostPath) ?: return - val hostProjectPath = hostService.projectPath + val hostProjectPath = hostService.hostProjectPath ManifestService.getInstance(project).generateManifest(hostProjectPath) } @@ -28,9 +28,9 @@ class AspireManifestAction : AnAction() { return } - val hostService = AspireServiceManager + val hostService = AspireHostManager .getInstance(project) - .getHostService(hostPath) + .getAspireHost(hostPath) if (hostService == null) { event.presentation.isEnabledAndVisible = false return diff --git a/src/main/kotlin/com/jetbrains/rider/aspire/actions/dashboard/AspireOpenDashboardAction.kt b/src/main/kotlin/com/jetbrains/rider/aspire/actions/dashboard/AspireOpenDashboardAction.kt index 975ebf6b..3935a6b1 100644 --- a/src/main/kotlin/com/jetbrains/rider/aspire/actions/dashboard/AspireOpenDashboardAction.kt +++ b/src/main/kotlin/com/jetbrains/rider/aspire/actions/dashboard/AspireOpenDashboardAction.kt @@ -4,16 +4,16 @@ import com.intellij.ide.BrowserUtil import com.intellij.openapi.actionSystem.ActionUpdateThread import com.intellij.openapi.actionSystem.AnAction import com.intellij.openapi.actionSystem.AnActionEvent -import com.jetbrains.rider.aspire.services.AspireServiceManager +import com.jetbrains.rider.aspire.services.AspireHostManager import com.jetbrains.rider.aspire.util.ASPIRE_HOST_PATH class AspireOpenDashboardAction : AnAction() { override fun actionPerformed(event: AnActionEvent) { val project = event.project ?: return val hostPath = event.getData(ASPIRE_HOST_PATH) ?: return - val hostService = AspireServiceManager + val hostService = AspireHostManager .getInstance(project) - .getHostService(hostPath) + .getAspireHost(hostPath) ?: return val dashboardUrl = hostService.dashboardUrl if (dashboardUrl.isNullOrEmpty()) return @@ -29,9 +29,9 @@ class AspireOpenDashboardAction : AnAction() { return } - val hostService = AspireServiceManager + val hostService = AspireHostManager .getInstance(project) - .getHostService(hostPath) + .getAspireHost(hostPath) if (hostService == null || !hostService.isActive || hostService.dashboardUrl.isNullOrEmpty()) { event.presentation.isEnabledAndVisible = false return diff --git a/src/main/kotlin/com/jetbrains/rider/aspire/actions/dashboard/DebugHostAction.kt b/src/main/kotlin/com/jetbrains/rider/aspire/actions/dashboard/DebugHostAction.kt index 3ebd86e2..d60ad503 100644 --- a/src/main/kotlin/com/jetbrains/rider/aspire/actions/dashboard/DebugHostAction.kt +++ b/src/main/kotlin/com/jetbrains/rider/aspire/actions/dashboard/DebugHostAction.kt @@ -4,16 +4,16 @@ import com.intellij.openapi.actionSystem.ActionUpdateThread import com.intellij.openapi.actionSystem.AnAction import com.intellij.openapi.actionSystem.AnActionEvent import com.jetbrains.rider.aspire.run.AspireHostRunManager -import com.jetbrains.rider.aspire.services.AspireServiceManager +import com.jetbrains.rider.aspire.services.AspireHostManager import com.jetbrains.rider.aspire.util.ASPIRE_HOST_PATH class DebugHostAction : AnAction() { override fun actionPerformed(event: AnActionEvent) { val project = event.project ?: return val hostPath = event.getData(ASPIRE_HOST_PATH) ?: return - val hostService = AspireServiceManager + val hostService = AspireHostManager .getInstance(project) - .getHostService(hostPath) + .getAspireHost(hostPath) ?: return AspireHostRunManager.getInstance(project) @@ -28,9 +28,9 @@ class DebugHostAction : AnAction() { return } - val hostService = AspireServiceManager + val hostService = AspireHostManager .getInstance(project) - .getHostService(hostPath) + .getAspireHost(hostPath) if (hostService == null) { event.presentation.isEnabledAndVisible = false return diff --git a/src/main/kotlin/com/jetbrains/rider/aspire/actions/dashboard/RunHostAction.kt b/src/main/kotlin/com/jetbrains/rider/aspire/actions/dashboard/RunHostAction.kt index ad51fbe4..d770e0de 100644 --- a/src/main/kotlin/com/jetbrains/rider/aspire/actions/dashboard/RunHostAction.kt +++ b/src/main/kotlin/com/jetbrains/rider/aspire/actions/dashboard/RunHostAction.kt @@ -4,16 +4,16 @@ import com.intellij.openapi.actionSystem.ActionUpdateThread import com.intellij.openapi.actionSystem.AnAction import com.intellij.openapi.actionSystem.AnActionEvent import com.jetbrains.rider.aspire.run.AspireHostRunManager -import com.jetbrains.rider.aspire.services.AspireServiceManager +import com.jetbrains.rider.aspire.services.AspireHostManager import com.jetbrains.rider.aspire.util.ASPIRE_HOST_PATH class RunHostAction : AnAction() { override fun actionPerformed(event: AnActionEvent) { val project = event.project ?: return val hostPath = event.getData(ASPIRE_HOST_PATH) ?: return - val hostService = AspireServiceManager + val hostService = AspireHostManager .getInstance(project) - .getHostService(hostPath) + .getAspireHost(hostPath) ?: return AspireHostRunManager.getInstance(project) @@ -28,9 +28,9 @@ class RunHostAction : AnAction() { return } - val hostService = AspireServiceManager + val hostService = AspireHostManager .getInstance(project) - .getHostService(hostPath) + .getAspireHost(hostPath) if (hostService == null) { event.presentation.isEnabledAndVisible = false return diff --git a/src/main/kotlin/com/jetbrains/rider/aspire/actions/dashboard/StopHostAction.kt b/src/main/kotlin/com/jetbrains/rider/aspire/actions/dashboard/StopHostAction.kt index 05991687..9d670f71 100644 --- a/src/main/kotlin/com/jetbrains/rider/aspire/actions/dashboard/StopHostAction.kt +++ b/src/main/kotlin/com/jetbrains/rider/aspire/actions/dashboard/StopHostAction.kt @@ -5,7 +5,7 @@ import com.intellij.openapi.actionSystem.AnAction import com.intellij.openapi.actionSystem.AnActionEvent import com.jetbrains.rider.aspire.AspireService import com.jetbrains.rider.aspire.run.AspireHostRunManager -import com.jetbrains.rider.aspire.services.AspireServiceManager +import com.jetbrains.rider.aspire.services.AspireHostManager import com.jetbrains.rider.aspire.util.ASPIRE_HOST_PATH import kotlinx.coroutines.launch @@ -13,9 +13,9 @@ class StopHostAction : AnAction() { override fun actionPerformed(event: AnActionEvent) { val project = event.project ?: return val hostPath = event.getData(ASPIRE_HOST_PATH) ?: return - val hostService = AspireServiceManager + val hostService = AspireHostManager .getInstance(project) - .getHostService(hostPath) + .getAspireHost(hostPath) ?: return AspireService.getInstance(project).scope.launch { @@ -32,9 +32,9 @@ class StopHostAction : AnAction() { return } - val hostService = AspireServiceManager + val hostService = AspireHostManager .getInstance(project) - .getHostService(hostPath) + .getAspireHost(hostPath) if (hostService == null) { event.presentation.isEnabledAndVisible = false return diff --git a/src/main/kotlin/com/jetbrains/rider/aspire/database/DatabaseResourceListener.kt b/src/main/kotlin/com/jetbrains/rider/aspire/database/DatabaseResourceListener.kt index f0a5bf21..117b5995 100644 --- a/src/main/kotlin/com/jetbrains/rider/aspire/database/DatabaseResourceListener.kt +++ b/src/main/kotlin/com/jetbrains/rider/aspire/database/DatabaseResourceListener.kt @@ -2,8 +2,8 @@ package com.jetbrains.rider.aspire.database import com.intellij.openapi.project.Project import com.jetbrains.rider.aspire.generated.ResourceType -import com.jetbrains.rider.aspire.services.AspireResourceService import com.jetbrains.rider.aspire.services.ResourceListener +import com.jetbrains.rider.aspire.services.AspireResource import java.net.URI class DatabaseResourceListener(private val project: Project) : ResourceListener { @@ -18,15 +18,15 @@ class DatabaseResourceListener(private val project: Project) : ResourceListener private const val REDIS = "redis" } - override fun resourceCreated(resource: AspireResourceService) { + override fun resourceCreated(resource: AspireResource) { applyChanges(resource) } - override fun resourceUpdated(resource: AspireResourceService) { + override fun resourceUpdated(resource: AspireResource) { applyChanges(resource) } - private fun applyChanges(resource: AspireResourceService) { + private fun applyChanges(resource: AspireResource) { val service = DatabaseService.getInstance(project) if (resource.type == ResourceType.Project) { resource.environment diff --git a/src/main/kotlin/com/jetbrains/rider/aspire/listeners/AspireSessionHostListener.kt b/src/main/kotlin/com/jetbrains/rider/aspire/listeners/AspireSessionHostListener.kt new file mode 100644 index 00000000..f18c1399 --- /dev/null +++ b/src/main/kotlin/com/jetbrains/rider/aspire/listeners/AspireSessionHostListener.kt @@ -0,0 +1,17 @@ +package com.jetbrains.rider.aspire.listeners + +import com.intellij.util.messages.Topic +import com.jetbrains.rd.util.lifetime.Lifetime +import com.jetbrains.rider.aspire.generated.AspireSessionHostModel +import com.jetbrains.rider.aspire.run.AspireHostConfig +import java.nio.file.Path + +interface AspireSessionHostListener { + companion object { + @Topic.ProjectLevel + val TOPIC = Topic("AspireSessionHostListener", AspireSessionHostListener::class.java) + } + + fun configCreated(aspireHostProjectPath: Path, config: AspireHostConfig) + fun modelCreated(aspireHostProjectPath: Path, sessionHostModel: AspireSessionHostModel, lifetime: Lifetime) +} \ No newline at end of file diff --git a/src/main/kotlin/com/jetbrains/rider/aspire/run/AspireHostConfig.kt b/src/main/kotlin/com/jetbrains/rider/aspire/run/AspireHostConfig.kt index f41141ba..79319761 100644 --- a/src/main/kotlin/com/jetbrains/rider/aspire/run/AspireHostConfig.kt +++ b/src/main/kotlin/com/jetbrains/rider/aspire/run/AspireHostConfig.kt @@ -9,14 +9,13 @@ data class AspireHostConfig( val debugSessionToken: String, //from env: DEBUG_SESSION_PORT val debugSessionPort: Int, - val aspireHostProjectPath: Path, - //from env: ASPNETCORE_URLS - val aspireHostProjectUrl: String?, val debuggingMode: Boolean, //from env: DOTNET_RESOURCE_SERVICE_ENDPOINT_URL val resourceServiceEndpointUrl: String?, //from env: DOTNET_DASHBOARD_RESOURCESERVICE_APIKEY val resourceServiceApiKey: String?, val aspireHostLifetime: Lifetime, - val hostRunConfiguration: AspireHostConfiguration? + val aspireHostProjectPath: Path, + val aspireHostProjectUrl: String?, + val aspireHostRunConfiguration: AspireHostConfiguration? ) \ No newline at end of file diff --git a/src/main/kotlin/com/jetbrains/rider/aspire/run/AspireHostConfiguration.kt b/src/main/kotlin/com/jetbrains/rider/aspire/run/AspireHostConfiguration.kt index 05a3d228..b158c3df 100644 --- a/src/main/kotlin/com/jetbrains/rider/aspire/run/AspireHostConfiguration.kt +++ b/src/main/kotlin/com/jetbrains/rider/aspire/run/AspireHostConfiguration.kt @@ -3,8 +3,8 @@ package com.jetbrains.rider.aspire.run import com.intellij.execution.configurations.ConfigurationFactory import com.intellij.execution.configurations.RunConfiguration import com.intellij.openapi.project.Project -import com.jetbrains.rider.build.tasks.SolutionRunConfiguration import com.jetbrains.rider.run.configurations.IAutoSelectableRunConfiguration +import com.jetbrains.rider.run.configurations.IProjectBasedRunConfiguration import com.jetbrains.rider.run.configurations.RiderAsyncRunConfiguration import org.jdom.Element @@ -19,7 +19,7 @@ class AspireHostConfiguration( factory, { AspireHostSettingsEditor(it) }, AspireHostExecutorFactory(project, parameters) -), SolutionRunConfiguration, IAutoSelectableRunConfiguration { +), IProjectBasedRunConfiguration, IAutoSelectableRunConfiguration { override fun checkConfiguration() { parameters.validate() } @@ -46,4 +46,10 @@ class AspireHostConfiguration( } override fun getAutoSelectPriority() = 10 + + override fun getProjectFilePath() = parameters.projectFilePath + + override fun setProjectFilePath(path: String) { + parameters.projectFilePath = path + } } \ No newline at end of file diff --git a/src/main/kotlin/com/jetbrains/rider/aspire/run/AspireHostRunManager.kt b/src/main/kotlin/com/jetbrains/rider/aspire/run/AspireHostRunManager.kt index b75bdc74..a6de834f 100644 --- a/src/main/kotlin/com/jetbrains/rider/aspire/run/AspireHostRunManager.kt +++ b/src/main/kotlin/com/jetbrains/rider/aspire/run/AspireHostRunManager.kt @@ -11,7 +11,7 @@ import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.project.Project import com.jetbrains.rd.util.lifetime.LifetimeDefinition import com.jetbrains.rd.util.lifetime.isNotAlive -import com.jetbrains.rider.aspire.services.AspireHostService +import com.jetbrains.rider.aspire.services.AspireHost import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import java.nio.file.Path @@ -38,7 +38,7 @@ class AspireHostRunManager(private val project: Project) { configurationLifetimes[aspireHostProjectPath] = aspireHostLifetime } - fun executeConfigurationForHost(host: AspireHostService, underDebug: Boolean) { + fun executeConfigurationForHost(host: AspireHost, underDebug: Boolean) { val executor = if (underDebug) DefaultDebugExecutor.getDebugExecutorInstance() else DefaultRunExecutor.getRunExecutorInstance() @@ -47,7 +47,7 @@ class AspireHostRunManager(private val project: Project) { val selected = runManager.selectedConfiguration val selectedConfiguration = selected?.configuration if (selectedConfiguration != null && selectedConfiguration is AspireHostConfiguration) { - if (host.projectPath == Path(selectedConfiguration.parameters.projectFilePath)) { + if (host.hostProjectPath == Path(selectedConfiguration.parameters.projectFilePath)) { ProgramRunnerUtil.executeConfiguration(selected, executor) return } @@ -57,7 +57,7 @@ class AspireHostRunManager(private val project: Project) { .filter { val path = (it.configuration as? AspireHostConfiguration)?.parameters?.projectFilePath ?: return@filter false - Path(path) == host.projectPath + Path(path) == host.hostProjectPath } if (configurations.isEmpty()) { @@ -65,7 +65,7 @@ class AspireHostRunManager(private val project: Project) { return } - val configurationName = configurationNames[host.projectPath] + val configurationName = configurationNames[host.hostProjectPath] if (configurationName != null) { val configurationWithName = configurations.firstOrNull { it.name == configurationName } if (configurationWithName != null) { @@ -78,10 +78,10 @@ class AspireHostRunManager(private val project: Project) { ProgramRunnerUtil.executeConfiguration(firstConfiguration, executor) } - suspend fun stopConfigurationForHost(host: AspireHostService) { - val lifetime = configurationLifetimes[host.projectPath] + suspend fun stopConfigurationForHost(host: AspireHost) { + val lifetime = configurationLifetimes[host.hostProjectPath] if (lifetime == null || lifetime.lifetime.isNotAlive) { - LOG.warn("Unable to stop configuration for Aspire host ${host.projectPathString}") + LOG.warn("Unable to stop configuration for Aspire host ${host.hostProjectPathString}") return } diff --git a/src/main/kotlin/com/jetbrains/rider/aspire/run/runners/AspireHostDebugProgramRunner.kt b/src/main/kotlin/com/jetbrains/rider/aspire/run/runners/AspireHostDebugProgramRunner.kt index 2e2bbb58..5f3fea5c 100644 --- a/src/main/kotlin/com/jetbrains/rider/aspire/run/runners/AspireHostDebugProgramRunner.kt +++ b/src/main/kotlin/com/jetbrains/rider/aspire/run/runners/AspireHostDebugProgramRunner.kt @@ -14,7 +14,6 @@ import com.jetbrains.rider.aspire.AspireService import com.jetbrains.rider.aspire.run.AspireHostConfig import com.jetbrains.rider.aspire.run.AspireHostConfiguration import com.jetbrains.rider.aspire.run.states.AspireHostDebugProfileState -import com.jetbrains.rider.aspire.services.AspireServiceManager import com.jetbrains.rider.debugger.DebuggerWorkerProcessHandler import com.jetbrains.rider.debugger.DotNetDebugRunner import com.jetbrains.rider.model.debuggerWorker.DebuggerWorkerModel @@ -54,17 +53,16 @@ class AspireHostDebugProgramRunner : DotNetDebugRunner() { LOG.trace { "Aspire session host config: $config" } - startSessionHostAndSubscribe( + saveRunConfiguration( environment.project, - config, + config.aspireHostProjectPath, + config.name, aspireHostLifetimeDefinition ) - val executionResult = state.execute(environment.executor, this, workerProcessHandler, sessionLifetime) + startSessionHostAndSubscribe(config, environment.project) - AspireServiceManager - .getInstance(environment.project) - .updateAspireHostService(config.aspireHostProjectPath, executionResult) + val executionResult = state.execute(environment.executor, this, workerProcessHandler, sessionLifetime) connectExecutionHandlerAndLifetime(executionResult, aspireHostLifetimeDefinition) diff --git a/src/main/kotlin/com/jetbrains/rider/aspire/run/runners/AspireHostProgramRunner.kt b/src/main/kotlin/com/jetbrains/rider/aspire/run/runners/AspireHostProgramRunner.kt index b87ee2e9..47219831 100644 --- a/src/main/kotlin/com/jetbrains/rider/aspire/run/runners/AspireHostProgramRunner.kt +++ b/src/main/kotlin/com/jetbrains/rider/aspire/run/runners/AspireHostProgramRunner.kt @@ -14,7 +14,6 @@ import com.jetbrains.rider.aspire.AspireService import com.jetbrains.rider.aspire.run.AspireHostConfig import com.jetbrains.rider.aspire.run.AspireHostConfiguration import com.jetbrains.rider.aspire.run.states.AspireHostRunProfileState -import com.jetbrains.rider.aspire.services.AspireServiceManager import com.jetbrains.rider.debugger.DotNetProgramRunner class AspireHostProgramRunner : DotNetProgramRunner() { @@ -44,22 +43,21 @@ class AspireHostProgramRunner : DotNetProgramRunner() { LOG.trace { "Aspire session host config: $config" } - startSessionHostAndSubscribe( + saveRunConfiguration( environment.project, - config, + config.aspireHostProjectPath, + config.name, aspireHostLifetimeDefinition ) + startSessionHostAndSubscribe(config, environment.project) + val executionResult = state.execute(environment.executor, this) if (executionResult == null) { LOG.warn("Unable to start Aspire run profile state") return null } - AspireServiceManager - .getInstance(environment.project) - .updateAspireHostService(config.aspireHostProjectPath, executionResult) - connectExecutionHandlerAndLifetime(executionResult, aspireHostLifetimeDefinition) return showRunContent(executionResult, environment) diff --git a/src/main/kotlin/com/jetbrains/rider/aspire/run/runners/AspireHostProgramRunnerUtils.kt b/src/main/kotlin/com/jetbrains/rider/aspire/run/runners/AspireHostProgramRunnerUtils.kt index ddee87b7..08407df2 100644 --- a/src/main/kotlin/com/jetbrains/rider/aspire/run/runners/AspireHostProgramRunnerUtils.kt +++ b/src/main/kotlin/com/jetbrains/rider/aspire/run/runners/AspireHostProgramRunnerUtils.kt @@ -15,14 +15,15 @@ import com.jetbrains.rd.util.lifetime.Lifetime import com.jetbrains.rd.util.lifetime.LifetimeDefinition import com.jetbrains.rdclient.protocol.RdDispatcher import com.jetbrains.rider.aspire.generated.aspireSessionHostModel +import com.jetbrains.rider.aspire.listeners.AspireSessionHostListener import com.jetbrains.rider.aspire.run.AspireHostConfig import com.jetbrains.rider.aspire.run.AspireHostConfiguration import com.jetbrains.rider.aspire.run.AspireHostRunManager import com.jetbrains.rider.aspire.run.states.* -import com.jetbrains.rider.aspire.services.AspireServiceManager import com.jetbrains.rider.aspire.sessionHost.SessionHostManager import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext +import java.nio.file.Path import kotlin.io.path.Path private val LOG = Logger.getInstance("#com.jetbrains.rider.aspire.run.runners.AspireHostProgramRunnerUtils") @@ -48,35 +49,53 @@ fun createAspireHostConfig( val parameters = aspireHostConfiguration.parameters val aspireHostProjectPath = Path(parameters.projectFilePath) - val aspireHostProjectUrl = parameters.startBrowserParameters.url + val browserToken = state.getDashboardBrowserToken() + val aspireHostProjectUrl = if (browserToken != null) { + "${parameters.startBrowserParameters.url}/login?t=$browserToken" + } else { + parameters.startBrowserParameters.url + } - return AspireHostConfig( + val config = AspireHostConfig( aspireHostConfiguration.name, debugSessionToken, debugSessionPort, - aspireHostProjectPath, - aspireHostProjectUrl, isDebuggingMode, resourceServiceEndpointUrl, resourceServiceApiKey, aspireHostLifetime, + aspireHostProjectPath, + aspireHostProjectUrl, aspireHostConfiguration ) + + environment.project.messageBus + .syncPublisher(AspireSessionHostListener.TOPIC) + .configCreated(config.aspireHostProjectPath, config) + + return config } -suspend fun startSessionHostAndSubscribe( +fun saveRunConfiguration( project: Project, - config: AspireHostConfig, + aspireHostProjectPath: Path, + runConfigurationName: String, aspireHostLifetimeDefinition: LifetimeDefinition +) { + AspireHostRunManager.Companion.getInstance(project) + .saveRunConfiguration(aspireHostProjectPath, aspireHostLifetimeDefinition, runConfigurationName) +} + +suspend fun startSessionHostAndSubscribe( + config: AspireHostConfig, + project: Project ) = withContext(Dispatchers.EDT) { val protocol = startSessionHostProtocol(config.aspireHostLifetime) val sessionHostModel = protocol.aspireSessionHostModel - AspireHostRunManager.Companion.getInstance(project) - .saveRunConfiguration(config.aspireHostProjectPath, aspireHostLifetimeDefinition, config.name) - - AspireServiceManager.Companion.getInstance(project) - .startAspireHostService(config, sessionHostModel) + project.messageBus + .syncPublisher(AspireSessionHostListener.TOPIC) + .modelCreated(config.aspireHostProjectPath, sessionHostModel, config.aspireHostLifetime) SessionHostManager.Companion.getInstance(project) .startSessionHost(config, protocol.wire.serverPort, sessionHostModel) diff --git a/src/main/kotlin/com/jetbrains/rider/aspire/run/states/AspireHostProfileState.kt b/src/main/kotlin/com/jetbrains/rider/aspire/run/states/AspireHostProfileState.kt index df7e7ed3..e9ce78ea 100644 --- a/src/main/kotlin/com/jetbrains/rider/aspire/run/states/AspireHostProfileState.kt +++ b/src/main/kotlin/com/jetbrains/rider/aspire/run/states/AspireHostProfileState.kt @@ -2,6 +2,7 @@ package com.jetbrains.rider.aspire.run.states import com.jetbrains.rider.aspire.util.DEBUG_SESSION_PORT import com.jetbrains.rider.aspire.util.DEBUG_SESSION_TOKEN +import com.jetbrains.rider.aspire.util.DOTNET_DASHBOARD_FRONTEND_BROWSERTOKEN import com.jetbrains.rider.aspire.util.DOTNET_DASHBOARD_RESOURCESERVICE_APIKEY import com.jetbrains.rider.aspire.util.DOTNET_RESOURCE_SERVICE_ENDPOINT_URL @@ -15,6 +16,8 @@ fun AspireHostProfileState.getDebugSessionPort() = environmentVariables[DEBUG_SE ?.substringAfter(':') ?.toInt() +fun AspireHostProfileState.getDashboardBrowserToken() = environmentVariables[DOTNET_DASHBOARD_FRONTEND_BROWSERTOKEN] + fun AspireHostProfileState.getResourceServiceEndpointUrl() = environmentVariables[DOTNET_RESOURCE_SERVICE_ENDPOINT_URL] fun AspireHostProfileState.getResourceServiceApiKey() = environmentVariables[DOTNET_DASHBOARD_RESOURCESERVICE_APIKEY] \ No newline at end of file diff --git a/src/main/kotlin/com/jetbrains/rider/aspire/services/AspireHost.kt b/src/main/kotlin/com/jetbrains/rider/aspire/services/AspireHost.kt new file mode 100644 index 00000000..904f5cc7 --- /dev/null +++ b/src/main/kotlin/com/jetbrains/rider/aspire/services/AspireHost.kt @@ -0,0 +1,244 @@ +package com.jetbrains.rider.aspire.services + +import com.intellij.execution.ExecutionListener +import com.intellij.execution.ExecutionManager +import com.intellij.execution.process.ProcessHandler +import com.intellij.execution.runners.ExecutionEnvironment +import com.intellij.execution.services.ServiceEventListener +import com.intellij.execution.services.ServiceViewManager +import com.intellij.execution.ui.ConsoleView +import com.intellij.openapi.Disposable +import com.intellij.openapi.diagnostic.logger +import com.intellij.openapi.diagnostic.trace +import com.intellij.openapi.project.Project +import com.intellij.openapi.util.Disposer +import com.intellij.util.application +import com.jetbrains.rd.util.addUnique +import com.jetbrains.rd.util.lifetime.Lifetime +import com.jetbrains.rider.aspire.generated.AspireSessionHostModel +import com.jetbrains.rider.aspire.generated.ResourceState +import com.jetbrains.rider.aspire.generated.ResourceType +import com.jetbrains.rider.aspire.generated.ResourceWrapper +import com.jetbrains.rider.aspire.listeners.AspireSessionHostListener +import com.jetbrains.rider.aspire.run.AspireHostConfig +import com.jetbrains.rider.aspire.run.AspireHostConfiguration +import com.jetbrains.rider.aspire.sessionHost.projectLaunchers.ProjectSessionProfile +import com.jetbrains.rider.aspire.util.getServiceInstanceId +import com.jetbrains.rider.debugger.DebuggerWorkerProcessHandler +import com.jetbrains.rider.run.ConsoleKind +import com.jetbrains.rider.run.createConsole +import com.jetbrains.rider.runtime.DotNetExecutable +import java.nio.file.Path +import java.util.concurrent.ConcurrentHashMap +import kotlin.collections.iterator +import kotlin.io.path.Path +import kotlin.io.path.absolutePathString +import kotlin.io.path.nameWithoutExtension + +class AspireHost( + val hostProjectPath: Path, + private val project: Project +) : Disposable { + companion object { + private val LOG = logger() + } + + private val serviceEventPublisher = project.messageBus.syncPublisher(ServiceEventListener.TOPIC) + + private val resources = ConcurrentHashMap() + private val handlers = mutableMapOf() + private val lock = Any() + + val hostProjectPathString = hostProjectPath.absolutePathString() + + val serviceViewContributor: AspireHostServiceViewContributor by lazy { + AspireHostServiceViewContributor(this) + } + + var displayName: String = hostProjectPath.nameWithoutExtension + private set + var isActive: Boolean = false + private set + var dashboardUrl: String? = null + private set + var consoleView: ConsoleView? = null + private set + + init { + project.messageBus.connect(this).subscribe(ExecutionManager.EXECUTION_TOPIC, object : ExecutionListener { + override fun processStarted( + executorId: String, + env: ExecutionEnvironment, + handler: ProcessHandler + ) { + val profile = env.runProfile + if (profile is AspireHostConfiguration) { + val projectFilePath = Path(profile.parameters.projectFilePath) + if (hostProjectPath != projectFilePath) return + + start(handler) + } else if (profile is ProjectSessionProfile) { + val aspireHostProjectPath = profile.aspireHostProjectPath ?: return + if (hostProjectPath != aspireHostProjectPath) return + + setHandlerForResource(profile.dotnetExecutable, handler) + } + } + + override fun processTerminated( + executorId: String, + env: ExecutionEnvironment, + handler: ProcessHandler, + exitCode: Int + ) { + val profile = env.runProfile + if (profile !is AspireHostConfiguration) return + + val projectFilePath = Path(profile.parameters.projectFilePath) + if (hostProjectPath != projectFilePath) return + + stop() + } + }) + + project.messageBus.connect(this).subscribe(AspireSessionHostListener.TOPIC, object : AspireSessionHostListener { + override fun configCreated( + aspireHostProjectPath: Path, + config: AspireHostConfig + ) { + if (aspireHostProjectPath != hostProjectPath) return + + addAspireHostUrl(config) + } + + override fun modelCreated( + aspireHostProjectPath: Path, + sessionHostModel: AspireSessionHostModel, + lifetime: Lifetime + ) { + if (aspireHostProjectPath != hostProjectPath) return + + addSessionHostModel(sessionHostModel, lifetime) + } + }) + } + + fun getResources(): List { + val result = mutableListOf() + for (resource in resources) { + if (resource.value.type == ResourceType.Unknown || resource.value.state == ResourceState.Hidden) + continue + + result.add(resource.value) + } + + return result.sortedBy { it.type } + } + + private fun addAspireHostUrl(config: AspireHostConfig) { + dashboardUrl = config.aspireHostProjectUrl + + sendServiceChangedEvent() + } + + private fun addSessionHostModel(sessionHostModel: AspireSessionHostModel, lifetime: Lifetime) { + val sessionHostLifetime = lifetime.createNested() + sessionHostModel.resources.view(sessionHostLifetime) { resourceLifetime, resourceId, resourceModel -> + viewResource(resourceId, resourceModel, resourceLifetime) + } + } + + private fun start(processHandler: ProcessHandler) { + LOG.trace { "Starting an Aspire Host $hostProjectPathString" } + + isActive = true + + val handler = + if (processHandler is DebuggerWorkerProcessHandler) processHandler.debuggerWorkerRealHandler + else processHandler + val console = createConsole( + ConsoleKind.Normal, + handler, + project + ) + Disposer.register(this, console) + consoleView = console + + selectHost() + + sendServiceChangedEvent() + } + + private fun stop() { + LOG.trace { "Stopping an Aspire Host $hostProjectPathString" } + + isActive = false + dashboardUrl = null + + sendServiceChangedEvent() + } + + private fun viewResource( + resourceId: String, + resourceModel: ResourceWrapper, + resourceLifetime: Lifetime + ) { + LOG.trace { "Adding a new resource with id $resourceId to the host $hostProjectPathString" } + + val resource = AspireResource(resourceModel, resourceLifetime, this, project) + Disposer.register(this, resource) + resources.addUnique(resourceLifetime, resourceId, resource) + + resourceModel.isInitialized.set(true) + + expand() + + val handler = synchronized(lock) { + handlers.remove(resource.serviceInstanceId) + } + handler?.let { resource.setHandler(it) } + } + + private fun setHandlerForResource(dotnetExecutable: DotNetExecutable, processHandler: ProcessHandler) { + val serviceInstanceId = dotnetExecutable.getServiceInstanceId() ?: return + val resource = synchronized(lock) { + val resource = resources.entries.firstOrNull { it.value.serviceInstanceId == serviceInstanceId }?.value + if (resource == null) { + handlers[serviceInstanceId] = processHandler + return + } + resource + } + + resource.setHandler(processHandler) + } + + private fun selectHost() { + application.invokeLater { + ServiceViewManager + .getInstance(project) + .select(serviceViewContributor.asService(), AspireMainServiceViewContributor::class.java, true, true) + } + } + + private fun expand() { + application.invokeLater { + ServiceViewManager + .getInstance(project) + .expand(serviceViewContributor.asService(), AspireMainServiceViewContributor::class.java) + } + } + + private fun sendServiceChangedEvent() { + val event = ServiceEventListener.ServiceEvent.createEvent( + ServiceEventListener.EventType.SERVICE_CHANGED, + serviceViewContributor.asService(), + AspireMainServiceViewContributor::class.java + ) + serviceEventPublisher.handle(event) + } + + + override fun dispose() { + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/jetbrains/rider/aspire/services/AspireHostManager.kt b/src/main/kotlin/com/jetbrains/rider/aspire/services/AspireHostManager.kt new file mode 100644 index 00000000..0dbb2626 --- /dev/null +++ b/src/main/kotlin/com/jetbrains/rider/aspire/services/AspireHostManager.kt @@ -0,0 +1,99 @@ +package com.jetbrains.rider.aspire.services + +import com.intellij.execution.RunManager +import com.intellij.execution.RunManagerListener +import com.intellij.execution.RunnerAndConfigurationSettings +import com.intellij.execution.configurations.ConfigurationTypeUtil +import com.intellij.execution.services.ServiceEventListener +import com.intellij.openapi.Disposable +import com.intellij.openapi.components.Service +import com.intellij.openapi.components.service +import com.intellij.openapi.diagnostic.logger +import com.intellij.openapi.diagnostic.trace +import com.intellij.openapi.project.Project +import com.intellij.openapi.util.Disposer +import com.jetbrains.rider.aspire.run.AspireHostConfiguration +import com.jetbrains.rider.aspire.run.AspireHostConfigurationType +import java.nio.file.Path +import java.util.concurrent.ConcurrentHashMap +import kotlin.io.path.Path +import kotlin.io.path.absolutePathString + +@Service(Service.Level.PROJECT) +class AspireHostManager(private val project: Project) : Disposable { + companion object { + fun getInstance(project: Project) = project.service() + + private val LOG = logger() + } + + private val aspireHosts = ConcurrentHashMap() + + private val serviceEventPublisher = project.messageBus.syncPublisher(ServiceEventListener.TOPIC) + + fun getAspireHosts(): List { + val hosts = mutableListOf() + for (host in aspireHosts) { + hosts.add(host.value) + } + return hosts + } + + fun getAspireHost(aspireHostProjectPath: Path) = aspireHosts[aspireHostProjectPath] + + fun addAspireHost(aspireHostProjectPath: Path) { + if (aspireHosts.containsKey(aspireHostProjectPath)) return + + LOG.trace { "Adding a new Aspire host ${aspireHostProjectPath.absolutePathString()}" } + + val aspireHost = AspireHost(aspireHostProjectPath, project) + Disposer.register(this, aspireHost) + + aspireHosts[aspireHostProjectPath] = aspireHost + + val event = ServiceEventListener.ServiceEvent.createEvent( + ServiceEventListener.EventType.RESET, + aspireHost, + AspireMainServiceViewContributor::class.java + ) + serviceEventPublisher.handle(event) + } + + fun removeAspireHost(aspireHostProjectPath: Path) { + val configurationType = ConfigurationTypeUtil.findConfigurationType(AspireHostConfigurationType::class.java) + val configurations = RunManager.getInstance(project).getConfigurationsList(configurationType) + if (configurations.isNotEmpty()) return + + LOG.trace { "Removing the Aspire host ${aspireHostProjectPath.absolutePathString()}" } + + val aspireHost = aspireHosts.remove(aspireHostProjectPath) ?: return + + val event = ServiceEventListener.ServiceEvent.createEvent( + ServiceEventListener.EventType.SERVICE_REMOVED, + aspireHost, + AspireMainServiceViewContributor::class.java + ) + serviceEventPublisher.handle(event) + + Disposer.dispose(aspireHost) + } + + override fun dispose() { + } + + class RunListener(private val project: Project) : RunManagerListener { + override fun runConfigurationAdded(settings: RunnerAndConfigurationSettings) { + val configuration = settings.configuration + if (configuration !is AspireHostConfiguration) return + val params = configuration.parameters + getInstance(project).addAspireHost(Path(params.projectFilePath)) + } + + override fun runConfigurationRemoved(settings: RunnerAndConfigurationSettings) { + val configuration = settings.configuration + if (configuration !is AspireHostConfiguration) return + val params = configuration.parameters + getInstance(project).removeAspireHost(Path(params.projectFilePath)) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/jetbrains/rider/aspire/services/AspireHostService.kt b/src/main/kotlin/com/jetbrains/rider/aspire/services/AspireHostService.kt deleted file mode 100644 index 1c3c74f7..00000000 --- a/src/main/kotlin/com/jetbrains/rider/aspire/services/AspireHostService.kt +++ /dev/null @@ -1,89 +0,0 @@ -package com.jetbrains.rider.aspire.services - -import com.intellij.execution.ExecutionResult -import com.intellij.execution.services.ServiceViewProvidingContributor -import com.intellij.execution.ui.ConsoleView -import com.intellij.openapi.project.Project -import com.intellij.openapi.util.Disposer -import com.jetbrains.rd.util.lifetime.Lifetime -import com.jetbrains.rider.aspire.AspireService -import com.jetbrains.rider.aspire.generated.AspireSessionHostModel -import com.jetbrains.rider.debugger.DebuggerWorkerProcessHandler -import com.jetbrains.rider.run.ConsoleKind -import com.jetbrains.rider.run.createConsole -import java.nio.file.Path -import kotlin.io.path.absolutePathString - -class AspireHostService( - name: String, - val projectPath: Path, -) : ServiceViewProvidingContributor { - - private val viewDescriptor by lazy { AspireHostServiceViewDescriptor(this) } - - val projectPathString = projectPath.absolutePathString() - - var displayName: String = name - private set - var isActive: Boolean = false - private set - var dashboardUrl: String? = null - private set - var model: AspireSessionHostModel? = null - private set - var lifetime: Lifetime? = null - private set - - var consoleView: ConsoleView? = null - private set - - fun startHost( - aspireHostDashboardUrl: String?, - sessionHostModel: AspireSessionHostModel, - aspireHostServiceLifetime: Lifetime - ) { - isActive = true - dashboardUrl = aspireHostDashboardUrl - model = sessionHostModel - lifetime = aspireHostServiceLifetime - } - - fun stopHost() { - isActive = false - dashboardUrl = null - model = null - lifetime = null - } - - fun update(name: String) { - displayName = name - } - - fun update(executionResult: ExecutionResult, project: Project) { - val processHandler = executionResult.processHandler - val handler = - if (processHandler is DebuggerWorkerProcessHandler) processHandler.debuggerWorkerRealHandler - else processHandler - val console = createConsole( - ConsoleKind.Normal, - handler, - project - ) - Disposer.register(AspireService.getInstance(project), console) - consoleView = console - } - - override fun getViewDescriptor(project: Project) = viewDescriptor - - override fun getServices(project: Project) = - AspireServiceManager.getInstance(project) - .getResourceServices(projectPathString) - .toMutableList() - - override fun asService() = this - - override fun getServiceDescriptor( - project: Project, - service: AspireResourceService - ) = AspireResourceServiceViewDescriptor(service) -} \ No newline at end of file diff --git a/src/main/kotlin/com/jetbrains/rider/aspire/services/AspireHostServiceViewContributor.kt b/src/main/kotlin/com/jetbrains/rider/aspire/services/AspireHostServiceViewContributor.kt new file mode 100644 index 00000000..acefe4d2 --- /dev/null +++ b/src/main/kotlin/com/jetbrains/rider/aspire/services/AspireHostServiceViewContributor.kt @@ -0,0 +1,86 @@ +@file:Suppress("UnstableApiUsage") + +package com.jetbrains.rider.aspire.services + +import com.intellij.execution.services.ServiceViewDescriptor +import com.intellij.execution.services.ServiceViewProvidingContributor +import com.intellij.ide.projectView.PresentationData +import com.intellij.openapi.actionSystem.ActionManager +import com.intellij.openapi.actionSystem.DataProvider +import com.intellij.openapi.actionSystem.DefaultActionGroup +import com.intellij.openapi.actionSystem.Separator +import com.intellij.openapi.project.Project +import com.intellij.ui.BadgeIconSupplier +import com.intellij.ui.SimpleTextAttributes +import com.intellij.ui.components.JBPanelWithEmptyText +import com.intellij.util.ui.JBUI +import com.jetbrains.rider.aspire.AspireIcons +import com.jetbrains.rider.aspire.util.ASPIRE_HOST_PATH +import java.awt.BorderLayout +import javax.swing.JPanel + +class AspireHostServiceViewContributor( + private val aspireHost: AspireHost +) : ServiceViewProvidingContributor { + private val descriptor = AspireHostServiceViewDescriptor() + + override fun getViewDescriptor(project: Project): ServiceViewDescriptor { + return descriptor + } + + override fun asService() = aspireHost + + override fun getServices(project: Project) = aspireHost.getResources().map { it.serviceViewContributor } + + override fun getServiceDescriptor( + project: Project, + service: AspireResourceServiceViewContributor + ) = service.getViewDescriptor(project) + + private fun getPanel(): JPanel { + val console = aspireHost.consoleView + val panel = if (console == null) { + JBPanelWithEmptyText() + } else { + JPanel(BorderLayout()).apply { + border = JBUI.Borders.empty() + add(console.component) + } + } + + return panel + } + + private inner class AspireHostServiceViewDescriptor : ServiceViewDescriptor, DataProvider { + private val toolbarActions = DefaultActionGroup( + ActionManager.getInstance().getAction("Aspire.Host.Run"), + ActionManager.getInstance().getAction("Aspire.Host.Debug"), + ActionManager.getInstance().getAction("Aspire.Host.Stop"), + Separator(), + ActionManager.getInstance().getAction("Aspire.Manifest"), + ActionManager.getInstance().getAction("Aspire.Dashboard"), + Separator(), + ActionManager.getInstance().getAction("Aspire.Settings"), + ActionManager.getInstance().getAction("Aspire.Help") + ) + + override fun getPresentation() = PresentationData().apply { + var icon = AspireIcons.Service + if (aspireHost.isActive) { + icon = BadgeIconSupplier(icon).liveIndicatorIcon + } + setIcon(icon) + addText(aspireHost.displayName, SimpleTextAttributes.REGULAR_ATTRIBUTES) + } + + override fun getContentComponent() = getPanel() + + override fun getToolbarActions() = toolbarActions + + override fun getDataProvider() = this + + override fun getData(dataId: String) = + if (ASPIRE_HOST_PATH.`is`(dataId)) aspireHost.hostProjectPath + else null + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/jetbrains/rider/aspire/services/AspireHostServiceViewDescriptor.kt b/src/main/kotlin/com/jetbrains/rider/aspire/services/AspireHostServiceViewDescriptor.kt deleted file mode 100644 index ba9b2c04..00000000 --- a/src/main/kotlin/com/jetbrains/rider/aspire/services/AspireHostServiceViewDescriptor.kt +++ /dev/null @@ -1,65 +0,0 @@ -@file:Suppress("UnstableApiUsage") - -package com.jetbrains.rider.aspire.services - -import com.intellij.execution.services.ServiceViewDescriptor -import com.intellij.ide.projectView.PresentationData -import com.intellij.openapi.actionSystem.ActionManager -import com.intellij.openapi.actionSystem.DataProvider -import com.intellij.openapi.actionSystem.DefaultActionGroup -import com.intellij.openapi.actionSystem.Separator -import com.intellij.ui.BadgeIconSupplier -import com.intellij.ui.SimpleTextAttributes -import com.intellij.ui.components.JBPanelWithEmptyText -import com.intellij.util.ui.JBUI -import com.jetbrains.rider.aspire.AspireIcons -import com.jetbrains.rider.aspire.util.ASPIRE_HOST_PATH -import java.awt.BorderLayout -import javax.swing.JPanel - -class AspireHostServiceViewDescriptor( - private val hostService: AspireHostService -) : ServiceViewDescriptor, DataProvider { - - private val toolbarActions = DefaultActionGroup( - ActionManager.getInstance().getAction("Aspire.Host.Run"), - ActionManager.getInstance().getAction("Aspire.Host.Debug"), - ActionManager.getInstance().getAction("Aspire.Host.Stop"), - Separator(), - ActionManager.getInstance().getAction("Aspire.Manifest"), - ActionManager.getInstance().getAction("Aspire.Dashboard"), - Separator(), - ActionManager.getInstance().getAction("Aspire.Settings"), - ActionManager.getInstance().getAction("Aspire.Help") - ) - - override fun getPresentation() = PresentationData().apply { - var icon = AspireIcons.Service - if (hostService.isActive) { - icon = BadgeIconSupplier(icon).liveIndicatorIcon - } - setIcon(icon) - addText(hostService.displayName, SimpleTextAttributes.REGULAR_ATTRIBUTES) - } - - override fun getContentComponent(): JPanel { - val console = hostService.consoleView - val panel = if (console == null) { - JBPanelWithEmptyText() - } else { - JPanel(BorderLayout()).apply { - border = JBUI.Borders.empty() - add(console.component) - } - } - return panel - } - - override fun getToolbarActions() = toolbarActions - - override fun getDataProvider() = this - - override fun getData(dataId: String) = - if (ASPIRE_HOST_PATH.`is`(dataId)) hostService.projectPathString - else null -} \ No newline at end of file diff --git a/src/main/kotlin/com/jetbrains/rider/aspire/services/AspireMainServiceViewContributor.kt b/src/main/kotlin/com/jetbrains/rider/aspire/services/AspireMainServiceViewContributor.kt new file mode 100644 index 00000000..98a14122 --- /dev/null +++ b/src/main/kotlin/com/jetbrains/rider/aspire/services/AspireMainServiceViewContributor.kt @@ -0,0 +1,21 @@ +package com.jetbrains.rider.aspire.services + +import com.intellij.execution.services.ServiceViewContributor +import com.intellij.execution.services.ServiceViewLazyContributor +import com.intellij.execution.services.SimpleServiceViewDescriptor +import com.intellij.openapi.project.Project +import com.jetbrains.rider.aspire.AspireIcons + +class AspireMainServiceViewContributor : ServiceViewContributor, + ServiceViewLazyContributor { + override fun getViewDescriptor(project: Project) = + SimpleServiceViewDescriptor("Aspire", AspireIcons.Service) + + override fun getServices(project: Project) = + AspireHostManager.getInstance(project).getAspireHosts().map { it.serviceViewContributor } + + override fun getServiceDescriptor( + project: Project, + service: AspireHostServiceViewContributor + ) = service.getViewDescriptor(project) +} \ No newline at end of file diff --git a/src/main/kotlin/com/jetbrains/rider/aspire/services/AspireResourceService.kt b/src/main/kotlin/com/jetbrains/rider/aspire/services/AspireResource.kt similarity index 66% rename from src/main/kotlin/com/jetbrains/rider/aspire/services/AspireResourceService.kt rename to src/main/kotlin/com/jetbrains/rider/aspire/services/AspireResource.kt index 47857fda..482618a8 100644 --- a/src/main/kotlin/com/jetbrains/rider/aspire/services/AspireResourceService.kt +++ b/src/main/kotlin/com/jetbrains/rider/aspire/services/AspireResource.kt @@ -1,15 +1,19 @@ package com.jetbrains.rider.aspire.services import com.intellij.execution.filters.TextConsoleBuilderFactory +import com.intellij.execution.process.ProcessHandler import com.intellij.execution.services.ServiceEventListener import com.intellij.execution.ui.ConsoleView import com.intellij.execution.ui.ConsoleViewContentType +import com.intellij.openapi.Disposable import com.intellij.openapi.project.Project import com.intellij.openapi.util.Disposer import com.jetbrains.rd.util.lifetime.Lifetime -import com.jetbrains.rider.aspire.AspireService import com.jetbrains.rider.aspire.generated.* import com.jetbrains.rider.aspire.util.getServiceInstanceId +import com.jetbrains.rider.debugger.DebuggerWorkerProcessHandler +import com.jetbrains.rider.run.ConsoleKind +import com.jetbrains.rider.run.createConsole import kotlinx.datetime.Instant import kotlinx.datetime.LocalDateTime import kotlinx.datetime.TimeZone @@ -18,12 +22,17 @@ import java.nio.file.Path import kotlin.io.path.Path import kotlin.math.roundToInt -class AspireResourceService( - wrapper: ResourceWrapper, +class AspireResource( + modelWrapper: ResourceWrapper, val lifetime: Lifetime, - private val hostService: AspireHostService, + private val aspireHost: AspireHost, private val project: Project -) { +): Disposable { + + val serviceViewContributor: AspireResourceServiceViewContributor by lazy { + AspireResourceServiceViewContributor(this) + } + var uid: String private set var name: String @@ -69,14 +78,15 @@ class AspireResourceService( var containerArgs: String? = null private set - val consoleView: ConsoleView = TextConsoleBuilderFactory + var consoleView: ConsoleView = TextConsoleBuilderFactory .getInstance() .createBuilder(project) .apply { setViewer(true) } .console init { - val model = wrapper.model.valueOrNull + val model = modelWrapper.model.valueOrNull + uid = model?.uid ?: "" name = model?.name ?: "" type = model?.type ?: ResourceType.Unknown @@ -90,12 +100,18 @@ class AspireResourceService( fillFromProperties(model?.properties ?: emptyArray()) - wrapper.model.advise(lifetime, ::update) - wrapper.logReceived.advise(lifetime, ::logReceived) + modelWrapper.model.advise(lifetime, ::update) + modelWrapper.logReceived.advise(lifetime, ::logReceived) - Disposer.register(AspireService.getInstance(project), consoleView) + Disposer.register(this, consoleView) project.messageBus.syncPublisher(ResourceListener.TOPIC).resourceCreated(this) + + lifetime.bracketIfAlive({ + sendServiceStructureChangedEvent() + }, { + sendServiceStructureChangedEvent() + }) } private fun fillFromProperties(properties: Array) { @@ -154,35 +170,71 @@ class AspireResourceService( } } - private fun update(resourceModel: ResourceModel) { - uid = resourceModel.uid - name = resourceModel.name - type = resourceModel.type - displayName = resourceModel.displayName - state = resourceModel.state - stateStyle = resourceModel.stateStyle - urls = resourceModel.urls - environment = resourceModel.environment + private fun update(model: ResourceModel) { + uid = model.uid + name = model.name + type = model.type + displayName = model.displayName + state = model.state + stateStyle = model.stateStyle + urls = model.urls + environment = model.environment - serviceInstanceId = resourceModel.getServiceInstanceId() + serviceInstanceId = model.getServiceInstanceId() - fillFromProperties(resourceModel.properties) + fillFromProperties(model.properties) project.messageBus.syncPublisher(ResourceListener.TOPIC).resourceUpdated(this) - val serviceEvent = ServiceEventListener.ServiceEvent.createEvent( - ServiceEventListener.EventType.SERVICE_CHILDREN_CHANGED, - hostService, - AspireServiceContributor::class.java + sendServiceChildrenChangedEvent() + } + + fun setHandler(processHandler: ProcessHandler) { + if (type != ResourceType.Project) return + + val handler = + if (processHandler is DebuggerWorkerProcessHandler) processHandler.debuggerWorkerRealHandler + else processHandler + val console = createConsole( + ConsoleKind.Normal, + handler, + project ) - project.messageBus.syncPublisher(ServiceEventListener.TOPIC).handle(serviceEvent) + Disposer.register(this, console) + consoleView = console + + sendServiceChildrenChangedEvent() } private fun logReceived(log: ResourceLog) { + if (type == ResourceType.Project) return + consoleView.print( log.text + "\n", if (!log.isError) ConsoleViewContentType.NORMAL_OUTPUT else ConsoleViewContentType.ERROR_OUTPUT ) } -} + + + private fun sendServiceStructureChangedEvent() { + val serviceEvent = ServiceEventListener.ServiceEvent.createEvent( + ServiceEventListener.EventType.SERVICE_STRUCTURE_CHANGED, + aspireHost.serviceViewContributor.asService(), + AspireMainServiceViewContributor::class.java + ) + project.messageBus.syncPublisher(ServiceEventListener.TOPIC).handle(serviceEvent) + } + + private fun sendServiceChildrenChangedEvent() { + val serviceEvent = ServiceEventListener.ServiceEvent.createEvent( + ServiceEventListener.EventType.SERVICE_CHILDREN_CHANGED, + aspireHost.serviceViewContributor.asService(), + AspireMainServiceViewContributor::class.java + ) + project.messageBus.syncPublisher(ServiceEventListener.TOPIC).handle(serviceEvent) + } + + override fun dispose() { + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/jetbrains/rider/aspire/services/AspireResourceServiceViewContributor.kt b/src/main/kotlin/com/jetbrains/rider/aspire/services/AspireResourceServiceViewContributor.kt new file mode 100644 index 00000000..a0dcfa1a --- /dev/null +++ b/src/main/kotlin/com/jetbrains/rider/aspire/services/AspireResourceServiceViewContributor.kt @@ -0,0 +1,75 @@ +package com.jetbrains.rider.aspire.services + +import com.intellij.execution.services.ServiceViewDescriptor +import com.intellij.execution.services.ServiceViewProvidingContributor +import com.intellij.ide.projectView.PresentationData +import com.intellij.openapi.actionSystem.ActionManager +import com.intellij.openapi.actionSystem.DataProvider +import com.intellij.openapi.actionSystem.DefaultActionGroup +import com.intellij.openapi.project.Project +import com.intellij.ui.SimpleTextAttributes +import com.intellij.ui.components.JBTabbedPane +import com.jetbrains.rider.aspire.AspireBundle +import com.jetbrains.rider.aspire.services.components.ResourceConsolePanel +import com.jetbrains.rider.aspire.services.components.ResourceDashboardPanel +import com.jetbrains.rider.aspire.util.* +import java.awt.BorderLayout +import javax.swing.JPanel + +class AspireResourceServiceViewContributor( + private val aspireResource: AspireResource +) : ServiceViewProvidingContributor { + private val descriptor = AspireResourceServiceViewDescriptor() + + override fun getViewDescriptor(project: Project): ServiceViewDescriptor { + return descriptor + } + + override fun asService() = aspireResource + + override fun getServices(project: Project): List { + return emptyList() + } + + override fun getServiceDescriptor( + project: Project, + service: AspireResourceServiceViewContributor + ) = service.getViewDescriptor(project) + + private fun getPanel(): JPanel { + val tabs = JBTabbedPane() + tabs.addTab(AspireBundle.getMessage("service.tab.dashboard"), ResourceDashboardPanel(aspireResource)) + tabs.addTab(AspireBundle.getMessage("service.tab.console"), ResourceConsolePanel(aspireResource)) + + val panel = JPanel(BorderLayout()).apply { + add(tabs, BorderLayout.CENTER) + } + + return panel + } + + private inner class AspireResourceServiceViewDescriptor : ServiceViewDescriptor, DataProvider { + private val toolbarActions = DefaultActionGroup( + ActionManager.getInstance().getAction("Aspire.Resource.Stop") + ) + + override fun getPresentation() = PresentationData().apply { + val icon = getIcon(aspireResource.type, aspireResource.state) + setIcon(icon) + addText(aspireResource.displayName, SimpleTextAttributes.REGULAR_ATTRIBUTES) + } + + override fun getContentComponent() = getPanel() + + override fun getToolbarActions() = toolbarActions + + override fun getDataProvider() = this + + override fun getData(dataId: String) = + if (ASPIRE_RESOURCE_UID.`is`(dataId)) aspireResource.uid + else if (ASPIRE_RESOURCE_SERVICE_INSTANCE_ID.`is`(dataId)) aspireResource.serviceInstanceId + else if (ASPIRE_RESOURCE_TYPE.`is`(dataId)) aspireResource.type + else if (ASPIRE_RESOURCE_STATE.`is`(dataId)) aspireResource.state + else null + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/jetbrains/rider/aspire/services/AspireResourceServiceViewDescriptor.kt b/src/main/kotlin/com/jetbrains/rider/aspire/services/AspireResourceServiceViewDescriptor.kt deleted file mode 100644 index 7ef3e810..00000000 --- a/src/main/kotlin/com/jetbrains/rider/aspire/services/AspireResourceServiceViewDescriptor.kt +++ /dev/null @@ -1,55 +0,0 @@ -@file:Suppress("UnstableApiUsage") - -package com.jetbrains.rider.aspire.services - -import com.intellij.execution.services.ServiceViewDescriptor -import com.intellij.ide.projectView.PresentationData -import com.intellij.openapi.actionSystem.ActionManager -import com.intellij.openapi.actionSystem.DataProvider -import com.intellij.openapi.actionSystem.DefaultActionGroup -import com.intellij.ui.SimpleTextAttributes -import com.intellij.ui.components.JBTabbedPane -import com.jetbrains.rider.aspire.AspireBundle -import com.jetbrains.rider.aspire.services.components.ResourceConsolePanel -import com.jetbrains.rider.aspire.services.components.ResourceDashboardPanel -import com.jetbrains.rider.aspire.util.* -import java.awt.BorderLayout -import javax.swing.JPanel - -class AspireResourceServiceViewDescriptor( - private val resourceService: AspireResourceService -) : ServiceViewDescriptor, DataProvider { - - private val toolbarActions = DefaultActionGroup( - ActionManager.getInstance().getAction("Aspire.Resource.Stop") - ) - - private val mainPanel by lazy { - val tabs = JBTabbedPane() - tabs.addTab(AspireBundle.getMessage("service.tab.dashboard"), ResourceDashboardPanel(resourceService)) - tabs.addTab(AspireBundle.getMessage("service.tab.console"), ResourceConsolePanel(resourceService)) - - JPanel(BorderLayout()).apply { - add(tabs, BorderLayout.CENTER) - } - } - - override fun getPresentation() = PresentationData().apply { - val icon = getIcon(resourceService.type, resourceService.state) - setIcon(icon) - addText(resourceService.displayName, SimpleTextAttributes.REGULAR_ATTRIBUTES) - } - - override fun getContentComponent() = mainPanel - - override fun getToolbarActions() = toolbarActions - - override fun getDataProvider() = this - - override fun getData(dataId: String) = - if (ASPIRE_RESOURCE_UID.`is`(dataId)) resourceService.uid - else if (ASPIRE_RESOURCE_SERVICE_INSTANCE_ID.`is`(dataId)) resourceService.serviceInstanceId - else if (ASPIRE_RESOURCE_TYPE.`is`(dataId)) resourceService.type - else if (ASPIRE_RESOURCE_STATE.`is`(dataId)) resourceService.state - else null -} \ No newline at end of file diff --git a/src/main/kotlin/com/jetbrains/rider/aspire/services/AspireServiceContributor.kt b/src/main/kotlin/com/jetbrains/rider/aspire/services/AspireServiceContributor.kt deleted file mode 100644 index df81e799..00000000 --- a/src/main/kotlin/com/jetbrains/rider/aspire/services/AspireServiceContributor.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.jetbrains.rider.aspire.services - -import com.intellij.execution.services.ServiceViewContributor -import com.intellij.execution.services.SimpleServiceViewDescriptor -import com.intellij.openapi.project.Project -import com.jetbrains.rider.aspire.AspireIcons - -class AspireServiceContributor : ServiceViewContributor { - override fun getViewDescriptor(project: Project) = - SimpleServiceViewDescriptor("Aspire", AspireIcons.Service) - - override fun getServices(project: Project) = - AspireServiceManager.getInstance(project) - .getHostServices() - .toMutableList() - - override fun getServiceDescriptor(project: Project, host: AspireHostService) = - host.getViewDescriptor(project) -} \ No newline at end of file diff --git a/src/main/kotlin/com/jetbrains/rider/aspire/services/AspireServiceManager.kt b/src/main/kotlin/com/jetbrains/rider/aspire/services/AspireServiceManager.kt deleted file mode 100644 index 567ed90a..00000000 --- a/src/main/kotlin/com/jetbrains/rider/aspire/services/AspireServiceManager.kt +++ /dev/null @@ -1,213 +0,0 @@ -package com.jetbrains.rider.aspire.services - -import com.intellij.execution.ExecutionResult -import com.intellij.execution.RunManagerListener -import com.intellij.execution.RunnerAndConfigurationSettings -import com.intellij.execution.services.ServiceEventListener -import com.intellij.execution.services.ServiceViewManager -import com.intellij.openapi.application.EDT -import com.intellij.openapi.components.Service -import com.intellij.openapi.components.service -import com.intellij.openapi.diagnostic.logger -import com.intellij.openapi.project.Project -import com.intellij.util.application -import com.jetbrains.rd.util.addUnique -import com.jetbrains.rd.util.lifetime.Lifetime -import com.jetbrains.rider.aspire.generated.AspireSessionHostModel -import com.jetbrains.rider.aspire.generated.ResourceState -import com.jetbrains.rider.aspire.generated.ResourceType -import com.jetbrains.rider.aspire.generated.ResourceWrapper -import com.jetbrains.rider.aspire.run.AspireHostConfig -import com.jetbrains.rider.aspire.run.AspireHostConfiguration -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext -import java.nio.file.Path -import java.util.concurrent.ConcurrentHashMap -import kotlin.io.path.Path -import kotlin.io.path.absolutePathString -import kotlin.io.path.nameWithoutExtension - -@Service(Service.Level.PROJECT) -class AspireServiceManager(private val project: Project) { - companion object { - fun getInstance(project: Project) = project.service() - - private val LOG = logger() - } - - private val hostServices = ConcurrentHashMap() - private val resourceServices = ConcurrentHashMap>() - - fun getHostServices() = hostServices.values.toList() - fun getHostService(hostPath: String) = hostServices[hostPath] - fun getResourceServices(hostPath: String) = - resourceServices[hostPath]?.values - ?.asSequence() - ?.filter { it.type != ResourceType.Unknown } - ?.filter { it.state != ResourceState.Hidden } - ?.sortedBy { it.type } - ?.toList() - ?: emptyList() - - private val serviceEventPublisher = project.messageBus.syncPublisher(ServiceEventListener.TOPIC) - - fun addAspireHostService(host: AspireHostService) { - if (hostServices.containsKey(host.projectPathString)) return - - LOG.trace("Adding a new Aspire host ${host.projectPathString}") - hostServices[host.projectPathString] = host - resourceServices[host.projectPathString] = mutableMapOf() - - val event = ServiceEventListener.ServiceEvent.createEvent( - ServiceEventListener.EventType.SERVICE_ADDED, - host, - AspireServiceContributor::class.java - ) - serviceEventPublisher.handle(event) - } - - fun removeAspireHostService(hostPath: Path) { - val hostPathString = hostPath.absolutePathString() - LOG.trace("Removing the Aspire host $hostPathString") - - val host = hostServices.remove(hostPathString) - resourceServices.remove(hostPathString) - if (host == null) return - - val event = ServiceEventListener.ServiceEvent.createEvent( - ServiceEventListener.EventType.SERVICE_REMOVED, - host, - AspireServiceContributor::class.java - ) - serviceEventPublisher.handle(event) - } - - fun updateAspireHostService(hostPath: Path, name: String) { - val hostPathString = hostPath.absolutePathString() - LOG.trace("Updating the Aspire host $hostPathString") - - val host = hostServices[hostPathString] ?: return - host.update(name) - - sendServiceChangedEvent(host) - } - - fun updateAspireHostService(hostPath: Path, executionResult: ExecutionResult) { - val hostPathString = hostPath.absolutePathString() - LOG.trace("Setting the execution result to the Aspire host $hostPathString") - - val host = hostServices[hostPathString] ?: return - host.update(executionResult, project) - - sendServiceChangedEvent(host) - } - - suspend fun startAspireHostService( - aspireHostConfig: AspireHostConfig, - sessionHostModel: AspireSessionHostModel - ) { - val hostPathString = aspireHostConfig.aspireHostProjectPath.absolutePathString() - LOG.trace("Starting the Aspire Host $hostPathString") - - val aspireHostServiceLifetime = aspireHostConfig.aspireHostLifetime.createNested() - - val hostService = hostServices[hostPathString] ?: return - - val serviceViewManager = ServiceViewManager.getInstance(project) - withContext(Dispatchers.EDT) { - serviceViewManager.select(hostService, AspireServiceContributor::class.java, true, true) - } - - aspireHostServiceLifetime.bracketIfAlive({ - hostService.startHost( - aspireHostConfig.aspireHostProjectUrl, - sessionHostModel, - aspireHostServiceLifetime - ) - sendServiceChangedEvent(hostService) - }, { - hostService.stopHost() - sendServiceChangedEvent(hostService) - }) - - withContext(Dispatchers.EDT) { - sessionHostModel.resources.view(aspireHostServiceLifetime) { resourceLifetime, resourceId, resource -> - viewResource(resourceId, resource, resourceLifetime, hostService) - } - } - } - - private fun sendServiceChangedEvent(host: AspireHostService) { - val event = ServiceEventListener.ServiceEvent.createEvent( - ServiceEventListener.EventType.SERVICE_CHANGED, - host, - AspireServiceContributor::class.java - ) - serviceEventPublisher.handle(event) - } - - private fun viewResource( - resourceId: String, - resource: ResourceWrapper, - resourceLifetime: Lifetime, - hostService: AspireHostService - ) { - LOG.trace("Adding a new resource $resourceId") - - val resourcesByHost = resourceServices[hostService.projectPathString] ?: return - - val resourceService = AspireResourceService(resource, resourceLifetime, hostService, project) - resourcesByHost.addUnique(resourceLifetime, resourceId, resourceService) - - val serviceViewManager = ServiceViewManager.getInstance(project) - application.invokeLater { - serviceViewManager.expand(hostService, AspireServiceContributor::class.java) - } - - resource.isInitialized.set(true) - - resourceLifetime.bracketIfAlive({ - sendServiceStructureChangedEvent(hostService) - }, { - sendServiceStructureChangedEvent(hostService) - }) - } - - private fun sendServiceStructureChangedEvent(host: AspireHostService) { - val serviceEvent = ServiceEventListener.ServiceEvent.createEvent( - ServiceEventListener.EventType.SERVICE_STRUCTURE_CHANGED, - host, - AspireServiceContributor::class.java - ) - project.messageBus.syncPublisher(ServiceEventListener.TOPIC).handle(serviceEvent) - } - - class Listener(private val project: Project) : RunManagerListener { - override fun runConfigurationAdded(settings: RunnerAndConfigurationSettings) { - val configuration = settings.configuration - if (configuration !is AspireHostConfiguration) return - val params = configuration.parameters - val projectPath = Path(params.projectFilePath) - val name = projectPath.nameWithoutExtension - val host = AspireHostService(name, projectPath) - getInstance(project).addAspireHostService(host) - } - - override fun runConfigurationChanged(settings: RunnerAndConfigurationSettings) { - val configuration = settings.configuration - if (configuration !is AspireHostConfiguration) return - val params = configuration.parameters - val projectPath = Path(params.projectFilePath) - val name = projectPath.nameWithoutExtension - getInstance(project).updateAspireHostService(projectPath, name) - } - - override fun runConfigurationRemoved(settings: RunnerAndConfigurationSettings) { - val configuration = settings.configuration - if (configuration !is AspireHostConfiguration) return - val params = configuration.parameters - val projectPath = Path(params.projectFilePath) - getInstance(project).removeAspireHostService(projectPath) - } - } -} \ No newline at end of file diff --git a/src/main/kotlin/com/jetbrains/rider/aspire/services/ResourceListener.kt b/src/main/kotlin/com/jetbrains/rider/aspire/services/ResourceListener.kt index 85b7b1c8..694dda2f 100644 --- a/src/main/kotlin/com/jetbrains/rider/aspire/services/ResourceListener.kt +++ b/src/main/kotlin/com/jetbrains/rider/aspire/services/ResourceListener.kt @@ -8,6 +8,6 @@ interface ResourceListener { val TOPIC = Topic.create("Aspire Resource Listener", ResourceListener::class.java) } - fun resourceCreated(resource: AspireResourceService) - fun resourceUpdated(resource: AspireResourceService) + fun resourceCreated(resource: AspireResource) + fun resourceUpdated(resource: AspireResource) } \ No newline at end of file diff --git a/src/main/kotlin/com/jetbrains/rider/aspire/services/components/ResourceConsolePanel.kt b/src/main/kotlin/com/jetbrains/rider/aspire/services/components/ResourceConsolePanel.kt index 13d97086..aae9b250 100644 --- a/src/main/kotlin/com/jetbrains/rider/aspire/services/components/ResourceConsolePanel.kt +++ b/src/main/kotlin/com/jetbrains/rider/aspire/services/components/ResourceConsolePanel.kt @@ -2,9 +2,9 @@ package com.jetbrains.rider.aspire.services.components import com.intellij.util.ui.JBUI import com.intellij.util.ui.components.BorderLayoutPanel -import com.jetbrains.rider.aspire.services.AspireResourceService +import com.jetbrains.rider.aspire.services.AspireResource -class ResourceConsolePanel(resourceService: AspireResourceService) : BorderLayoutPanel() { +class ResourceConsolePanel(resourceService: AspireResource) : BorderLayoutPanel() { init { border = JBUI.Borders.empty() add(resourceService.consoleView.component) diff --git a/src/main/kotlin/com/jetbrains/rider/aspire/services/components/ResourceDashboardPanel.kt b/src/main/kotlin/com/jetbrains/rider/aspire/services/components/ResourceDashboardPanel.kt index 92acb7e3..49dd5364 100644 --- a/src/main/kotlin/com/jetbrains/rider/aspire/services/components/ResourceDashboardPanel.kt +++ b/src/main/kotlin/com/jetbrains/rider/aspire/services/components/ResourceDashboardPanel.kt @@ -14,19 +14,19 @@ import com.intellij.util.ui.UIUtil import com.intellij.util.ui.components.BorderLayoutPanel import com.jetbrains.rider.aspire.AspireBundle import com.jetbrains.rider.aspire.generated.ResourceType -import com.jetbrains.rider.aspire.services.AspireResourceService +import com.jetbrains.rider.aspire.services.AspireResource import com.jetbrains.rider.aspire.util.getIcon import kotlin.io.path.absolutePathString -class ResourceDashboardPanel(resourceService: AspireResourceService) : BorderLayoutPanel() { - private var panel = setUpPanel(resourceService) +class ResourceDashboardPanel(aspireResource: AspireResource) : BorderLayoutPanel() { + private var panel = setUpPanel(aspireResource) init { border = JBUI.Borders.empty(5, 10) add(ScrollPaneFactory.createScrollPane(panel, SideBorder.NONE)) } - private fun setUpPanel(resourceData: AspireResourceService): DialogPanel = panel { + private fun setUpPanel(resourceData: AspireResource): DialogPanel = panel { row { val resourceIcon = getIcon(resourceData.type, resourceData.state) icon(resourceIcon) diff --git a/src/main/kotlin/com/jetbrains/rider/aspire/sessionHost/SessionManager.kt b/src/main/kotlin/com/jetbrains/rider/aspire/sessionHost/SessionManager.kt index cf083215..3e358bb1 100644 --- a/src/main/kotlin/com/jetbrains/rider/aspire/sessionHost/SessionManager.kt +++ b/src/main/kotlin/com/jetbrains/rider/aspire/sessionHost/SessionManager.kt @@ -1,11 +1,7 @@ package com.jetbrains.rider.aspire.sessionHost import com.intellij.database.util.common.removeIf -import com.intellij.execution.process.KillableProcessHandler -import com.intellij.execution.process.ProcessAdapter -import com.intellij.execution.process.ProcessEvent -import com.intellij.execution.process.ProcessListener -import com.intellij.execution.process.ProcessOutputType +import com.intellij.execution.process.* import com.intellij.openapi.application.EDT import com.intellij.openapi.components.Service import com.intellij.openapi.components.service @@ -77,7 +73,7 @@ class SessionManager(private val project: Project, scope: CoroutineScope) { command.sessionLifetimeDefinition, SequentialLifetimes(command.sessionLifetimeDefinition), command.sessionEvents, - command.aspireHostConfig.hostRunConfiguration + command.aspireHostConfig.aspireHostRunConfiguration ) sessions[command.sessionId] = session diff --git a/src/main/kotlin/com/jetbrains/rider/aspire/sessionHost/dotnetProject/DotNetProjectSessionDebugProfile.kt b/src/main/kotlin/com/jetbrains/rider/aspire/sessionHost/dotnetProject/DotNetProjectSessionDebugProfile.kt index 7e68a2cf..fec4b7cc 100644 --- a/src/main/kotlin/com/jetbrains/rider/aspire/sessionHost/dotnetProject/DotNetProjectSessionDebugProfile.kt +++ b/src/main/kotlin/com/jetbrains/rider/aspire/sessionHost/dotnetProject/DotNetProjectSessionDebugProfile.kt @@ -1,29 +1,26 @@ package com.jetbrains.rider.aspire.sessionHost.dotnetProject import com.intellij.execution.Executor -import com.intellij.execution.configurations.RunProfile import com.intellij.execution.process.ProcessListener import com.intellij.execution.runners.ExecutionEnvironment import com.jetbrains.rd.util.lifetime.Lifetime import com.jetbrains.rider.aspire.sessionHost.projectLaunchers.ProjectSessionDebugProfileState +import com.jetbrains.rider.aspire.sessionHost.projectLaunchers.ProjectSessionProfile import com.jetbrains.rider.debugger.IRiderDebuggable import com.jetbrains.rider.runtime.DotNetExecutable import com.jetbrains.rider.runtime.dotNetCore.DotNetCoreRuntime -import icons.RiderIcons +import java.nio.file.Path class DotNetProjectSessionDebugProfile( private val sessionId: String, - private val projectName: String, - private val dotnetExecutable: DotNetExecutable, + projectName: String, + dotnetExecutable: DotNetExecutable, private val dotnetRuntime: DotNetCoreRuntime, private val sessionProcessEventListener: ProcessListener, private val sessionProcessTerminatedListener: ProcessListener, - private val sessionProcessLifetime: Lifetime -) : RunProfile, IRiderDebuggable { - override fun getName() = projectName - - override fun getIcon() = RiderIcons.RunConfigurations.DotNetProject - + private val sessionProcessLifetime: Lifetime, + aspireHostProjectPath: Path? +) : ProjectSessionProfile(projectName, dotnetExecutable, aspireHostProjectPath), IRiderDebuggable { override fun getState( executor: Executor, environment: ExecutionEnvironment diff --git a/src/main/kotlin/com/jetbrains/rider/aspire/sessionHost/dotnetProject/DotNetProjectSessionProcessLauncher.kt b/src/main/kotlin/com/jetbrains/rider/aspire/sessionHost/dotnetProject/DotNetProjectSessionProcessLauncher.kt index 716f636c..c250829d 100644 --- a/src/main/kotlin/com/jetbrains/rider/aspire/sessionHost/dotnetProject/DotNetProjectSessionProcessLauncher.kt +++ b/src/main/kotlin/com/jetbrains/rider/aspire/sessionHost/dotnetProject/DotNetProjectSessionProcessLauncher.kt @@ -23,7 +23,8 @@ class DotNetProjectSessionProcessLauncher : BaseProjectSessionProcessLauncher() dotnetRuntime: DotNetCoreRuntime, sessionProcessEventListener: ProcessListener, sessionProcessTerminatedListener: ProcessListener, - sessionProcessLifetime: Lifetime + sessionProcessLifetime: Lifetime, + aspireHostProjectPath: Path? ) = DotNetProjectSessionRunProfile( sessionId, projectName, @@ -31,7 +32,8 @@ class DotNetProjectSessionProcessLauncher : BaseProjectSessionProcessLauncher() dotnetRuntime, sessionProcessEventListener, sessionProcessTerminatedListener, - sessionProcessLifetime + sessionProcessLifetime, + aspireHostProjectPath ) override fun getDebugProfile( @@ -43,7 +45,8 @@ class DotNetProjectSessionProcessLauncher : BaseProjectSessionProcessLauncher() browserSettings: StartBrowserSettings?, sessionProcessEventListener: ProcessListener, sessionProcessTerminatedListener: ProcessListener, - sessionProcessLifetime: Lifetime + sessionProcessLifetime: Lifetime, + aspireHostProjectPath: Path? ) = DotNetProjectSessionDebugProfile( sessionId, projectName, @@ -51,6 +54,7 @@ class DotNetProjectSessionProcessLauncher : BaseProjectSessionProcessLauncher() dotnetRuntime, sessionProcessEventListener, sessionProcessTerminatedListener, - sessionProcessLifetime + sessionProcessLifetime, + aspireHostProjectPath ) } \ No newline at end of file diff --git a/src/main/kotlin/com/jetbrains/rider/aspire/sessionHost/dotnetProject/DotNetProjectSessionRunProfile.kt b/src/main/kotlin/com/jetbrains/rider/aspire/sessionHost/dotnetProject/DotNetProjectSessionRunProfile.kt index 116366eb..8e082012 100644 --- a/src/main/kotlin/com/jetbrains/rider/aspire/sessionHost/dotnetProject/DotNetProjectSessionRunProfile.kt +++ b/src/main/kotlin/com/jetbrains/rider/aspire/sessionHost/dotnetProject/DotNetProjectSessionRunProfile.kt @@ -4,20 +4,22 @@ import com.intellij.execution.Executor import com.intellij.execution.process.ProcessListener import com.intellij.execution.runners.ExecutionEnvironment import com.jetbrains.rd.util.lifetime.Lifetime -import com.jetbrains.rider.aspire.sessionHost.projectLaunchers.ProjectSessionRunProfile +import com.jetbrains.rider.aspire.sessionHost.projectLaunchers.ProjectSessionProfile import com.jetbrains.rider.aspire.sessionHost.projectLaunchers.ProjectSessionRunProfileState import com.jetbrains.rider.runtime.DotNetExecutable import com.jetbrains.rider.runtime.dotNetCore.DotNetCoreRuntime +import java.nio.file.Path class DotNetProjectSessionRunProfile( private val sessionId: String, projectName: String, - private val dotnetExecutable: DotNetExecutable, + dotnetExecutable: DotNetExecutable, private val dotnetRuntime: DotNetCoreRuntime, private val sessionProcessEventListener: ProcessListener, private val sessionProcessTerminatedListener: ProcessListener, - private val sessionProcessLifetime: Lifetime -) : ProjectSessionRunProfile(projectName) { + private val sessionProcessLifetime: Lifetime, + aspireHostProjectPath: Path? +) : ProjectSessionProfile(projectName, dotnetExecutable, aspireHostProjectPath) { override fun getState( executor: Executor, environment: ExecutionEnvironment diff --git a/src/main/kotlin/com/jetbrains/rider/aspire/sessionHost/projectLaunchers/BaseProjectSessionProcessLauncher.kt b/src/main/kotlin/com/jetbrains/rider/aspire/sessionHost/projectLaunchers/BaseProjectSessionProcessLauncher.kt index c1f3d060..8843eca5 100644 --- a/src/main/kotlin/com/jetbrains/rider/aspire/sessionHost/projectLaunchers/BaseProjectSessionProcessLauncher.kt +++ b/src/main/kotlin/com/jetbrains/rider/aspire/sessionHost/projectLaunchers/BaseProjectSessionProcessLauncher.kt @@ -63,6 +63,7 @@ abstract class BaseProjectSessionProcessLauncher : SessionProcessLauncherExtensi val runtime = getDotNetRuntime(executableWithHotReload, project) ?: return val projectName = Path(sessionModel.projectPath).nameWithoutExtension + val aspireHostProjectPath = hostRunConfiguration?.let { Path(it.parameters.projectFilePath) } val profile = getRunProfile( sessionId, projectName, @@ -70,7 +71,8 @@ abstract class BaseProjectSessionProcessLauncher : SessionProcessLauncherExtensi runtime, sessionProcessEventListener, sessionProcessTerminatedListener, - sessionProcessLifetime + sessionProcessLifetime, + aspireHostProjectPath ) val environment = ExecutionEnvironmentBuilder @@ -95,7 +97,8 @@ abstract class BaseProjectSessionProcessLauncher : SessionProcessLauncherExtensi dotnetRuntime: DotNetCoreRuntime, sessionProcessEventListener: ProcessListener, sessionProcessTerminatedListener: ProcessListener, - sessionProcessLifetime: Lifetime + sessionProcessLifetime: Lifetime, + aspireHostProjectPath: Path? ): RunProfile override suspend fun launchDebugProcess( @@ -113,6 +116,7 @@ abstract class BaseProjectSessionProcessLauncher : SessionProcessLauncherExtensi val runtime = getDotNetRuntime(executable, project) ?: return val projectPath = Path(sessionModel.projectPath) + val aspireHostProjectPath = hostRunConfiguration?.let { Path(it.parameters.projectFilePath) } val profile = getDebugProfile( sessionId, projectPath.nameWithoutExtension, @@ -122,7 +126,8 @@ abstract class BaseProjectSessionProcessLauncher : SessionProcessLauncherExtensi browserSettings, sessionProcessEventListener, sessionProcessTerminatedListener, - sessionProcessLifetime + sessionProcessLifetime, + aspireHostProjectPath ) val environment = ExecutionEnvironmentBuilder @@ -147,7 +152,8 @@ abstract class BaseProjectSessionProcessLauncher : SessionProcessLauncherExtensi browserSettings: StartBrowserSettings?, sessionProcessEventListener: ProcessListener, sessionProcessTerminatedListener: ProcessListener, - sessionProcessLifetime: Lifetime + sessionProcessLifetime: Lifetime, + aspireHostProjectPath: Path? ): RunProfile protected suspend fun getDotNetExecutable( diff --git a/src/main/kotlin/com/jetbrains/rider/aspire/sessionHost/projectLaunchers/ProjectSessionRunProfile.kt b/src/main/kotlin/com/jetbrains/rider/aspire/sessionHost/projectLaunchers/ProjectSessionProfile.kt similarity index 50% rename from src/main/kotlin/com/jetbrains/rider/aspire/sessionHost/projectLaunchers/ProjectSessionRunProfile.kt rename to src/main/kotlin/com/jetbrains/rider/aspire/sessionHost/projectLaunchers/ProjectSessionProfile.kt index 9dc5f73e..72b2334d 100644 --- a/src/main/kotlin/com/jetbrains/rider/aspire/sessionHost/projectLaunchers/ProjectSessionRunProfile.kt +++ b/src/main/kotlin/com/jetbrains/rider/aspire/sessionHost/projectLaunchers/ProjectSessionProfile.kt @@ -1,9 +1,15 @@ package com.jetbrains.rider.aspire.sessionHost.projectLaunchers import com.intellij.execution.configurations.RunProfile +import com.jetbrains.rider.runtime.DotNetExecutable import icons.RiderIcons +import java.nio.file.Path -abstract class ProjectSessionRunProfile(private val projectName: String) : RunProfile { +abstract class ProjectSessionProfile( + private val projectName: String, + val dotnetExecutable: DotNetExecutable, + val aspireHostProjectPath: Path? +) : RunProfile { override fun getName() = projectName override fun getIcon() = RiderIcons.RunConfigurations.DotNetProject diff --git a/src/main/kotlin/com/jetbrains/rider/aspire/sessionHost/projectLaunchers/ProjectSessionProgramRunner.kt b/src/main/kotlin/com/jetbrains/rider/aspire/sessionHost/projectLaunchers/ProjectSessionProgramRunner.kt index 8d0d783a..2369645d 100644 --- a/src/main/kotlin/com/jetbrains/rider/aspire/sessionHost/projectLaunchers/ProjectSessionProgramRunner.kt +++ b/src/main/kotlin/com/jetbrains/rider/aspire/sessionHost/projectLaunchers/ProjectSessionProgramRunner.kt @@ -6,5 +6,5 @@ import com.jetbrains.rider.debugger.DotNetProgramRunner class ProjectSessionProgramRunner : DotNetProgramRunner() { override fun canRun(executorId: String, runProfile: RunProfile) = - executorId == DefaultRunExecutor.EXECUTOR_ID && runProfile is ProjectSessionRunProfile + executorId == DefaultRunExecutor.EXECUTOR_ID && runProfile is ProjectSessionProfile } \ No newline at end of file diff --git a/src/main/kotlin/com/jetbrains/rider/aspire/sessionHost/wasmHost/WasmHostProjectSessionDebugProfile.kt b/src/main/kotlin/com/jetbrains/rider/aspire/sessionHost/wasmHost/WasmHostProjectSessionDebugProfile.kt index 9a2afee1..722be9a1 100644 --- a/src/main/kotlin/com/jetbrains/rider/aspire/sessionHost/wasmHost/WasmHostProjectSessionDebugProfile.kt +++ b/src/main/kotlin/com/jetbrains/rider/aspire/sessionHost/wasmHost/WasmHostProjectSessionDebugProfile.kt @@ -1,31 +1,27 @@ package com.jetbrains.rider.aspire.sessionHost.wasmHost import com.intellij.execution.Executor -import com.intellij.execution.configurations.RunProfile import com.intellij.execution.process.ProcessListener import com.intellij.execution.runners.ExecutionEnvironment import com.intellij.ide.browsers.StartBrowserSettings import com.jetbrains.rd.util.lifetime.Lifetime +import com.jetbrains.rider.aspire.sessionHost.projectLaunchers.ProjectSessionProfile import com.jetbrains.rider.runtime.DotNetExecutable import com.jetbrains.rider.runtime.dotNetCore.DotNetCoreRuntime -import icons.RiderIcons import java.nio.file.Path class WasmHostProjectSessionDebugProfile( private val sessionId: String, - private val projectName: String, + projectName: String, private val projectPath: Path, - private val dotnetExecutable: DotNetExecutable, + dotnetExecutable: DotNetExecutable, private val dotnetRuntime: DotNetCoreRuntime, private val browserSettings: StartBrowserSettings?, private val sessionProcessEventListener: ProcessListener, private val sessionProcessTerminatedListener: ProcessListener, - private val sessionProcessLifetime: Lifetime -) : RunProfile { - override fun getName() = projectName - - override fun getIcon() = RiderIcons.RunConfigurations.DotNetProject - + private val sessionProcessLifetime: Lifetime, + aspireHostProjectPath: Path? +) : ProjectSessionProfile(projectName, dotnetExecutable, aspireHostProjectPath) { override fun getState( executor: Executor, environment: ExecutionEnvironment diff --git a/src/main/kotlin/com/jetbrains/rider/aspire/sessionHost/wasmHost/WasmHostProjectSessionProcessLauncher.kt b/src/main/kotlin/com/jetbrains/rider/aspire/sessionHost/wasmHost/WasmHostProjectSessionProcessLauncher.kt index a4296068..5a1fe911 100644 --- a/src/main/kotlin/com/jetbrains/rider/aspire/sessionHost/wasmHost/WasmHostProjectSessionProcessLauncher.kt +++ b/src/main/kotlin/com/jetbrains/rider/aspire/sessionHost/wasmHost/WasmHostProjectSessionProcessLauncher.kt @@ -35,7 +35,8 @@ class WasmHostProjectSessionProcessLauncher : BaseProjectSessionProcessLauncher( dotnetRuntime: DotNetCoreRuntime, sessionProcessEventListener: ProcessListener, sessionProcessTerminatedListener: ProcessListener, - sessionProcessLifetime: Lifetime + sessionProcessLifetime: Lifetime, + aspireHostProjectPath: Path? ) = WasmHostSessionRunProfile( sessionId, projectName, @@ -43,7 +44,8 @@ class WasmHostProjectSessionProcessLauncher : BaseProjectSessionProcessLauncher( dotnetRuntime, sessionProcessEventListener, sessionProcessTerminatedListener, - sessionProcessLifetime + sessionProcessLifetime, + aspireHostProjectPath ) override fun getDebugProfile( @@ -55,7 +57,8 @@ class WasmHostProjectSessionProcessLauncher : BaseProjectSessionProcessLauncher( browserSettings: StartBrowserSettings?, sessionProcessEventListener: ProcessListener, sessionProcessTerminatedListener: ProcessListener, - sessionProcessLifetime: Lifetime + sessionProcessLifetime: Lifetime, + aspireHostProjectPath: Path? ) = WasmHostProjectSessionDebugProfile( sessionId, projectPath.nameWithoutExtension, @@ -65,6 +68,7 @@ class WasmHostProjectSessionProcessLauncher : BaseProjectSessionProcessLauncher( browserSettings, sessionProcessEventListener, sessionProcessTerminatedListener, - sessionProcessLifetime + sessionProcessLifetime, + aspireHostProjectPath ) } \ No newline at end of file diff --git a/src/main/kotlin/com/jetbrains/rider/aspire/sessionHost/wasmHost/WasmHostSessionRunProfile.kt b/src/main/kotlin/com/jetbrains/rider/aspire/sessionHost/wasmHost/WasmHostSessionRunProfile.kt index a95c0be9..be07bb63 100644 --- a/src/main/kotlin/com/jetbrains/rider/aspire/sessionHost/wasmHost/WasmHostSessionRunProfile.kt +++ b/src/main/kotlin/com/jetbrains/rider/aspire/sessionHost/wasmHost/WasmHostSessionRunProfile.kt @@ -4,20 +4,22 @@ import com.intellij.execution.Executor import com.intellij.execution.process.ProcessListener import com.intellij.execution.runners.ExecutionEnvironment import com.jetbrains.rd.util.lifetime.Lifetime -import com.jetbrains.rider.aspire.sessionHost.projectLaunchers.ProjectSessionRunProfile +import com.jetbrains.rider.aspire.sessionHost.projectLaunchers.ProjectSessionProfile import com.jetbrains.rider.aspire.sessionHost.projectLaunchers.ProjectSessionRunProfileState import com.jetbrains.rider.runtime.DotNetExecutable import com.jetbrains.rider.runtime.dotNetCore.DotNetCoreRuntime +import java.nio.file.Path class WasmHostSessionRunProfile( private val sessionId: String, projectName: String, - private val dotnetExecutable: DotNetExecutable, + dotnetExecutable: DotNetExecutable, private val dotnetRuntime: DotNetCoreRuntime, private val sessionProcessEventListener: ProcessListener, private val sessionProcessTerminatedListener: ProcessListener, - private val sessionProcessLifetime: Lifetime -) : ProjectSessionRunProfile(projectName) { + private val sessionProcessLifetime: Lifetime, + aspireHostProjectPath: Path? +) : ProjectSessionProfile(projectName, dotnetExecutable, aspireHostProjectPath) { override fun getState( executor: Executor, environment: ExecutionEnvironment diff --git a/src/main/kotlin/com/jetbrains/rider/aspire/unitTests/AspireUnitTestService.kt b/src/main/kotlin/com/jetbrains/rider/aspire/unitTests/AspireUnitTestService.kt index b6305482..dbacab61 100644 --- a/src/main/kotlin/com/jetbrains/rider/aspire/unitTests/AspireUnitTestService.kt +++ b/src/main/kotlin/com/jetbrains/rider/aspire/unitTests/AspireUnitTestService.kt @@ -62,12 +62,12 @@ class AspireUnitTestService(private val project: Project, private val scope: Cor aspireHostProjectPath.nameWithoutExtension, debugSessionToken, debugSessionPort, - aspireHostProjectPath, - null, request.underDebugger, null, null, aspireHostLifetimeDefinition.lifetime, + aspireHostProjectPath, + null, null ) diff --git a/src/main/kotlin/com/jetbrains/rider/aspire/util/DataKeys.kt b/src/main/kotlin/com/jetbrains/rider/aspire/util/DataKeys.kt index afd69c8f..064ca295 100644 --- a/src/main/kotlin/com/jetbrains/rider/aspire/util/DataKeys.kt +++ b/src/main/kotlin/com/jetbrains/rider/aspire/util/DataKeys.kt @@ -3,8 +3,9 @@ package com.jetbrains.rider.aspire.util import com.intellij.openapi.actionSystem.DataKey import com.jetbrains.rider.aspire.generated.ResourceState import com.jetbrains.rider.aspire.generated.ResourceType +import java.nio.file.Path -val ASPIRE_HOST_PATH: DataKey = DataKey.create("Aspire.Host.Path") +val ASPIRE_HOST_PATH: DataKey = DataKey.create("Aspire.Host.Path") val ASPIRE_RESOURCE_UID: DataKey = DataKey.create("Aspire.Resource.Uid") val ASPIRE_RESOURCE_SERVICE_INSTANCE_ID: DataKey = DataKey.create("Aspire.Resource.Service.Instance.Id") val ASPIRE_RESOURCE_TYPE: DataKey = DataKey.create("Aspire.Resource.Type") diff --git a/src/main/kotlin/com/jetbrains/rider/aspire/util/OTelUtils.kt b/src/main/kotlin/com/jetbrains/rider/aspire/util/OTelUtils.kt index 79291daa..3cad42a6 100644 --- a/src/main/kotlin/com/jetbrains/rider/aspire/util/OTelUtils.kt +++ b/src/main/kotlin/com/jetbrains/rider/aspire/util/OTelUtils.kt @@ -2,6 +2,7 @@ package com.jetbrains.rider.aspire.util import com.jetbrains.rider.aspire.generated.ResourceModel import com.jetbrains.rider.aspire.generated.SessionModel +import com.jetbrains.rider.runtime.DotNetExecutable private const val OTEL_RESOURCE_ATTRIBUTES = "OTEL_RESOURCE_ATTRIBUTES" private const val SERVICE_INSTANCE_ID = "service.instance.id" @@ -21,5 +22,17 @@ fun ResourceModel.getServiceInstanceId(): String? { val serviceInstanceId = resourceAttributes.split(",").firstOrNull { it.startsWith(SERVICE_INSTANCE_ID) } ?: return null + return serviceInstanceId.removePrefix("service.instance.id=") +} + +fun DotNetExecutable.getServiceInstanceId(): String? { + val resourceAttributes = environmentVariables + .entries + .firstOrNull { it.key.equals(OTEL_RESOURCE_ATTRIBUTES, true) } + ?.value + ?: return null + val serviceInstanceId = resourceAttributes.split(",").firstOrNull { it.startsWith(SERVICE_INSTANCE_ID) } + ?: return null + return serviceInstanceId.removePrefix("service.instance.id=") } \ No newline at end of file diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index efa1ca73..2b2568d8 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -20,13 +20,17 @@ + + - - + + - + -