Skip to content

Commit

Permalink
Fix running session configuration
Browse files Browse the repository at this point in the history
  • Loading branch information
rafaelldi committed Dec 6, 2023
1 parent 9714c1e commit 28072cd
Show file tree
Hide file tree
Showing 2 changed files with 108 additions and 40 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ class AspireSessionHostService {
model.sessions.view(processLifetime) { lt, id, session ->
LOG.info("New session added $id, $session")
val runner = AspireSessionRunner.getInstance(project)
runner.runSession(id, session, lt, hostId, model, processLifetime)
runner.runSession(AspireSessionRunner.RunSessionCommand(id, session, lt, hostId, model, processLifetime))
}
}
}
146 changes: 107 additions & 39 deletions src/main/kotlin/com/intellij/aspire/sessionHost/AspireSessionRunner.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,30 +7,39 @@ import com.intellij.execution.RunnerAndConfigurationSettings
import com.intellij.execution.configurations.ConfigurationTypeUtil
import com.intellij.execution.executors.DefaultDebugExecutor
import com.intellij.execution.executors.DefaultRunExecutor
import com.intellij.execution.process.AnsiEscapeDecoder
import com.intellij.execution.process.ProcessAdapter
import com.intellij.execution.process.ProcessEvent
import com.intellij.execution.process.ProcessOutputType
import com.intellij.execution.runners.ExecutionEnvironmentBuilder
import com.intellij.execution.runners.ProgramRunner
import com.intellij.execution.ui.RunContentDescriptor
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.openapi.rd.util.launchOnUi
import com.intellij.openapi.rd.util.withUiContext
import com.intellij.openapi.util.Key
import com.intellij.util.execution.ParametersListUtil
import com.intellij.util.io.systemIndependentPath
import com.jetbrains.rd.util.lifetime.Lifetime
import com.jetbrains.rd.util.lifetime.isNotAlive
import com.jetbrains.rider.model.RunnableProject
import com.jetbrains.rider.model.runnableProjectsModel
import com.jetbrains.rider.projectView.solution
import com.jetbrains.rider.run.configurations.project.DotNetProjectConfiguration
import com.jetbrains.rider.run.configurations.project.DotNetProjectConfigurationType
import com.jetbrains.rider.run.pid
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.consumeAsFlow
import kotlinx.coroutines.launch
import kotlin.io.path.Path

