/*
* Copyright 2013-2016 Sergey Ignatov, Alexander Zolotov, Florin Patan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.goide.runconfig;
import com.goide.GoEnvironmentUtil;
import com.goide.dlv.DlvDebugProcess;
import com.goide.dlv.DlvRemoteVmConnection;
import com.goide.runconfig.application.GoApplicationConfiguration;
import com.goide.runconfig.application.GoApplicationRunningState;
import com.goide.util.GoHistoryProcessListener;
import com.intellij.execution.ExecutionException;
import com.intellij.execution.ExecutionResult;
import com.intellij.execution.RunProfileStarter;
import com.intellij.execution.RunnerAndConfigurationSettings;
import com.intellij.execution.configurations.RunProfile;
import com.intellij.execution.configurations.RunProfileState;
import com.intellij.execution.executors.DefaultDebugExecutor;
import com.intellij.execution.executors.DefaultRunExecutor;
import com.intellij.execution.process.ProcessAdapter;
import com.intellij.execution.process.ProcessEvent;
import com.intellij.execution.runners.AsyncGenericProgramRunner;
import com.intellij.execution.runners.ExecutionEnvironment;
import com.intellij.execution.runners.RunContentBuilder;
import com.intellij.execution.ui.RunContentDescriptor;
import com.intellij.internal.statistic.UsageTrigger;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.ArrayUtil;
import com.intellij.util.net.NetUtils;
import com.intellij.xdebugger.XDebugProcess;
import com.intellij.xdebugger.XDebugProcessStarter;
import com.intellij.xdebugger.XDebugSession;
import com.intellij.xdebugger.XDebuggerManager;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.concurrency.AsyncPromise;
import org.jetbrains.concurrency.Promise;
import org.jetbrains.debugger.connection.RemoteVmConnection;
import java.io.File;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
public class GoBuildingRunner extends AsyncGenericProgramRunner {
private static final String ID = "GoBuildingRunner";
@NotNull
@Override
public String getRunnerId() {
return ID;
}
@Override
public boolean canRun(@NotNull String executorId, @NotNull RunProfile profile) {
if (profile instanceof GoApplicationConfiguration) {
return DefaultRunExecutor.EXECUTOR_ID.equals(executorId)
|| DefaultDebugExecutor.EXECUTOR_ID.equals(executorId) && !DlvDebugProcess.IS_DLV_DISABLED;
}
return false;
}
@NotNull
@Override
protected Promise<RunProfileStarter> prepare(@NotNull ExecutionEnvironment environment, @NotNull RunProfileState state)
throws ExecutionException {
File outputFile = getOutputFile(environment, (GoApplicationRunningState)state);
FileDocumentManager.getInstance().saveAllDocuments();
AsyncPromise<RunProfileStarter> buildingPromise = new AsyncPromise<>();
GoHistoryProcessListener historyProcessListener = new GoHistoryProcessListener();
((GoApplicationRunningState)state).createCommonExecutor()
.withParameters("build")
.withParameterString(((GoApplicationRunningState)state).getGoBuildParams())
.withParameters("-o", outputFile.getAbsolutePath())
.withParameters(((GoApplicationRunningState)state).isDebug() ? new String[]{"-gcflags", "-N -l"} : ArrayUtil.EMPTY_STRING_ARRAY)
.withParameters(((GoApplicationRunningState)state).getTarget())
.disablePty()
.withPresentableName("go build")
.withProcessListener(historyProcessListener)
.withProcessListener(new ProcessAdapter() {
@Override
public void processTerminated(ProcessEvent event) {
super.processTerminated(event);
boolean compilationFailed = event.getExitCode() != 0;
if (((GoApplicationRunningState)state).isDebug()) {
buildingPromise.setResult(new MyDebugStarter(outputFile.getAbsolutePath(), historyProcessListener, compilationFailed));
}
else {
buildingPromise.setResult(new MyRunStarter(outputFile.getAbsolutePath(), historyProcessListener, compilationFailed));
}
}
}).executeWithProgress(false);
return buildingPromise;
}
@NotNull
private static File getOutputFile(@NotNull ExecutionEnvironment environment, @NotNull GoApplicationRunningState state)
throws ExecutionException {
File outputFile;
String outputDirectoryPath = state.getConfiguration().getOutputFilePath();
RunnerAndConfigurationSettings settings = environment.getRunnerAndConfigurationSettings();
String configurationName = settings != null ? settings.getName() : "application";
if (StringUtil.isEmpty(outputDirectoryPath)) {
try {
outputFile = FileUtil.createTempFile(configurationName, "go", true);
}
catch (IOException e) {
throw new ExecutionException("Cannot create temporary output file", e);
}
}
else {
File outputDirectory = new File(outputDirectoryPath);
if (outputDirectory.isDirectory() || !outputDirectory.exists() && outputDirectory.mkdirs()) {
outputFile = new File(outputDirectoryPath, GoEnvironmentUtil.getBinaryFileNameForPath(configurationName));
try {
if (!outputFile.exists() && !outputFile.createNewFile()) {
throw new ExecutionException("Cannot create output file " + outputFile.getAbsolutePath());
}
}
catch (IOException e) {
throw new ExecutionException("Cannot create output file " + outputFile.getAbsolutePath());
}
}
else {
throw new ExecutionException("Cannot create output file in " + outputDirectory.getAbsolutePath());
}
}
if (!prepareFile(outputFile)) {
throw new ExecutionException("Cannot make temporary file executable " + outputFile.getAbsolutePath());
}
return outputFile;
}
private static boolean prepareFile(@NotNull File file) {
try {
FileUtil.writeToFile(file, new byte[]{0x7F, 'E', 'L', 'F'});
}
catch (IOException e) {
return false;
}
return file.setExecutable(true);
}
private class MyDebugStarter extends RunProfileStarter {
private final String myOutputFilePath;
private final GoHistoryProcessListener myHistoryProcessListener;
private final boolean myCompilationFailed;
private MyDebugStarter(@NotNull String outputFilePath,
@NotNull GoHistoryProcessListener historyProcessListener,
boolean compilationFailed) {
myOutputFilePath = outputFilePath;
myHistoryProcessListener = historyProcessListener;
myCompilationFailed = compilationFailed;
}
@Nullable
@Override
public RunContentDescriptor execute(@NotNull RunProfileState state, @NotNull ExecutionEnvironment env)
throws ExecutionException {
if (state instanceof GoApplicationRunningState) {
int port = findFreePort();
FileDocumentManager.getInstance().saveAllDocuments();
((GoApplicationRunningState)state).setHistoryProcessHandler(myHistoryProcessListener);
((GoApplicationRunningState)state).setOutputFilePath(myOutputFilePath);
((GoApplicationRunningState)state).setDebugPort(port);
((GoApplicationRunningState)state).setCompilationFailed(myCompilationFailed);
// start debugger
ExecutionResult executionResult = state.execute(env.getExecutor(), GoBuildingRunner.this);
if (executionResult == null) {
throw new ExecutionException("Cannot run debugger");
}
UsageTrigger.trigger("go.dlv.debugger");
return XDebuggerManager.getInstance(env.getProject()).startSession(env, new XDebugProcessStarter() {
@NotNull
@Override
public XDebugProcess start(@NotNull XDebugSession session) throws ExecutionException {
RemoteVmConnection connection = new DlvRemoteVmConnection();
DlvDebugProcess process = new DlvDebugProcess(session, connection, executionResult);
connection.open(new InetSocketAddress(NetUtils.getLoopbackAddress(), port));
return process;
}
}).getRunContentDescriptor();
}
return null;
}
}
private class MyRunStarter extends RunProfileStarter {
private final String myOutputFilePath;
private final GoHistoryProcessListener myHistoryProcessListener;
private final boolean myCompilationFailed;
private MyRunStarter(@NotNull String outputFilePath,
@NotNull GoHistoryProcessListener historyProcessListener,
boolean compilationFailed) {
myOutputFilePath = outputFilePath;
myHistoryProcessListener = historyProcessListener;
myCompilationFailed = compilationFailed;
}
@Nullable
@Override
public RunContentDescriptor execute(@NotNull RunProfileState state, @NotNull ExecutionEnvironment env)
throws ExecutionException {
if (state instanceof GoApplicationRunningState) {
FileDocumentManager.getInstance().saveAllDocuments();
((GoApplicationRunningState)state).setHistoryProcessHandler(myHistoryProcessListener);
((GoApplicationRunningState)state).setOutputFilePath(myOutputFilePath);
((GoApplicationRunningState)state).setCompilationFailed(myCompilationFailed);
ExecutionResult executionResult = state.execute(env.getExecutor(), GoBuildingRunner.this);
return executionResult != null ? new RunContentBuilder(executionResult, env).showRunContent(env.getContentToReuse()) : null;
}
return null;
}
}
private static int findFreePort() {
try(ServerSocket socket = new ServerSocket(0)) {
socket.setReuseAddress(true);
return socket.getLocalPort();
}
catch (Exception ignore) {
}
throw new IllegalStateException("Could not find a free TCP/IP port to start dlv");
}
}