Gradle: cross-cutting debug of the Gradle groovy DSL(daemon) and subsequent java forked tasks (IDEA-119494)

This commit is contained in:
Vladislav.Soroka
2018-02-26 17:50:48 +03:00
parent 1ad33a25e3
commit 036cc0a0fc
6 changed files with 404 additions and 112 deletions
@@ -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<InputStream> RUN_INPUT_KEY = Key.create("RUN_INPUT_KEY");
public static final Key<Class<? extends BuildProgressListener>> 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();
@@ -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);
}
}
}
}
@@ -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();
}
}
}
@@ -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<CachedValue<ClassLoader>> GRADLE_CLASS_LOADER = Key.create("GRADLE_CLASS_LOADER");
private static final Key<CachedValue<Map<File, String>>> 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<URL> 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<Map<File, String>> {
private static class ScriptSourceMapCalculator implements CachedValueProvider<Map<File, String>> {
private final Module myModule;
public ScriptSourceMapCalculator(Module module) {
@@ -159,36 +129,15 @@ public class GradlePositionManager extends ScriptPositionManagerHelper {
@Override
public Result<Map<File, String>> compute() {
final Map<File, String> result = ConcurrentFactoryMap.createMap(scriptFile -> calcClassName(scriptFile));
final Map<File, String> 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();
}
}
}
@@ -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<String> 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<String> 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();
@@ -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<GradleExecut
GradleExecutionSettings effectiveSettings =
settings == null ? new GradleExecutionSettings(null, null, DistributionType.BUNDLED, false) : settings;
if (getForkedDebuggerSetup(jvmAgentSetup) != -1) {
effectiveSettings.withVmOption(jvmAgentSetup);
}
Function<ProjectConnection, Void> f = connection -> {
try {
appendInitScriptArgument(taskNames, jvmAgentSetup, effectiveSettings);
@@ -127,6 +132,20 @@ public class GradleTaskManager implements ExternalSystemTaskManager<GradleExecut
myHelper.execute(projectPath, effectiveSettings, f);
}
public static int getForkedDebuggerSetup(@Nullable String jvmAgentSetup) {
if (jvmAgentSetup != null && jvmAgentSetup.startsWith(ExternalSystemRunConfiguration.DEBUG_SETUP_PREFIX)) {
int forkSocketIndex = jvmAgentSetup.indexOf("-forkSocket");
if (forkSocketIndex > 0) {
try {
return Integer.parseInt(jvmAgentSetup.substring(forkSocketIndex + "-forkSocket".length()));
}
catch (NumberFormatException ignore) {
}
}
}
return -1;
}
public static void appendInitScriptArgument(@NotNull List<String> taskNames,
@Nullable String jvmAgentSetup,
@NotNull GradleExecutionSettings effectiveSettings) {