jetbrains-rider/src-203-212/software/aws/toolkits/jetbrains/services/clouddebug/DotNetDebuggerSupport.kt [4:257]: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - package software.aws.toolkits.jetbrains.services.clouddebug import com.intellij.execution.configurations.RuntimeConfigurationError import com.intellij.execution.filters.TextConsoleBuilderFactory import com.intellij.execution.process.ProcessAdapter import com.intellij.execution.process.ProcessEvent import com.intellij.execution.process.ProcessHandler import com.intellij.execution.runners.ExecutionEnvironment import com.intellij.execution.ui.RunContentDescriptor import com.intellij.openapi.application.runInEdt import com.intellij.openapi.rd.defineNestedLifetime import com.intellij.openapi.util.io.FileUtil import com.intellij.xdebugger.XDebugProcessStarter import com.intellij.xdebugger.XDebuggerManager import com.jetbrains.rd.framework.IdKind import com.jetbrains.rd.framework.Identities import com.jetbrains.rd.framework.Protocol import com.jetbrains.rd.framework.Serializers import com.jetbrains.rd.framework.SocketWire import com.jetbrains.rd.framework.impl.RpcTimeouts import com.jetbrains.rd.util.lifetime.isAlive import com.jetbrains.rd.util.lifetime.onTermination import com.jetbrains.rd.util.put import com.jetbrains.rd.util.reactive.adviseUntil import com.jetbrains.rdclient.protocol.RdDispatcher import com.jetbrains.rider.RiderEnvironment import com.jetbrains.rider.debugger.DebuggerHelperHost import com.jetbrains.rider.debugger.RiderDebuggerWorkerModelManager import com.jetbrains.rider.model.debuggerWorker.DotNetDebuggerSessionModel import com.jetbrains.rider.model.debuggerWorkerConnectionHelperModel import com.jetbrains.rider.projectView.solution import com.jetbrains.rider.run.IDebuggerOutputListener import com.jetbrains.rider.run.bindToSettings import org.jetbrains.concurrency.AsyncPromise import org.jetbrains.concurrency.Promise import software.aws.toolkits.core.utils.debug import software.aws.toolkits.core.utils.getLogger import software.aws.toolkits.core.utils.info import software.aws.toolkits.core.utils.trace import software.aws.toolkits.jetbrains.services.clouddebug.execution.steps.ResourceTransferStep import software.aws.toolkits.jetbrains.services.ecs.execution.ImmutableContainerOptions import software.aws.toolkits.jetbrains.utils.DotNetDebuggerUtils import software.aws.toolkits.jetbrains.utils.DotNetRuntimeUtils import software.aws.toolkits.jetbrains.utils.compatability.createNetCoreStartInfo import software.aws.toolkits.jetbrains.utils.execution.steps.Context import software.aws.toolkits.resources.message import java.io.File import java.io.FileNotFoundException import java.io.OutputStream import java.util.Timer import java.util.concurrent.CompletableFuture import kotlin.concurrent.schedule class DotNetDebuggerSupport : DebuggerSupport() { companion object { private val logger = getLogger() private const val DOTNET_EXECUTABLE = "dotnet" private const val START_COMMAND_ASSEMBLY_PLACEHOLDER = "$DOTNET_EXECUTABLE " private const val DEBUGGER_MODE = "server" } private var exeRemotePath: String = "" override val numberOfDebugPorts: Int get() = 2 override val platform: CloudDebuggingPlatform get() = CloudDebuggingPlatform.DOTNET private var localDebuggerPath: String = "" override fun startupCommand(): CloudDebugStartupCommand = DotNetStartupCommand() override val debuggerPath = object : DebuggerPath { override fun getDebuggerPath(): String = localDebuggerPath override fun getDebuggerEntryPoint(): String = "${getRemoteDebuggerPath()}/${DotNetDebuggerUtils.cloudDebuggerTempDirName}/${DotNetDebuggerUtils.debuggerAssemblyFile.name}" // TODO fix when cloud-debug fixes rsync to run mkdir -p override fun getRemoteDebuggerPath(): String = // "/aws/cloud-debug/debugger/$platform" "/aws/$platform" } override fun attachDebugger( context: Context, containerName: String, containerOptions: ImmutableContainerOptions, environment: ExecutionEnvironment, ports: List, displayName: String ): CompletableFuture { val manager = XDebuggerManager.getInstance(environment.project) val future = CompletableFuture() if (exeRemotePath.isEmpty()) { future.completeExceptionally(RuntimeException("DotNet executable to debug is not specified")) return future } if (ports.size < 2) { future.completeExceptionally(RuntimeException("DotNet requires two ports to be specified")) return future } val frontendPort = ports[0] val backendPort = ports[1] runInEdt { try { createDebugProcessAsync(environment, frontendPort, backendPort, exeRemotePath).then { it?.let { future.complete( manager.startSessionAndShowTab( displayName, null, it ).runContentDescriptor ) } ?: run { future.complete(null) } } } catch (e: Exception) { future.completeExceptionally(e) } } return future } override fun createDebuggerUploadStep(context: Context, containerName: String): ResourceTransferStep { val debuggerAssemblyNames = DebuggerHelperHost.getInstance(context.getRequiredAttribute(Context.PROJECT_ATTRIBUTE)) .model.getDebuggerAssemblies.sync(Unit, RpcTimeouts.longRunning) // Helper assembly to detect dbgshim on 192 Rider val tempDirectory = FileUtil.getTempDirectory() val debuggerLocalTemp = File(tempDirectory, DotNetDebuggerUtils.cloudDebuggerTempDirName) if (debuggerLocalTemp.exists()) { FileUtil.delete(debuggerLocalTemp) } debuggerLocalTemp.mkdirs() localDebuggerPath = debuggerLocalTemp.canonicalPath prepareDebuggerArtifacts(debuggerLocalTemp, debuggerAssemblyNames.toTypedArray()) val remoteDebuggerPath = debuggerPath.getRemoteDebuggerPath() return ResourceTransferStep( localPath = localDebuggerPath, remotePath = remoteDebuggerPath, containerName = containerName ) } override fun automaticallyAugmentable(input: List): Boolean { if (input.first().trim() != DOTNET_EXECUTABLE) { throw RuntimeConfigurationError(message("cloud_debug.run_configuration.dotnet.start_command.miss_runtime", DOTNET_EXECUTABLE)) } val restCommands = input.drop(1) val path = restCommands.firstOrNull()?.trim() ?: throw RuntimeConfigurationError( message("cloud_debug.run_configuration.dotnet.start_command.miss_assembly_path", START_COMMAND_ASSEMBLY_PLACEHOLDER) ) if (!path.contains('/')) { throw RuntimeConfigurationError(message("cloud_debug.run_configuration.dotnet.start_command.assembly_path_not_valid", path)) } // Start command should follow the pattern 'dotnet '. Take a remote assembly path. exeRemotePath = path return true } override fun attachDebuggingArguments(input: List, ports: List, debuggerPath: String): String { val debuggerRemoteDirPath = "${this.debuggerPath.getRemoteDebuggerPath()}/${DotNetDebuggerUtils.cloudDebuggerTempDirName}" val remoteDebuggerLogPath = "$debuggerRemoteDirPath/Logs" if (ports.size < 2) { val message = message("cloud_debug.step.dotnet.two_ports_required") logger.debug { message } throw IllegalStateException(message) } val frontendPort = ports[0] val backendPort = ports[1] val debugArgs = StringBuilder() .append("RESHARPER_HOST_LOG_DIR=$remoteDebuggerLogPath ") .append("dotnet ") .append("$debuggerPath ") .append("--mode=$DEBUGGER_MODE ") .append("--frontend-port=$frontendPort ") .append("--backend-port=$backendPort") logger.info { "Attaching default Rider Debugger arguments" } return debugArgs.toString() } private fun createDebugProcessAsync( environment: ExecutionEnvironment, frontendPort: Int, backendPort: Int, exeRemotePath: String ): Promise { val promise = AsyncPromise() // Define a debugger lifetime to be able to dispose the debugger process and all nested component on termination val debuggerLifetimeDefinition = environment.defineNestedLifetime() val debuggerLifetime = debuggerLifetimeDefinition.lifetime val scheduler = RdDispatcher(debuggerLifetime) val startInfo = createNetCoreStartInfo( exePath = exeRemotePath ) val protocol = Protocol( name = "", serializers = Serializers(), identity = Identities(IdKind.Client), scheduler = scheduler, wire = SocketWire.Client( lifetime = debuggerLifetime, scheduler = scheduler, port = frontendPort, optId = "FrontendToDebugWorker" ), lifetime = debuggerLifetime ) protocol.wire.connected.adviseUntil(debuggerLifetime) connected@{ isConnected -> if (!isConnected) { return@connected false } try { val workerModel = RiderDebuggerWorkerModelManager.createDebuggerModel(debuggerLifetime, protocol) workerModel.initialized.adviseUntil(debuggerLifetime) initialized@{ isInitialized -> if (!isInitialized) { return@initialized false } // Fire backend to connect to debugger. environment.project.solution.debuggerWorkerConnectionHelperModel.ports.put( debuggerLifetime, environment.executionId, backendPort ) val sessionModel = DotNetDebuggerSessionModel(startInfo) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - jetbrains-rider/src-213+/software/aws/toolkits/jetbrains/services/clouddebug/DotNetDebuggerSupport.kt [4:257]: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - package software.aws.toolkits.jetbrains.services.clouddebug import com.intellij.execution.configurations.RuntimeConfigurationError import com.intellij.execution.filters.TextConsoleBuilderFactory import com.intellij.execution.process.ProcessAdapter import com.intellij.execution.process.ProcessEvent import com.intellij.execution.process.ProcessHandler import com.intellij.execution.runners.ExecutionEnvironment import com.intellij.execution.ui.RunContentDescriptor import com.intellij.openapi.application.runInEdt import com.intellij.openapi.rd.defineNestedLifetime import com.intellij.openapi.util.io.FileUtil import com.intellij.xdebugger.XDebugProcessStarter import com.intellij.xdebugger.XDebuggerManager import com.jetbrains.rd.framework.IdKind import com.jetbrains.rd.framework.Identities import com.jetbrains.rd.framework.Protocol import com.jetbrains.rd.framework.Serializers import com.jetbrains.rd.framework.SocketWire import com.jetbrains.rd.framework.impl.RpcTimeouts import com.jetbrains.rd.util.lifetime.isAlive import com.jetbrains.rd.util.lifetime.onTermination import com.jetbrains.rd.util.put import com.jetbrains.rd.util.reactive.adviseUntil import com.jetbrains.rdclient.protocol.RdDispatcher import com.jetbrains.rider.RiderEnvironment import com.jetbrains.rider.debugger.DebuggerHelperHost import com.jetbrains.rider.debugger.RiderDebuggerWorkerModelManager import com.jetbrains.rider.model.debuggerWorker.DotNetDebuggerSessionModel import com.jetbrains.rider.model.debuggerWorkerConnectionHelperModel import com.jetbrains.rider.projectView.solution import com.jetbrains.rider.run.IDebuggerOutputListener import com.jetbrains.rider.run.bindToSettings import org.jetbrains.concurrency.AsyncPromise import org.jetbrains.concurrency.Promise import software.aws.toolkits.core.utils.debug import software.aws.toolkits.core.utils.getLogger import software.aws.toolkits.core.utils.info import software.aws.toolkits.core.utils.trace import software.aws.toolkits.jetbrains.services.clouddebug.execution.steps.ResourceTransferStep import software.aws.toolkits.jetbrains.services.ecs.execution.ImmutableContainerOptions import software.aws.toolkits.jetbrains.utils.DotNetDebuggerUtils import software.aws.toolkits.jetbrains.utils.DotNetRuntimeUtils import software.aws.toolkits.jetbrains.utils.compatability.createNetCoreStartInfo import software.aws.toolkits.jetbrains.utils.execution.steps.Context import software.aws.toolkits.resources.message import java.io.File import java.io.FileNotFoundException import java.io.OutputStream import java.util.Timer import java.util.concurrent.CompletableFuture import kotlin.concurrent.schedule class DotNetDebuggerSupport : DebuggerSupport() { companion object { private val logger = getLogger() private const val DOTNET_EXECUTABLE = "dotnet" private const val START_COMMAND_ASSEMBLY_PLACEHOLDER = "$DOTNET_EXECUTABLE " private const val DEBUGGER_MODE = "server" } private var exeRemotePath: String = "" override val numberOfDebugPorts: Int get() = 2 override val platform: CloudDebuggingPlatform get() = CloudDebuggingPlatform.DOTNET private var localDebuggerPath: String = "" override fun startupCommand(): CloudDebugStartupCommand = DotNetStartupCommand() override val debuggerPath = object : DebuggerPath { override fun getDebuggerPath(): String = localDebuggerPath override fun getDebuggerEntryPoint(): String = "${getRemoteDebuggerPath()}/${DotNetDebuggerUtils.cloudDebuggerTempDirName}/${DotNetDebuggerUtils.debuggerAssemblyFile.name}" // TODO fix when cloud-debug fixes rsync to run mkdir -p override fun getRemoteDebuggerPath(): String = // "/aws/cloud-debug/debugger/$platform" "/aws/$platform" } override fun attachDebugger( context: Context, containerName: String, containerOptions: ImmutableContainerOptions, environment: ExecutionEnvironment, ports: List, displayName: String ): CompletableFuture { val manager = XDebuggerManager.getInstance(environment.project) val future = CompletableFuture() if (exeRemotePath.isEmpty()) { future.completeExceptionally(RuntimeException("DotNet executable to debug is not specified")) return future } if (ports.size < 2) { future.completeExceptionally(RuntimeException("DotNet requires two ports to be specified")) return future } val frontendPort = ports[0] val backendPort = ports[1] runInEdt { try { createDebugProcessAsync(environment, frontendPort, backendPort, exeRemotePath).then { it?.let { future.complete( manager.startSessionAndShowTab( displayName, null, it ).runContentDescriptor ) } ?: run { future.complete(null) } } } catch (e: Exception) { future.completeExceptionally(e) } } return future } override fun createDebuggerUploadStep(context: Context, containerName: String): ResourceTransferStep { val debuggerAssemblyNames = DebuggerHelperHost.getInstance(context.getRequiredAttribute(Context.PROJECT_ATTRIBUTE)) .model.getDebuggerAssemblies.sync(Unit, RpcTimeouts.longRunning) // Helper assembly to detect dbgshim on 192 Rider val tempDirectory = FileUtil.getTempDirectory() val debuggerLocalTemp = File(tempDirectory, DotNetDebuggerUtils.cloudDebuggerTempDirName) if (debuggerLocalTemp.exists()) { FileUtil.delete(debuggerLocalTemp) } debuggerLocalTemp.mkdirs() localDebuggerPath = debuggerLocalTemp.canonicalPath prepareDebuggerArtifacts(debuggerLocalTemp, debuggerAssemblyNames.toTypedArray()) val remoteDebuggerPath = debuggerPath.getRemoteDebuggerPath() return ResourceTransferStep( localPath = localDebuggerPath, remotePath = remoteDebuggerPath, containerName = containerName ) } override fun automaticallyAugmentable(input: List): Boolean { if (input.first().trim() != DOTNET_EXECUTABLE) { throw RuntimeConfigurationError(message("cloud_debug.run_configuration.dotnet.start_command.miss_runtime", DOTNET_EXECUTABLE)) } val restCommands = input.drop(1) val path = restCommands.firstOrNull()?.trim() ?: throw RuntimeConfigurationError( message("cloud_debug.run_configuration.dotnet.start_command.miss_assembly_path", START_COMMAND_ASSEMBLY_PLACEHOLDER) ) if (!path.contains('/')) { throw RuntimeConfigurationError(message("cloud_debug.run_configuration.dotnet.start_command.assembly_path_not_valid", path)) } // Start command should follow the pattern 'dotnet '. Take a remote assembly path. exeRemotePath = path return true } override fun attachDebuggingArguments(input: List, ports: List, debuggerPath: String): String { val debuggerRemoteDirPath = "${this.debuggerPath.getRemoteDebuggerPath()}/${DotNetDebuggerUtils.cloudDebuggerTempDirName}" val remoteDebuggerLogPath = "$debuggerRemoteDirPath/Logs" if (ports.size < 2) { val message = message("cloud_debug.step.dotnet.two_ports_required") logger.debug { message } throw IllegalStateException(message) } val frontendPort = ports[0] val backendPort = ports[1] val debugArgs = StringBuilder() .append("RESHARPER_HOST_LOG_DIR=$remoteDebuggerLogPath ") .append("dotnet ") .append("$debuggerPath ") .append("--mode=$DEBUGGER_MODE ") .append("--frontend-port=$frontendPort ") .append("--backend-port=$backendPort") logger.info { "Attaching default Rider Debugger arguments" } return debugArgs.toString() } private fun createDebugProcessAsync( environment: ExecutionEnvironment, frontendPort: Int, backendPort: Int, exeRemotePath: String ): Promise { val promise = AsyncPromise() // Define a debugger lifetime to be able to dispose the debugger process and all nested component on termination val debuggerLifetimeDefinition = environment.defineNestedLifetime() val debuggerLifetime = debuggerLifetimeDefinition.lifetime val scheduler = RdDispatcher(debuggerLifetime) val startInfo = createNetCoreStartInfo( exePath = exeRemotePath ) val protocol = Protocol( name = "", serializers = Serializers(), identity = Identities(IdKind.Client), scheduler = scheduler, wire = SocketWire.Client( lifetime = debuggerLifetime, scheduler = scheduler, port = frontendPort, optId = "FrontendToDebugWorker" ), lifetime = debuggerLifetime ) protocol.wire.connected.adviseUntil(debuggerLifetime) connected@{ isConnected -> if (!isConnected) { return@connected false } try { val workerModel = RiderDebuggerWorkerModelManager.createDebuggerModel(debuggerLifetime, protocol) workerModel.initialized.adviseUntil(debuggerLifetime) initialized@{ isInitialized -> if (!isInitialized) { return@initialized false } // Fire backend to connect to debugger. environment.project.solution.debuggerWorkerConnectionHelperModel.ports.put( debuggerLifetime, environment.executionId, backendPort ) val sessionModel = DotNetDebuggerSessionModel(startInfo) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -