From 036cc0a0fc3af19e25a19748fd0e2a812d61f7cc Mon Sep 17 00:00:00 2001 From: "Vladislav.Soroka" Date: Mon, 26 Feb 2018 17:49:53 +0300 Subject: [PATCH] Gradle: cross-cutting debug of the Gradle groovy DSL(daemon) and subsequent java forked tasks (IDEA-119494) --- .../ExternalSystemRunConfiguration.java | 23 +- .../ExternalSystemTaskDebugRunner.java | 228 ++++++++++++++++-- .../rt/execution/ForkedDebuggerHelper.java | 82 +++++++ .../gradle/config/GradlePositionManager.java | 107 +++----- .../BaseGradleProjectResolverExtension.java | 57 ++++- .../service/task/GradleTaskManager.java | 19 ++ 6 files changed, 404 insertions(+), 112 deletions(-) create mode 100644 platform/external-system-rt/src/com/intellij/openapi/externalSystem/rt/execution/ForkedDebuggerHelper.java diff --git a/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/execution/ExternalSystemRunConfiguration.java b/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/execution/ExternalSystemRunConfiguration.java index f8f40fe90102..b01e007dec61 100644 --- a/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/execution/ExternalSystemRunConfiguration.java +++ b/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/execution/ExternalSystemRunConfiguration.java @@ -79,6 +79,8 @@ import javax.swing.*; import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.net.InetAddress; +import java.net.ServerSocket; import java.util.List; import static com.intellij.openapi.externalSystem.util.ExternalSystemUtil.convert; @@ -92,9 +94,11 @@ public class ExternalSystemRunConfiguration extends LocatableConfigurationBase i SMRunnerConsolePropertiesProvider { public static final Key RUN_INPUT_KEY = Key.create("RUN_INPUT_KEY"); public static final Key> PROGRESS_LISTENER_KEY = Key.create("PROGRESS_LISTENER_KEY"); + public static final String DEBUG_SETUP_PREFIX = "-agentlib:jdwp=transport=dt_socket,server=n,suspend=y,address="; private static final Logger LOG = Logger.getInstance(ExternalSystemRunConfiguration.class); private ExternalSystemTaskExecutionSettings mySettings = new ExternalSystemTaskExecutionSettings(); + private static final boolean DISABLE_FORK_DEBUGGER = Boolean.getBoolean("external.system.disable.fork.debugger."); public ExternalSystemRunConfiguration(@NotNull ProjectSystemId externalSystemId, Project project, @@ -219,6 +223,7 @@ public class ExternalSystemRunConfiguration extends LocatableConfigurationBase i @Nullable private RunContentDescriptor myContentDescriptor; private final int myDebugPort; + private ServerSocket myForkSocket = null; public MyRunnableState(@NotNull ExternalSystemTaskExecutionSettings settings, @NotNull Project project, @@ -249,6 +254,19 @@ public class ExternalSystemRunConfiguration extends LocatableConfigurationBase i return myDebugPort; } + @Nullable + public ServerSocket getForkSocket() { + if (myForkSocket == null && !DISABLE_FORK_DEBUGGER) { + try { + myForkSocket = new ServerSocket(0, 0, InetAddress.getByName("127.0.0.1")); + } + catch (IOException e) { + LOG.error(e); + } + } + return myForkSocket; + } + @Nullable @Override public ExecutionResult execute(Executor executor, @NotNull ProgramRunner runner) throws ExecutionException { @@ -266,7 +284,10 @@ public class ExternalSystemRunConfiguration extends LocatableConfigurationBase i String jvmAgentSetup; if (myDebugPort > 0) { - jvmAgentSetup = "-agentlib:jdwp=transport=dt_socket,server=n,suspend=y,address=" + myDebugPort; + jvmAgentSetup = DEBUG_SETUP_PREFIX + myDebugPort; + if (getForkSocket() != null) { + jvmAgentSetup += " -forkSocket" + getForkSocket().getLocalPort(); + } } else { ParametersList parametersList = extensionsJP.getVMParametersList(); diff --git a/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/execution/ExternalSystemTaskDebugRunner.java b/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/execution/ExternalSystemTaskDebugRunner.java index aca70d3d55c3..31fd344720b8 100644 --- a/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/execution/ExternalSystemTaskDebugRunner.java +++ b/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/execution/ExternalSystemTaskDebugRunner.java @@ -18,18 +18,40 @@ package com.intellij.openapi.externalSystem.service.execution; import com.intellij.build.BuildView; import com.intellij.debugger.impl.GenericDebuggerRunner; import com.intellij.execution.ExecutionException; +import com.intellij.execution.ProgramRunnerUtil; +import com.intellij.execution.RunManager; +import com.intellij.execution.RunnerAndConfigurationSettings; +import com.intellij.execution.configurations.ConfigurationFactory; import com.intellij.execution.configurations.RemoteConnection; import com.intellij.execution.configurations.RunProfile; import com.intellij.execution.configurations.RunProfileState; import com.intellij.execution.executors.DefaultDebugExecutor; +import com.intellij.execution.process.ProcessAdapter; +import com.intellij.execution.process.ProcessEvent; +import com.intellij.execution.process.ProcessHandler; +import com.intellij.execution.remote.RemoteConfiguration; +import com.intellij.execution.remote.RemoteConfigurationType; import com.intellij.execution.runners.ExecutionEnvironment; +import com.intellij.execution.runners.ExecutionEnvironmentBuilder; import com.intellij.execution.ui.ExecutionConsole; import com.intellij.execution.ui.RunContentDescriptor; +import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.externalSystem.util.ExternalSystemConstants; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.wm.ToolWindowId; +import com.intellij.openapi.wm.ToolWindowManager; +import com.intellij.ui.content.Content; +import com.intellij.ui.content.ContentManager; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.io.DataInputStream; +import java.io.EOFException; +import java.io.IOException; +import java.net.ServerSocket; +import java.net.Socket; + /** * @author Denis Zhdanov * @since 6/7/13 11:18 AM @@ -52,30 +74,18 @@ public class ExternalSystemTaskDebugRunner extends GenericDebuggerRunner { @Override protected RunContentDescriptor createContentDescriptor(@NotNull RunProfileState state, @NotNull ExecutionEnvironment environment) throws ExecutionException { if (state instanceof ExternalSystemRunConfiguration.MyRunnableState) { - int port = ((ExternalSystemRunConfiguration.MyRunnableState)state).getDebugPort(); + ExternalSystemRunConfiguration.MyRunnableState myRunnableState = (ExternalSystemRunConfiguration.MyRunnableState)state; + int port = myRunnableState.getDebugPort(); if (port > 0) { - RemoteConnection connection = new RemoteConnection(true, "127.0.0.1", String.valueOf(port), true); - RunContentDescriptor runContentDescriptor = attachVirtualMachine(state, environment, connection, true); + RunContentDescriptor runContentDescriptor = doGetRunContentDescriptor(myRunnableState, environment, port); if (runContentDescriptor == null) return null; - ((ExternalSystemRunConfiguration.MyRunnableState)state).setContentDescriptor(runContentDescriptor); - - ExecutionConsole executionConsole = runContentDescriptor.getExecutionConsole(); - if(executionConsole instanceof BuildView) { - return runContentDescriptor; + ProcessHandler processHandler = runContentDescriptor.getProcessHandler(); + final ServerSocket socket = myRunnableState.getForkSocket(); + if (socket != null && processHandler != null) { + new ForkedDebuggerThread(processHandler, socket, environment.getProject()).start(); } - RunContentDescriptor descriptor = - new RunContentDescriptor(runContentDescriptor.getExecutionConsole(), runContentDescriptor.getProcessHandler(), - runContentDescriptor.getComponent(), runContentDescriptor.getDisplayName(), - runContentDescriptor.getIcon(), null, - runContentDescriptor.getRestartActions()) { - @Override - public boolean isHiddenContent() { - return true; - } - }; - descriptor.setRunnerLayoutUi(runContentDescriptor.getRunnerLayoutUi()); - return descriptor; + return runContentDescriptor; } else { LOG.warn("Can't attach debugger to external system task execution. Reason: target debug port is unknown"); @@ -90,4 +100,182 @@ public class ExternalSystemTaskDebugRunner extends GenericDebuggerRunner { } return null; } + + @Nullable + private RunContentDescriptor doGetRunContentDescriptor(@NotNull ExternalSystemRunConfiguration.MyRunnableState state, + @NotNull ExecutionEnvironment environment, + int port) throws ExecutionException { + RemoteConnection connection = new RemoteConnection(true, "127.0.0.1", String.valueOf(port), true); + RunContentDescriptor runContentDescriptor = attachVirtualMachine(state, environment, connection, true); + if (runContentDescriptor == null) return null; + + state.setContentDescriptor(runContentDescriptor); + + ExecutionConsole executionConsole = runContentDescriptor.getExecutionConsole(); + if (executionConsole instanceof BuildView) { + return runContentDescriptor; + } + RunContentDescriptor descriptor = + new RunContentDescriptor(runContentDescriptor.getExecutionConsole(), runContentDescriptor.getProcessHandler(), + runContentDescriptor.getComponent(), runContentDescriptor.getDisplayName(), + runContentDescriptor.getIcon(), null, + runContentDescriptor.getRestartActions()) { + @Override + public boolean isHiddenContent() { + return true; + } + }; + descriptor.setRunnerLayoutUi(runContentDescriptor.getRunnerLayoutUi()); + return descriptor; + } + + private static class ForkedDebuggerThread extends Thread { + private final ProcessHandler myProcessHandler; + private final ServerSocket mySocket; + private final Project myProject; + + public ForkedDebuggerThread(ProcessHandler processHandler, ServerSocket socket, Project project) { + super("external task forked debugger runner"); + setDaemon(true); + myProcessHandler = processHandler; + mySocket = socket; + myProject = project; + myProcessHandler.addProcessListener(new ProcessAdapter() { + @Override + public void processTerminated(@NotNull ProcessEvent event) { + closeSocket(); + } + + @Override + public void processWillTerminate(@NotNull ProcessEvent event, boolean willBeDestroyed) { + closeSocket(); + } + + void closeSocket() { + try { + mySocket.close(); + } + catch (IOException ignore) { + } + } + }); + } + + @Override + public void run() { + while (!myProcessHandler.isProcessTerminated() && !myProcessHandler.isProcessTerminating() && !mySocket.isClosed()) { + try { + Socket accept = mySocket.accept(); + handleForkedProcessSignal(accept, myProject, myProcessHandler); + } + catch (EOFException ignored) { + } + catch (IOException e) { + LOG.warn(e); + } + } + try { + mySocket.close(); + } + catch (IOException e) { + LOG.debug(e); + } + } + + public static void handleForkedProcessSignal(Socket accept, Project project, ProcessHandler processHandler) throws IOException { + try { + DataInputStream stream = new DataInputStream(accept.getInputStream()); + try { + int signal = stream.readInt(); + String processName = stream.readUTF(); + if (signal > 0) { + String debugPort = String.valueOf(signal); + attachVM(project, processName, debugPort, new Callback() { + @Override + public void processStarted(RunContentDescriptor descriptor) { + // select tab for the forked process only when it has been suspended + descriptor.setSelectContentWhenAdded(false); + + // restore selection of the 'main' tab to avoid flickering of the reused content tab when no suspend events occur + ProcessHandler processHandler = descriptor.getProcessHandler(); + if (processHandler != null) { + processHandler.addProcessListener(new ProcessAdapter() { + @Override + public void processTerminated(@NotNull ProcessEvent event) { + final ToolWindowManager toolWindowManager = ToolWindowManager.getInstance(project); + ContentManager contentManager = toolWindowManager.getToolWindow(ToolWindowId.DEBUG).getContentManager(); + Content content = descriptor.getAttachedContent(); + if (content != null) { + ApplicationManager.getApplication().invokeLater(() -> contentManager.removeContent(content, true)); + } + } + }); + try { + accept.getOutputStream().write(0); + } + catch (IOException e) { + LOG.debug(e); + } + } + } + }); + } + else if (signal == 0) { + // remove content for terminated forked processes + ApplicationManager.getApplication().invokeLater(() -> { + final ToolWindowManager toolWindowManager = ToolWindowManager.getInstance(project); + ContentManager contentManager = toolWindowManager.getToolWindow(ToolWindowId.DEBUG).getContentManager(); + Content content = contentManager.findContent(processName); + if (content != null) { + RunContentDescriptor descriptor = content.getUserData(RunContentDescriptor.DESCRIPTOR_KEY); + if (descriptor != null) { + ProcessHandler handler = descriptor.getProcessHandler(); + if (handler != null) { + handler.destroyProcess(); + } + } + } + try { + accept.getOutputStream().write(0); + } + catch (IOException e) { + LOG.debug(e); + } + }); + } + } + finally { + stream.close(); + } + } + finally { + accept.close(); + } + } + + private static void attachVM(@NotNull Project project, String runConfigName, @NotNull String debugPort, Callback callback) { + RemoteConfigurationType remoteConfigurationType = RemoteConfigurationType.getInstance(); + ConfigurationFactory factory = remoteConfigurationType.getFactory(); + RunnerAndConfigurationSettings runSettings = RunManager.getInstance(project).createRunConfiguration(runConfigName, factory); + runSettings.setActivateToolWindowBeforeRun(false); + + RemoteConfiguration configuration = (RemoteConfiguration)runSettings.getConfiguration(); + configuration.HOST = "localhost"; + configuration.PORT = debugPort; + configuration.USE_SOCKET_TRANSPORT = true; + configuration.SERVER_MODE = true; + + try { + ExecutionEnvironment environment = ExecutionEnvironmentBuilder.create(DefaultDebugExecutor.getDebugExecutorInstance(), runSettings) + .contentToReuse(null) + .dataContext(null) + .activeTarget() + .build(); + ProgramRunnerUtil.executeConfigurationAsync(environment, true, true, callback); + } + catch (ExecutionException e) { + LOG.error(e); + } + } + } } diff --git a/platform/external-system-rt/src/com/intellij/openapi/externalSystem/rt/execution/ForkedDebuggerHelper.java b/platform/external-system-rt/src/com/intellij/openapi/externalSystem/rt/execution/ForkedDebuggerHelper.java new file mode 100644 index 000000000000..58eac59b2064 --- /dev/null +++ b/platform/external-system-rt/src/com/intellij/openapi/externalSystem/rt/execution/ForkedDebuggerHelper.java @@ -0,0 +1,82 @@ +// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.openapi.externalSystem.rt.execution; + +import java.io.DataOutputStream; +import java.io.IOException; +import java.net.ServerSocket; +import java.net.Socket; + +/** + * @author Vladislav.Soroka + */ +public class ForkedDebuggerHelper { + + public static String setupDebugger(String processName, int debugPort) { + String setup = ""; + try { + if (debugPort > -1) { + int debugAddress = findAvailableSocketPort(); + if (debugAddress > -1) { + setup = "-agentlib:jdwp=transport=dt_socket,server=n,suspend=y,address=" + debugAddress; + send(debugAddress, processName, debugPort); + } + } + } + catch (Exception e) { + //noinspection CallToPrintStackTrace + e.printStackTrace(); + } + return setup; + } + + public static void processFinished(String processName, int debugPort) { + send(0, processName, debugPort); + } + + private static void send(int signal, String processName, int debugPort) { + try { + Socket socket = new Socket("127.0.0.1", debugPort); + try { + DataOutputStream stream = new DataOutputStream(socket.getOutputStream()); + try { + stream.writeInt(signal); + stream.writeUTF(processName); + // wait for the signal handling + int read = socket.getInputStream().read(); + } + finally { + stream.close(); + } + } + finally { + socket.close(); + } + } + catch (Exception ignore) { + } + } + + // copied from NetUtils + protected static int findAvailableSocketPort() throws IOException { + final ServerSocket serverSocket = new ServerSocket(0); + try { + int port = serverSocket.getLocalPort(); + // workaround for linux : calling close() immediately after opening socket + // may result that socket is not closed + //noinspection SynchronizationOnLocalVariableOrMethodParameter + synchronized (serverSocket) { + try { + //noinspection WaitNotInLoop + serverSocket.wait(1); + } + catch (InterruptedException e) { + System.err.println(e); + } + } + return port; + } + finally { + serverSocket.close(); + } + } +} diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/config/GradlePositionManager.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/config/GradlePositionManager.java index b530727f0331..31d8d9633d2d 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/config/GradlePositionManager.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/config/GradlePositionManager.java @@ -15,18 +15,16 @@ */ package org.jetbrains.plugins.gradle.config; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.externalSystem.ExternalSystemModulePropertyManager; import com.intellij.openapi.module.Module; import com.intellij.openapi.module.ModuleUtilCore; import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.ProjectRootManager; import com.intellij.openapi.util.Key; import com.intellij.openapi.util.io.FileUtil; -import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VfsUtilCore; import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiClass; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiManager; import com.intellij.psi.search.GlobalSearchScope; @@ -34,41 +32,27 @@ import com.intellij.psi.util.CachedValue; import com.intellij.psi.util.CachedValueProvider; import com.intellij.psi.util.CachedValuesManager; import com.intellij.util.containers.ConcurrentFactoryMap; -import com.intellij.util.lang.UrlClassLoader; import com.sun.jdi.AbsentInformationException; import com.sun.jdi.ReferenceType; +import org.gradle.groovy.scripts.TextResourceScriptSource; +import org.gradle.internal.resource.BasicTextResourceLoader; +import org.gradle.internal.resource.TextResource; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.jetbrains.plugins.gradle.service.GradleInstallationManager; import org.jetbrains.plugins.groovy.extensions.debugger.ScriptPositionManagerHelper; import org.jetbrains.plugins.groovy.lang.psi.GroovyFile; import org.jetbrains.plugins.groovy.runner.GroovyScriptUtil; import java.io.File; -import java.net.URL; -import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.regex.Pattern; -/** - * @author John Murph - */ public class GradlePositionManager extends ScriptPositionManagerHelper { - - private static final Logger LOG = Logger.getInstance("#org.jetbrains.plugins.gradle.config.GradlePositionManager"); - private static final Pattern GRADLE_CLASS_PATTERN = Pattern.compile(".*_gradle_.*"); private static final String SCRIPT_CLOSURE_PREFIX = "build_"; - private static final Key> GRADLE_CLASS_LOADER = Key.create("GRADLE_CLASS_LOADER"); private static final Key>> GRADLE_CLASS_NAME = Key.create("GRADLE_CLASS_NAME"); - private final GradleInstallationManager myLibraryManager; - - public GradlePositionManager(@NotNull GradleInstallationManager manager) { - myLibraryManager = manager; - } - @Override public boolean isAppropriateRuntimeName(@NotNull final String runtimeName) { return runtimeName.startsWith(SCRIPT_CLOSURE_PREFIX) || GRADLE_CLASS_PATTERN.matcher(runtimeName).matches(); @@ -92,12 +76,28 @@ public class GradlePositionManager extends ScriptPositionManagerHelper { final File scriptFile = VfsUtilCore.virtualToIoFile(virtualFile); final String className = CachedValuesManager.getManager(module.getProject()) - .getCachedValue(module, GRADLE_CLASS_NAME, new ScriptSourceMapCalculator(module), false).get(scriptFile); + .getCachedValue(module, GRADLE_CLASS_NAME, new ScriptSourceMapCalculator(module), false) + .get(scriptFile); return className == null ? "" : className; } + @Nullable @Override - public PsiFile getExtraScriptIfNotFound(@NotNull ReferenceType refType, @NotNull String runtimeName, @NotNull Project project, @NotNull GlobalSearchScope scope) { + public String customizeClassName(@NotNull PsiClass psiClass) { + PsiFile file = psiClass.getContainingFile(); + if (file instanceof GroovyFile) { + return getRuntimeScriptName((GroovyFile)file); + } + else { + return null; + } + } + + @Override + public PsiFile getExtraScriptIfNotFound(@NotNull ReferenceType refType, + @NotNull String runtimeName, + @NotNull Project project, + @NotNull GlobalSearchScope scope) { String sourceFilePath = getScriptForClassName(refType); if (sourceFilePath == null) return null; @@ -120,37 +120,7 @@ public class GradlePositionManager extends ScriptPositionManagerHelper { return null; } - @Nullable - private ClassLoader getGradleClassLoader(@NotNull final Module module) { - final Project project = module.getProject(); - return CachedValuesManager.getManager(project).getCachedValue(module, GRADLE_CLASS_LOADER, - () -> CachedValueProvider.Result.create(createGradleClassLoader(module), ProjectRootManager.getInstance(project)), false); - } - - @Nullable - private ClassLoader createGradleClassLoader(@NotNull Module module) { - String rootProjectPath = ExternalSystemModulePropertyManager.getInstance(module).getRootProjectPath(); - if (StringUtil.isEmpty(rootProjectPath)) { - return null; - } - final VirtualFile sdkHome = myLibraryManager.getGradleHome(module, module.getProject(), rootProjectPath); - if (sdkHome == null) { - return null; - } - - List urls = new ArrayList<>(); - final VirtualFile libDir = sdkHome.findChild("lib"); - assert libDir != null; - for (final VirtualFile child : libDir.getChildren()) { - if ("jar".equals(child.getExtension())) { - urls.add(VfsUtilCore.convertToURL(child.getUrl())); - } - } - - return UrlClassLoader.build().urls(urls).get(); - } - - private class ScriptSourceMapCalculator implements CachedValueProvider> { + private static class ScriptSourceMapCalculator implements CachedValueProvider> { private final Module myModule; public ScriptSourceMapCalculator(Module module) { @@ -159,36 +129,15 @@ public class GradlePositionManager extends ScriptPositionManagerHelper { @Override public Result> compute() { - final Map result = ConcurrentFactoryMap.createMap(scriptFile -> calcClassName(scriptFile)); + final Map result = ConcurrentFactoryMap.createMap(ScriptSourceMapCalculator::calcClassName); return Result.create(result, ProjectRootManager.getInstance(myModule.getProject())); } @Nullable - private String calcClassName(File scriptFile) { - final ClassLoader loader = getGradleClassLoader(myModule); - if (loader != null) { - Class fileScriptSource; - try { - fileScriptSource = Class.forName("org.gradle.groovy.scripts.UriScriptSource", true, loader); - } - catch (ClassNotFoundException e) { - try { - fileScriptSource = Class.forName("org.gradle.groovy.scripts.FileScriptSource", true, loader); //before 0.9 - } - catch (ClassNotFoundException e1) { - return null; - } - } - - try { - final Object source = fileScriptSource.getConstructor(String.class, File.class).newInstance("script", scriptFile); - return (String)fileScriptSource.getMethod("getClassName").invoke(source); - } - catch (Exception e) { - LOG.error(e); - } - } - return null; + private static String calcClassName(File scriptFile) { + TextResource resource = new BasicTextResourceLoader().loadFile("script", scriptFile); + TextResourceScriptSource scriptSource = new TextResourceScriptSource(resource); + return scriptSource.getClassName(); } } } diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/service/project/BaseGradleProjectResolverExtension.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/service/project/BaseGradleProjectResolverExtension.java index a9031fa01e40..58e469f7a164 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/service/project/BaseGradleProjectResolverExtension.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/service/project/BaseGradleProjectResolverExtension.java @@ -92,6 +92,7 @@ import static com.intellij.openapi.util.Pair.pair; import static org.jetbrains.plugins.gradle.service.project.GradleProjectResolver.CONFIGURATION_ARTIFACTS; import static org.jetbrains.plugins.gradle.service.project.GradleProjectResolver.MODULES_OUTPUTS; import static org.jetbrains.plugins.gradle.service.project.GradleProjectResolverUtil.*; +import static org.jetbrains.plugins.gradle.service.task.GradleTaskManager.getForkedDebuggerSetup; /** * {@link BaseGradleProjectResolverExtension} provides base implementation of Gradle project resolver. @@ -727,21 +728,53 @@ public class BaseGradleProjectResolverExtension implements GradleProjectResolver @Nullable String jvmAgentSetup, @NotNull Consumer initScriptConsumer) { if (!StringUtil.isEmpty(jvmAgentSetup)) { - final String names = "[\"" + StringUtil.join(taskNames, "\", \"") + "\"]"; - final String[] lines = { - "gradle.taskGraph.beforeTask { Task task ->", - " if (task instanceof JavaForkOptions && (" + names + ".contains(task.name) || " + names + ".contains(task.path))) {", - " def jvmArgs = task.jvmArgs.findAll{!it?.startsWith('-agentlib:jdwp') && !it?.startsWith('-Xrunjdwp')}", - " jvmArgs << '" + jvmAgentSetup.trim().replace("\\", "\\\\") + '\'', - " task.jvmArgs = jvmArgs", - " }" + - "}", - }; - final String script = StringUtil.join(lines, SystemProperties.getLineSeparator()); - initScriptConsumer.consume(script); + int debugPort = getForkedDebuggerSetup(jvmAgentSetup); + if (debugPort != -1) { + setupDebugForAllJvmForkedTasks(initScriptConsumer, debugPort); + } + else { + final String names = "[\"" + StringUtil.join(taskNames, "\", \"") + "\"]"; + final String[] lines = { + "gradle.taskGraph.beforeTask { Task task ->", + " if (task instanceof JavaForkOptions && (" + names + ".contains(task.name) || " + names + ".contains(task.path))) {", + " def jvmArgs = task.jvmArgs.findAll{!it?.startsWith('-agentlib:jdwp') && !it?.startsWith('-Xrunjdwp')}", + " jvmArgs << '" + jvmAgentSetup.trim().replace("\\", "\\\\") + '\'', + " task.jvmArgs = jvmArgs", + " }" + + "}", + }; + final String script = StringUtil.join(lines, SystemProperties.getLineSeparator()); + initScriptConsumer.consume(script); + } } } + public void setupDebugForAllJvmForkedTasks(@NotNull Consumer initScriptConsumer, int debugPort) { + // external-system-rt.jar + String esRtJarPath = PathUtil.getCanonicalPath(PathManager.getJarPathForClass(ExternalSystemSourceType.class)); + final String[] lines = { + "initscript {", + " dependencies {", + " classpath files(\"" + esRtJarPath + "\")", + " }", + "}", + "gradle.taskGraph.beforeTask { Task task ->", + " if (task instanceof JavaForkOptions) {", + " def jvmArgs = task.jvmArgs.findAll{!it?.startsWith('-agentlib:jdwp') && !it?.startsWith('-Xrunjdwp')}", + " vmArgs << com.intellij.openapi.externalSystem.rt.execution.ForkedDebuggerHelper.setupDebugger(task.path, " + debugPort + ")", + " task.jvmArgs = jvmArgs", + " }", + "}", + "gradle.taskGraph.afterTask { Task task ->", + " if (task instanceof JavaForkOptions) {", + " com.intellij.openapi.externalSystem.rt.execution.ForkedDebuggerHelper.processFinished(task.path, " + debugPort + ")", + " }", + "}", + }; + final String script = StringUtil.join(lines, SystemProperties.getLineSeparator()); + initScriptConsumer.consume(script); + } + @Override public void enhanceRemoteProcessing(@NotNull SimpleJavaParameters parameters) throws ExecutionException { PathsList classPath = parameters.getClassPath(); diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/service/task/GradleTaskManager.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/service/task/GradleTaskManager.java index ab93deb7c401..6754ad796158 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/service/task/GradleTaskManager.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/service/task/GradleTaskManager.java @@ -21,6 +21,7 @@ import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskId; import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskNotificationListener; import com.intellij.openapi.externalSystem.model.task.event.ExternalSystemProgressEventUnsupportedImpl; import com.intellij.openapi.externalSystem.model.task.event.ExternalSystemTaskExecutionEvent; +import com.intellij.openapi.externalSystem.service.execution.ExternalSystemRunConfiguration; import com.intellij.openapi.externalSystem.task.ExternalSystemTaskManager; import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil; import com.intellij.openapi.util.Key; @@ -85,6 +86,10 @@ public class GradleTaskManager implements ExternalSystemTaskManager f = connection -> { try { appendInitScriptArgument(taskNames, jvmAgentSetup, effectiveSettings); @@ -127,6 +132,20 @@ public class GradleTaskManager implements ExternalSystemTaskManager 0) { + try { + return Integer.parseInt(jvmAgentSetup.substring(forkSocketIndex + "-forkSocket".length())); + } + catch (NumberFormatException ignore) { + } + } + } + return -1; + } + public static void appendInitScriptArgument(@NotNull List taskNames, @Nullable String jvmAgentSetup, @NotNull GradleExecutionSettings effectiveSettings) {