@Service(Service.Level.PROJECT)
class AspireSessionRunner(private val project: Project) {
class AspireSessionRunner(private val project: Project, scope: CoroutineScope) {
companion object {
fun getInstance(project: Project): AspireSessionRunner = project.service()

Expand All @@ -39,29 +48,65 @@ class AspireSessionRunner(private val project: Project) {
private const val ASPIRE_SUFFIX = "Aspire"
}

fun runSession(
id: String,
session: SessionModel,
private val commandChannel = Channel<RunSessionCommand>(Channel.UNLIMITED)
private val ansiEscapeDecoder = AnsiEscapeDecoder()

data class RunSessionCommand(
val sessionId: String,
val sessionModel: SessionModel,
val sessionLifetime: Lifetime,
val hostId: String,
val hostModel: AspireSessionHostModel,
val hostLifetime: Lifetime
)

init {
scope.launch {
commandChannel.consumeAsFlow().collect {
runSession(
it.sessionId,
it.sessionModel,
it.sessionLifetime,
it.hostId,
it.hostModel,
it.hostLifetime
)
}
}
}

fun runSession(command: RunSessionCommand) {
commandChannel.trySend(command)
}

private suspend fun runSession(
sessionId: String,
sessionModel: SessionModel,
sessionLifetime: Lifetime,
hostId: String,
model: AspireSessionHostModel,
hostModel: AspireSessionHostModel,
hostLifetime: Lifetime
) {
LOG.info("Starting a session for the project ${session.projectPath}")
LOG.info("Starting a session for the project ${sessionModel.projectPath}")

if (hostLifetime.isNotAlive || sessionLifetime.isNotAlive) {
LOG.info("Unable to run project ${sessionModel.projectPath} because lifetimes are not alive")
return
}

val host = AspireSessionHostService.getInstance().getHost(hostId)
if (host == null) {
LOG.warn("Unable to find Aspire host with id=$hostId")
return
}

val configuration = getOrCreateConfiguration(session, host)
val configuration = getOrCreateConfiguration(sessionModel, host)
if (configuration == null) {
LOG.warn("Unable to find or create run configuration for the project ${session.projectPath}")
LOG.warn("Unable to find or create run configuration for the project ${sessionModel.projectPath}")
return
}

val isDebug = host.isDebug || session.debug
val isDebug = host.isDebug || sessionModel.debug
val executor =
if (!isDebug) DefaultRunExecutor.getRunExecutorInstance()
else DefaultDebugExecutor.getDebugExecutorInstance()
Expand All @@ -70,43 +115,64 @@ class AspireSessionRunner(private val project: Project) {
.create(project, executor, configuration.configuration)
.build()

environment.callback = ProgramRunner.Callback {
val processHandler = it?.processHandler
if (processHandler != null) {
val pid = processHandler.pid()
if (pid != null) {
hostLifetime.launchOnUi {
model.processStarted.fire(ProcessStarted(id, pid))
}
}
val started = CompletableDeferred<Boolean>()
withUiContext {
ProgramRunnerUtil.executeConfigurationAsync(environment, false, true, object : ProgramRunner.Callback {
override fun processStarted(descriptor: RunContentDescriptor?) {
val processHandler = descriptor?.processHandler
if (processHandler != null) {
val pid = processHandler.pid()
if (pid != null) {
hostLifetime.launchOnUi {
hostModel.processStarted.fire(ProcessStarted(sessionId, pid))
}
}

val processAdapter = object : ProcessAdapter() {
override fun onTextAvailable(event: ProcessEvent, outputType: Key<*>) {
hostLifetime.launchOnUi {
model.logReceived.fire(
LogReceived(id, outputType == ProcessOutputType.STDERR, event.text)
)
val processAdapter = object : ProcessAdapter() {
override fun onTextAvailable(event: ProcessEvent, outputType: Key<*>) {
ansiEscapeDecoder.escapeText(event.text, outputType) { textChunk, _ ->
hostLifetime.launchOnUi {
hostModel.logReceived.fire(
LogReceived(sessionId, outputType == ProcessOutputType.STDERR, textChunk)
)
}
}
}

override fun processTerminated(event: ProcessEvent) {
hostLifetime.launchOnUi {
hostModel.processTerminated.fire(ProcessTerminated(sessionId, event.exitCode))
}
}

override fun processNotStarted() {
hostLifetime.launchOnUi {
hostModel.processTerminated.fire(ProcessTerminated(sessionId, -1))
}
}
}
}

override fun processTerminated(event: ProcessEvent) {
hostLifetime.launchOnUi {
model.processTerminated.fire(ProcessTerminated(id, event.exitCode))
sessionLifetime.onTermination {
if (!processHandler.isProcessTerminating && !processHandler.isProcessTerminated) {
processHandler.destroyProcess()
}
}
}
}

sessionLifetime.onTermination {
if (!processHandler.isProcessTerminating && !processHandler.isProcessTerminated) {
processHandler.destroyProcess()
processHandler.addProcessListener(processAdapter)
}

started.complete(true)
}

processHandler.addProcessListener(processAdapter)
}
override fun processNotStarted() {
started.complete(false)
}
})
}

ProgramRunnerUtil.executeConfiguration(environment, false, true)
if (!started.await()) {
LOG.warn("Unable to start run configuration for the project ${sessionModel.projectPath}")
}
}

private fun getOrCreateConfiguration(
Expand All @@ -130,8 +196,9 @@ class AspireSessionRunner(private val project: Project) {

val runManager = RunManager.getInstance(project)
val configurationType = ConfigurationTypeUtil.findConfigurationType(DotNetProjectConfigurationType::class.java)
val configurationName = "${runnableProject.name}-$ASPIRE_SUFFIX"
val existingConfiguration = runManager.allSettings.firstOrNull {
it.type.id == configurationType.id && it.name.endsWith(ASPIRE_SUFFIX) && it.folderName == host.projectName
it.type.id == configurationType.id && it.name == configurationName && it.folderName == host.projectName
}

if (existingConfiguration != null) {
Expand All @@ -140,7 +207,7 @@ class AspireSessionRunner(private val project: Project) {
}

LOG.trace("Creating a new configuration")
return createConfiguration(configurationType, runManager, runnableProject, session, host)
return createConfiguration(configurationType, configurationName, runManager, runnableProject, session, host)
}

private fun updateConfiguration(
Expand All @@ -162,14 +229,15 @@ class AspireSessionRunner(private val project: Project) {

private fun createConfiguration(
configurationType: DotNetProjectConfigurationType,
configurationName: String,
runManager: RunManager,
runnableProject: RunnableProject,
session: SessionModel,
host: AspireSessionHostService.HostConfiguration
): RunnerAndConfigurationSettings {
val factory = configurationType.factory
val defaultConfiguration =
runManager.createConfiguration("${runnableProject.name}-$ASPIRE_SUFFIX", factory).apply {
runManager.createConfiguration(configurationName, factory).apply {
(configuration as DotNetProjectConfiguration).apply {
parameters.projectFilePath = runnableProject.projectFilePath
parameters.projectKind = runnableProject.kind
Expand Down

0 comments on commit 28072cd

Please sign in to comment.