mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-02-05 08:06:56 +07:00
242 lines
9.5 KiB
Java
242 lines
9.5 KiB
Java
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
|
package com.jetbrains.env;
|
|
|
|
import com.intellij.execution.*;
|
|
import com.intellij.execution.executors.DefaultRunExecutor;
|
|
import com.intellij.execution.impl.ConsoleViewImpl;
|
|
import com.intellij.execution.impl.RunManagerImpl;
|
|
import com.intellij.execution.impl.RunnerAndConfigurationSettingsImpl;
|
|
import com.intellij.execution.process.ProcessAdapter;
|
|
import com.intellij.execution.process.ProcessEvent;
|
|
import com.intellij.execution.process.ProcessHandler;
|
|
import com.intellij.execution.process.ProcessListener;
|
|
import com.intellij.execution.runners.DefaultProgramRunnerKt;
|
|
import com.intellij.execution.runners.ExecutionEnvironment;
|
|
import com.intellij.execution.runners.ExecutionEnvironmentBuilder;
|
|
import com.intellij.execution.runners.ProgramRunner;
|
|
import com.intellij.execution.ui.RunContentDescriptor;
|
|
import com.intellij.openapi.application.ApplicationManager;
|
|
import com.intellij.openapi.application.ModalityState;
|
|
import com.intellij.openapi.application.WriteAction;
|
|
import com.intellij.openapi.project.Project;
|
|
import com.intellij.openapi.projectRoots.Sdk;
|
|
import com.jetbrains.python.run.AbstractPythonRunConfiguration;
|
|
import org.jetbrains.annotations.NotNull;
|
|
import org.jetbrains.annotations.Nullable;
|
|
import org.junit.Assert;
|
|
|
|
import java.io.IOException;
|
|
import java.util.ArrayList;
|
|
import java.util.Collections;
|
|
import java.util.List;
|
|
|
|
|
|
/**
|
|
* Runner to run python configurations. You only need to provide {@link PyConfigurationProducerForRunner} (see it's inheritors for useful defaults)
|
|
* Some methods are overridible to allow customization.
|
|
* <p/>
|
|
* This class allows configuration to <strong>rerun</strong> (using {@link #shouldRunAgain()},
|
|
* you need to implement {@link #getEnvironmentToRerun(RunContentDescriptor)}, accept last run descriptor and return
|
|
* {@link ExecutionEnvironment} to rerun (probably obtained from descriptor).
|
|
* <p/>
|
|
* It also has {@link #getAvailableRunnersForLastRun()} with list of runners that represents runner ids available for last run.
|
|
*
|
|
* @param <CONF_T> configuration class this runner supports
|
|
* @author Ilya.Kazakevich
|
|
*/
|
|
public abstract class ConfigurationBasedProcessRunner<CONF_T extends AbstractPythonRunConfiguration<?>>
|
|
extends ProcessWithConsoleRunner {
|
|
@NotNull
|
|
private final Class<CONF_T> myExpectedConfigurationType;
|
|
|
|
@NotNull
|
|
private final List<ProgramRunner<?>> myAvailableRunnersForLastRun = new ArrayList<>();
|
|
@NotNull
|
|
private final PyConfigurationProducerForRunner<CONF_T> myConfigurationProducer;
|
|
|
|
/**
|
|
* Environment to be used to run instead of factory. Used to rerun
|
|
*/
|
|
private ExecutionEnvironment myRerunExecutionEnvironment;
|
|
|
|
/**
|
|
* Process descriptor of last run
|
|
*/
|
|
protected RunContentDescriptor myLastProcessDescriptor;
|
|
|
|
|
|
protected ConfigurationBasedProcessRunner(
|
|
@NotNull Class<CONF_T> expectedConfigurationType,
|
|
@NotNull final PyConfigurationProducerForRunner<CONF_T> configurationProducer) {
|
|
myExpectedConfigurationType = expectedConfigurationType;
|
|
myConfigurationProducer = configurationProducer;
|
|
}
|
|
|
|
@Override
|
|
final void runProcess(@NotNull final String sdkPath,
|
|
@Nullable final Sdk sdk,
|
|
@NotNull final Project project,
|
|
@NotNull final ProcessListener processListener,
|
|
@NotNull final String tempWorkingPath)
|
|
throws ExecutionException {
|
|
ensureConsoleOk(myConsole);
|
|
|
|
// Do not create new environment from factory, if child provided environment to rerun
|
|
final ExecutionEnvironment executionEnvironment =
|
|
// TODO: RENAME
|
|
(myRerunExecutionEnvironment != null ? myRerunExecutionEnvironment : createExecutionEnvironment
|
|
(sdkPath, sdk, project, tempWorkingPath));
|
|
|
|
// Engine to be run after process end to post process console
|
|
ProcessListener consolePostprocessor = new ProcessAdapter() {
|
|
@Override
|
|
public void processTerminated(@NotNull final ProcessEvent event) {
|
|
super.processTerminated(event);
|
|
ApplicationManager.getApplication().invokeAndWait(() -> prepareConsoleAfterProcessEnd(), ModalityState.nonModal());
|
|
}
|
|
};
|
|
|
|
/// Find all available runners to report them to the test
|
|
myAvailableRunnersForLastRun.clear();
|
|
for (ProgramRunner<?> runner : ProgramRunner.PROGRAM_RUNNER_EP.getExtensions()) {
|
|
for (Executor executor : Executor.EXECUTOR_EXTENSION_NAME.getExtensions()) {
|
|
if (runner.canRun(executor.getId(), executionEnvironment.getRunProfile())) {
|
|
myAvailableRunnersForLastRun.add(runner);
|
|
}
|
|
}
|
|
}
|
|
ExecutionResult executionResult =
|
|
executionEnvironment.getState().execute(executionEnvironment.getExecutor(), executionEnvironment.getRunner());
|
|
ProcessHandler handler = executionResult.getProcessHandler();
|
|
|
|
handler.addProcessListener(new ProcessAdapter() {
|
|
});
|
|
RunContentDescriptor descriptor = DefaultProgramRunnerKt.showRunContent(executionResult, executionEnvironment);
|
|
handler.addProcessListener(processListener, project);
|
|
handler.addProcessListener(consolePostprocessor, project);
|
|
fetchConsoleAndSetToField(descriptor);
|
|
assert myConsole != null : "fetchConsoleAndSetToField did not set console!";
|
|
final var component = myConsole.getComponent(); // Console does not work without of this method
|
|
assert component != null;
|
|
myLastProcessDescriptor = descriptor;
|
|
handler.startNotify();
|
|
}
|
|
|
|
/**
|
|
* {@link PyProcessWithConsoleTestTask#createProcessRunner()} should always return new runner.
|
|
* But some buggy code returns runner from prev. rerun with stale project in console.
|
|
* This code checks it.
|
|
*/
|
|
private static void ensureConsoleOk(@Nullable final ConsoleViewImpl console) {
|
|
if (console == null) { // Not set yet
|
|
return;
|
|
}
|
|
if (console.getProject().isDisposed()) {
|
|
throw new AssertionError(
|
|
"=== Console is stale. Make sure you did not cache and reuse runner from prev. run. See this method doc for more info === ");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @return descriptor of last run
|
|
*/
|
|
@Nullable
|
|
public RunContentDescriptor getLastProcessDescriptor() {
|
|
return myLastProcessDescriptor;
|
|
}
|
|
|
|
@NotNull
|
|
private ExecutionEnvironment createExecutionEnvironment(@NotNull final String sdkPath,
|
|
@Nullable final Sdk sdk,
|
|
@NotNull final Project project,
|
|
@NotNull final String workingDir)
|
|
throws ExecutionException {
|
|
|
|
CONF_T configuration = myConfigurationProducer.createConfiguration(project, myExpectedConfigurationType);
|
|
RunnerAndConfigurationSettings settings =
|
|
new RunnerAndConfigurationSettingsImpl(RunManagerImpl.getInstanceImpl(project), configuration);
|
|
assert myExpectedConfigurationType.isInstance(configuration) :
|
|
String.format("Expected configuration %s, but got %s", myExpectedConfigurationType, configuration.getClass());
|
|
|
|
configuration.setSdkHome(sdkPath);
|
|
configuration.setSdk(sdk);
|
|
configuration.setWorkingDirectory(workingDir);
|
|
|
|
try {
|
|
WriteAction.run(() -> {
|
|
configurationCreatedAndWillLaunch(configuration);
|
|
|
|
RunManager runManager = RunManager.getInstance(project);
|
|
runManager.addConfiguration(settings);
|
|
runManager.setSelectedConfiguration(settings);
|
|
Assert.assertSame(settings, runManager.getSelectedConfiguration());
|
|
});
|
|
}
|
|
catch (IOException e) {
|
|
throw new ExecutionException(e);
|
|
}
|
|
|
|
|
|
// Execute
|
|
final Executor executor = DefaultRunExecutor.getRunExecutorInstance();
|
|
return ExecutionEnvironmentBuilder.create(executor, settings).build();
|
|
}
|
|
|
|
/**
|
|
* Prepares console to be tested after process end. Always call super if override.
|
|
*/
|
|
protected void prepareConsoleAfterProcessEnd() {
|
|
myConsole.flushDeferredText(); // Console may have deferred text, lets flush it
|
|
}
|
|
|
|
/**
|
|
* Fetches console from process descriptor and sets it to {@link #myConsole}.
|
|
* If override, always set console!
|
|
*
|
|
* @param descriptor process descriptor
|
|
*/
|
|
protected void fetchConsoleAndSetToField(@NotNull final RunContentDescriptor descriptor) {
|
|
myConsole = (ConsoleViewImpl)descriptor.getExecutionConsole();
|
|
}
|
|
|
|
/**
|
|
* Called when configuration is created. Used by runners to configure configuration before run.
|
|
* Always call parent when override.
|
|
*
|
|
* @param configuration configuration to configure
|
|
*/
|
|
protected void configurationCreatedAndWillLaunch(@NotNull final CONF_T configuration) throws IOException {
|
|
|
|
}
|
|
|
|
@Override
|
|
protected final boolean shouldRunAgain() {
|
|
final ExecutionEnvironment rerunEnvironment = getEnvironmentToRerun(myLastProcessDescriptor);
|
|
if (rerunEnvironment == null) {
|
|
return false;
|
|
}
|
|
myRerunExecutionEnvironment = rerunEnvironment;
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Fetches rerun environment from descriptor if any
|
|
*
|
|
* @param lastRunDescriptor descriptor of last process run
|
|
* @return environment to rerun or null if no rerun needed
|
|
*/
|
|
@Nullable
|
|
protected ExecutionEnvironment getEnvironmentToRerun(@NotNull final RunContentDescriptor lastRunDescriptor) {
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* @return list of runner ids which are allowed to run against current environment.
|
|
* Use it to check if desired runner (like debugger) exists
|
|
*/
|
|
@NotNull
|
|
public final List<ProgramRunner<?>> getAvailableRunnersForLastRun() {
|
|
return Collections.unmodifiableList(myAvailableRunnersForLastRun);
|
|
}
|
|
}
|