Skip to content

support cwd, env and envFile parameters using sun jdi #90

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ class KotlinDebugAdapter(
override fun launch(args: Map<String, Any>) = launcherAsync.execute {
performInitialization()

LOG.debug("launch args: $args")

val projectRoot = (args["projectRoot"] as? String)?.let { Paths.get(it) }
?: throw missingRequestArgument("launch", "projectRoot")

Expand All @@ -98,13 +100,36 @@ class KotlinDebugAdapter(

val vmArguments = (args["vmArguments"] as? String) ?: ""

var cwd = (args["cwd"] as? String).let { if(it.isNullOrBlank()) projectRoot else Paths.get(it) }

val env = mutableMapOf<String, String>().apply {
(args["envFile"] as? String)?.let { Paths.get(it) }?.let { envFile ->
envFile.toFile().readLines()
.map { it.trim() }
.filter { it.isNotBlank() && !it.startsWith("#") }
.forEach {
val (key, value) = it.split("=", limit = 2)
set(key, value)
}
}

// apply 'env' from launch request overriding contents of 'envFile'
args.get("env")?.let { env ->
// Cast from com.google.gson.internal.LinkedTreeMap
@Suppress("UNCHECKED_CAST")
putAll(env as Map<String, String>)
}
}.toMap()

setupCommonInitializationParams(args)

val config = LaunchConfiguration(
debugClassPathResolver(listOf(projectRoot)).classpathOrEmpty.map { it.compiledJar }.toSet(),
mainClass,
projectRoot,
vmArguments
vmArguments,
cwd,
env
)
debuggee = launcher.launch(
config,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,7 @@ class LaunchConfiguration(
val classpath: Set<Path>,
val mainClass: String,
val projectRoot: Path,
val vmArguments: String = ""
val vmArguments: String = "",
val cwd: Path = projectRoot,
val env: Map<String, String> = mapOf()
)
35 changes: 13 additions & 22 deletions adapter/src/main/kotlin/org/javacs/ktda/jdi/launch/JDILauncher.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import org.javacs.kt.LOG
import org.javacs.ktda.core.launch.DebugLauncher
import org.javacs.ktda.core.launch.LaunchConfiguration
import org.javacs.ktda.core.launch.AttachConfiguration
import org.javacs.ktda.core.Debuggee
import org.javacs.ktda.core.DebugContext
import org.javacs.ktda.util.KotlinDAException
import org.javacs.ktda.jdi.JDIDebuggee
Expand All @@ -16,22 +15,19 @@ import com.sun.jdi.connect.AttachingConnector
import java.io.File
import java.nio.file.Path
import java.nio.file.Files
import java.net.URLEncoder
import java.net.URLDecoder
import java.nio.charset.StandardCharsets
import java.util.stream.Collectors
import org.javacs.kt.LOG

class JDILauncher(
private val attachTimeout: Int = 50,
private val vmArguments: String? = null,
private val modulePaths: String? = null
) : DebugLauncher {
private val vmManager: VirtualMachineManager
get() = Bootstrap.virtualMachineManager()

override fun launch(config: LaunchConfiguration, context: DebugContext): JDIDebuggee {
val connector = createLaunchConnector()
LOG.info("Starting JVM debug session with main class {}", config.mainClass)
LOG.info("Starting JVM debug session with main class {} and connector {}", config.mainClass, connector)

LOG.debug("Launching VM")
val vm = connector.launch(createLaunchArgs(config, connector)) ?: throw KotlinDAException("Could not launch a new VM")
Expand All @@ -54,9 +50,11 @@ class JDILauncher(

private fun createLaunchArgs(config: LaunchConfiguration, connector: Connector): Map<String, Connector.Argument> = connector.defaultArguments()
.also { args ->
args["suspend"]!!.setValue("true")
args["options"]!!.setValue(formatOptions(config))
args["main"]!!.setValue(formatMainClass(config))
args.get("suspend")?.setValue("true")
args.get("options")?.setValue(formatOptions(config))
args.get("main")?.setValue(formatMainClass(config))
args.get("cwd")?.setValue(config.cwd.toAbsolutePath().toString())
args.get("env")?.setValue(KDACommandLineLauncher.urlEncode(config.env.map { "${it.key}=${it.value}" }) ?: "")
}

private fun createAttachArgs(config: AttachConfiguration, connector: Connector): Map<String, Connector.Argument> = connector.defaultArguments()
Expand All @@ -67,12 +65,13 @@ class JDILauncher(
}

private fun createAttachConnector(): AttachingConnector = vmManager.attachingConnectors()
.let { it.find { it.name() == "com.sun.jdi.SocketAttach" } ?: it.firstOrNull() }
.let { it.find { it.name() == "com.sun.jdi.SocketAttach" } }
?: throw KotlinDAException("Could not find an attaching connector (for a new debuggee VM)")

private fun createLaunchConnector(): LaunchingConnector = vmManager.launchingConnectors()
// Workaround for JDK 11+ where the first launcher (RawCommandLineLauncher) does not properly support args
.let { it.find { it.javaClass.name == "com.sun.tools.jdi.SunCommandLineLauncher" } ?: it.firstOrNull() }
.also { LOG.debug("connectors: $it") }
// Using our own connector to support cwd and envs
.let { it.find { it.name() == KDACommandLineLauncher::class.java.name } }
?: throw KotlinDAException("Could not find a launching connector (for a new debuggee VM)")

private fun sourcesRootsOf(projectRoot: Path): Set<Path> =
Expand Down Expand Up @@ -104,13 +103,5 @@ class JDILauncher(
private fun formatClasspath(config: LaunchConfiguration): String = config.classpath
.map { it.toAbsolutePath().toString() }
.reduce { prev, next -> "$prev${File.pathSeparatorChar}$next" }

private fun urlEncode(arg: Collection<String>?) = arg
?.map { URLEncoder.encode(it, StandardCharsets.UTF_8.name()) }
?.reduce { a, b -> "$a\n$b" }

private fun urlDecode(arg: String?) = arg
?.split("\n")
?.map { URLDecoder.decode(it, StandardCharsets.UTF_8.name()) }
?.toList()

}
Loading