[java-debugger][IDEA-375478] use eel api to provide the java-agent path

GitOrigin-RevId: d8d26f7200c617c5d537c7a1439c11d16f12a853
This commit is contained in:
Alexander.Glukhov
2025-07-09 18:19:45 +00:00
committed by intellij-monorepo-bot
parent 3e7f0329bf
commit 1f41a5f75f
3 changed files with 117 additions and 72 deletions
@@ -28,9 +28,12 @@ import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.io.FileUtilRt;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.platform.eel.EelDescriptor;
import com.intellij.platform.eel.annotations.NativePath;
import com.intellij.platform.eel.provider.EelProviderUtil;
import com.intellij.platform.eel.provider.LocalEelDescriptor;
import com.intellij.platform.eel.provider.utils.EelPathUtils;
import com.intellij.util.PathUtil;
import com.intellij.util.SlowOperations;
@@ -49,6 +52,8 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import static com.intellij.platform.eel.provider.EelNioBridgeServiceKt.asEelPath;
public final class AsyncStacksUtils {
private static final Logger LOG = Logger.getInstance(AsyncStacksUtils.class);
// TODO: obtain CaptureStorage fqn from the class somehow
@@ -316,39 +321,35 @@ public final class AsyncStacksUtils {
return;
}
Path agentArtifactPath = getAgentArtifactPath(disposable);
if (Files.exists(agentArtifactPath)) {
String agentPath = JavaExecutionUtil.handleSpacesInAgentPath(agentArtifactPath.toAbsolutePath().toString(),
"captureAgent", null,
f -> f.getName().startsWith("debugger-agent"));
if (agentPath != null) {
try (AccessToken ignore = SlowOperations.knownIssue("IDEA-307303, EA-835503")) {
parametersList.prepend(prefix + agentPath + generateAgentSettings(project));
String agentPath = getAgentArtifactPath(project, disposable);
if (agentPath != null) {
try (AccessToken ignore = SlowOperations.knownIssue("IDEA-307303, EA-835503")) {
parametersList.prepend(prefix + agentPath + generateAgentSettings(project));
}
if (Registry.is("debugger.async.stacks.coroutines", false)) {
parametersList.addProperty("kotlinx.coroutines.debug.enable.creation.stack.trace", "false");
parametersList.addProperty("debugger.agent.enable.coroutines", "true");
if (Registry.is("debugger.async.stacks.flows", false)) {
parametersList.addProperty("kotlinx.coroutines.debug.enable.flows.stack.trace", "true");
}
if (Registry.is("debugger.async.stacks.coroutines", false)) {
parametersList.addProperty("kotlinx.coroutines.debug.enable.creation.stack.trace", "false");
parametersList.addProperty("debugger.agent.enable.coroutines", "true");
if (Registry.is("debugger.async.stacks.flows", false)) {
parametersList.addProperty("kotlinx.coroutines.debug.enable.flows.stack.trace", "true");
}
if (Registry.is("debugger.async.stacks.state.flows", false)) {
parametersList.addProperty("kotlinx.coroutines.debug.enable.mutable.state.flows.stack.trace", "true");
}
}
if (!Registry.is("debugger.async.stack.trace.for.exceptions.printing", false)) {
parametersList.addProperty("debugger.agent.support.throwable", "false");
}
if (Registry.is("debugger.async.stack.trace.for.all.threads")) {
parametersList.addProperty("debugger.async.stack.trace.for.all.threads", "true");
if (Registry.is("debugger.async.stacks.state.flows", false)) {
parametersList.addProperty("kotlinx.coroutines.debug.enable.mutable.state.flows.stack.trace", "true");
}
}
if (!Registry.is("debugger.async.stack.trace.for.exceptions.printing", false)) {
parametersList.addProperty("debugger.agent.support.throwable", "false");
}
if (Registry.is("debugger.async.stack.trace.for.all.threads")) {
parametersList.addProperty("debugger.async.stack.trace.for.all.threads", "true");
}
}
else {
LOG.error("Capture agent not found: " + agentArtifactPath);
LOG.error("Capture agent not found: " + agentPath);
}
}
private static @NotNull Path getAgentArtifactPath(@Nullable Disposable disposable) {
@NativePath
private static @Nullable String getAgentArtifactPath(@Nullable Project project, @Nullable Disposable disposable) {
String relevantJarsRoot = PathManager.getArchivedCompliedClassesLocation();
Path classesRoot = Path.of(PathUtil.getJarPathForClass(DebuggerManagerImpl.class));
// isDirectory(classesRoot) is used instead of `PluginManagerCore.isRunningFromSources()`
@@ -356,43 +357,79 @@ public final class AsyncStacksUtils {
// where the layout is quite the same as in installers.
// but `PluginManagerCore.isRunningFromSources()` still returns `true` in this case
if (Files.isDirectory(classesRoot) || (relevantJarsRoot != null && classesRoot.startsWith(relevantJarsRoot))) {
// Code runs from IDEA run configuration (code from .class file in out/ directory)
try {
// The agent file must have a fixed name (AGENT_JAR_NAME) which is mentioned in MANIFEST.MF inside
Path debuggerAgentDir =
FileUtil.createTempDirectory(new File(PathManager.getTempPath()), "debugger-agent", "", disposable == null).toPath();
if (disposable != null) {
Disposer.register(disposable, () -> {
try {
FileUtilRt.deleteRecursively(debuggerAgentDir);
}
catch (IOException ignored) {
}
});
}
Path agentArtifactPath = debuggerAgentDir.resolve(AGENT_JAR_NAME);
Path communityRoot = Path.of(PathManager.getCommunityHomePath());
Path iml = BuildDependenciesJps.getProjectModule(communityRoot, "intellij.java.debugger.agent.holder");
Path downloadedAgent = BuildDependenciesJps.INSTANCE.getModuleLibrarySingleRootSync(
iml,
"debugger-agent",
"https://cache-redirector.jetbrains.com/intellij-dependencies",
new BuildDependenciesCommunityRoot(Path.of(PathManager.getCommunityHomePath())));
Files.copy(downloadedAgent, agentArtifactPath);
return agentArtifactPath;
}
catch (IOException e) {
throw new RuntimeException(e);
}
return getArtifactPathForDownloadedAgent(project, disposable);
}
else {
return classesRoot.resolveSibling("rt").resolve(AGENT_JAR_NAME);
return getArtifactPathForBundledAgent(classesRoot, project, disposable);
}
}
// Code runs from IDEA run configuration (code from .class file in out/ directory)
@NativePath
private static @NotNull String getArtifactPathForDownloadedAgent(@Nullable Project project, @Nullable Disposable disposable) {
try {
Path agentArtifactPath = createTemporaryAgentPath(project, disposable);
Path communityRoot = Path.of(PathManager.getCommunityHomePath());
Path iml = BuildDependenciesJps.getProjectModule(communityRoot, "intellij.java.debugger.agent.holder");
Path downloadedAgent = BuildDependenciesJps.INSTANCE.getModuleLibrarySingleRootSync(
iml,
"debugger-agent",
"https://cache-redirector.jetbrains.com/intellij-dependencies",
new BuildDependenciesCommunityRoot(Path.of(PathManager.getCommunityHomePath())));
// The agent file must have a fixed name (AGENT_JAR_NAME) which is mentioned in MANIFEST.MF inside
Files.copy(downloadedAgent, agentArtifactPath);
return asEelPath(agentArtifactPath).toString();
}
catch (IOException e) {
throw new RuntimeException(e);
}
}
@NativePath
private static @Nullable String getArtifactPathForBundledAgent(
@NotNull Path classesRoot,
@Nullable Project project,
@Nullable Disposable disposable
) {
Path bundledAgentPath = classesRoot.resolveSibling("rt").resolve(AGENT_JAR_NAME);
if (!Files.exists(bundledAgentPath)) {
return null;
}
EelDescriptor projectEelDescriptor = project == null ? null : EelProviderUtil.getEelDescriptor(project);
if (project == null || LocalEelDescriptor.INSTANCE.equals(projectEelDescriptor)) {
return JavaExecutionUtil.handleSpacesInAgentPath(
bundledAgentPath.toAbsolutePath().toString(),
"captureAgent",
null,
f -> f.getName().startsWith("debugger-agent")
);
}
Path temporaryAgentPath = createTemporaryAgentPath(project, disposable);
EelPathUtils.transferLocalContentToRemote(
bundledAgentPath,
new EelPathUtils.TransferTarget.Explicit(temporaryAgentPath)
);
return asEelPath(temporaryAgentPath).toString();
}
private static @NotNull Path createTemporaryAgentPath(@Nullable Project project, @Nullable Disposable disposable) {
// The agent file must have a fixed name (AGENT_JAR_NAME) which is mentioned in MANIFEST.MF inside
Path debuggerAgentDir = EelPathUtils.createTemporaryDirectory(project, "debugger-agent", "", disposable == null);
if (disposable != null) {
Disposer.register(disposable, () -> {
try {
FileUtilRt.deleteRecursively(debuggerAgentDir);
}
catch (IOException ignored) {
}
});
}
return debuggerAgentDir.resolve(AGENT_JAR_NAME);
}
private static String generateAgentSettings(@Nullable Project project) {
Properties properties = CaptureSettingsProvider.getPointsProperties(project);
if (isSuspendHelperEnabled()) {
@@ -12,11 +12,7 @@ import com.intellij.platform.eel.fs.createTemporaryDirectory
import com.intellij.platform.eel.fs.createTemporaryFile
import com.intellij.platform.eel.isWindows
import com.intellij.platform.eel.path.EelPath
import com.intellij.platform.eel.provider.LocalEelDescriptor
import com.intellij.platform.eel.provider.asEelPath
import com.intellij.platform.eel.provider.asNioPath
import com.intellij.platform.eel.provider.getEelDescriptor
import com.intellij.platform.eel.provider.toEelApiBlocking
import com.intellij.platform.eel.provider.*
import com.intellij.platform.eel.provider.utils.EelPathUtils.transferLocalContentToRemote
import com.intellij.util.concurrency.annotations.RequiresBackgroundThread
import kotlinx.coroutines.*
@@ -105,21 +101,31 @@ object EelPathUtils {
}
@JvmStatic
@OptIn(ExperimentalPathApi::class)
@RequiresBackgroundThread(generateAssertion = false)
fun createTemporaryDirectory(project: Project?, prefix: String = "", suffix: String = ""): Path {
if (project == null || isProjectLocal(project)) {
return Files.createTempDirectory(prefix)
fun createTemporaryDirectory(project: Project?, prefix: String = "", suffix: String = "", deleteOnExit: Boolean = false): Path {
if (project == null || isProjectLocal(project) || project.projectFilePath == null) {
val dir = Files.createTempDirectory(prefix)
if (deleteOnExit) {
Runtime.getRuntime().addShutdownHook(Thread {
dir.deleteRecursively()
})
}
return dir
}
val projectFilePath = project.projectFilePath ?: return Files.createTempDirectory(prefix)
return runBlockingMaybeCancellable {
val eel = Path.of(projectFilePath).getEelDescriptor().toEelApi()
createTemporaryDirectory(eel, prefix, suffix)
val eel = Path.of(project.projectFilePath!!).getEelDescriptor().toEelApi()
createTemporaryDirectory(eel, prefix, suffix, deleteOnExit)
}
}
@JvmStatic
suspend fun createTemporaryDirectory(eelApi: EelApi, prefix: String = "", suffix: String = ""): Path {
val file = eelApi.fs.createTemporaryDirectory().prefix(prefix).suffix(suffix).getOrThrowFileSystemException()
suspend fun createTemporaryDirectory(eelApi: EelApi, prefix: String = "", suffix: String = "", deleteOnExit: Boolean = false): Path {
val file = eelApi.fs.createTemporaryDirectory()
.prefix(prefix)
.suffix(suffix)
.deleteOnExit(deleteOnExit)
.getOrThrowFileSystemException()
return file.asNioPath()
}
@@ -333,7 +339,7 @@ object EelPathUtils {
private suspend fun EelApi.createTempFor(source: Path, deleteOnExit: Boolean): Path {
return if (source.isDirectory()) {
fs.createTemporaryDirectory().deleteOnExit(deleteOnExit).getOrThrowFileSystemException().asNioPath()
createTemporaryDirectory(eelApi = this, deleteOnExit = deleteOnExit)
}
else {
fs.createTemporaryFile().suffix(source.name).deleteOnExit(deleteOnExit).getOrThrowFileSystemException().asNioPath()
@@ -108,6 +108,8 @@ class ScriptMavenExecutionTest : MavenExecutionTest() {
val debugOpts = debugExecInfo.stdout.lines().singleOrNull { it.startsWith("MAVEN_OPTS") }
assertNotNull(debugExecInfo.toString(), debugOpts)
assertTrue(debugOpts, debugOpts!!.contains("-agentlib:jdwp=transport=dt_socket"))
// maven.use.scripts.debug.agent = true is set to `true` by default
assertTrue(debugOpts, debugOpts.contains("debugger-agent.jar"))
}