mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-04-19 04:51:24 +07:00
PY-40486 Introduce Python interpreter introspection procedure implementation based on Targets API
GitOrigin-RevId: da24a7bef8e812d8f5fefeebed7852bf3bb726c5
This commit is contained in:
committed by
intellij-monorepo-bot
parent
ec320a15a3
commit
180883b8e8
@@ -0,0 +1,168 @@
|
||||
package com.jetbrains.python.sdk.skeletons;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.intellij.execution.ExecutionException;
|
||||
import com.intellij.execution.configurations.GeneralCommandLine;
|
||||
import com.intellij.execution.process.CapturingProcessHandler;
|
||||
import com.intellij.execution.process.ProcessOutput;
|
||||
import com.intellij.openapi.projectRoots.Sdk;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.util.ArrayUtil;
|
||||
import com.intellij.util.Time;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.jetbrains.python.PythonHelpersLocator;
|
||||
import com.jetbrains.python.sdk.InvalidSdkException;
|
||||
import com.jetbrains.python.sdk.PySdkUtil;
|
||||
import com.jetbrains.python.sdk.PythonEnvUtil;
|
||||
import com.jetbrains.python.sdk.flavors.PythonSdkFlavor;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class PyLegacySkeletonGenerator extends PySkeletonGenerator {
|
||||
/**
|
||||
* @param skeletonPath path where skeletons should be generated
|
||||
* @param pySdk SDK
|
||||
* @param currentFolder current folder (some flavors may search for binary files there) or null if unknown
|
||||
*/
|
||||
public PyLegacySkeletonGenerator(String skeletonPath,
|
||||
@NotNull Sdk pySdk,
|
||||
@Nullable String currentFolder) {
|
||||
super(skeletonPath, pySdk, currentFolder);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public final Builder commandBuilder() {
|
||||
final Builder builder = new LegacyBuilder();
|
||||
if (myCurrentFolder != null) {
|
||||
builder.workingDir(myCurrentFolder);
|
||||
}
|
||||
return builder;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ensureSuccess throw {@link InvalidSdkException} containing additional diagnostic on process non-zero exit code.
|
||||
* You might want to disable it for commands where non-zero exit code is possible for situations other
|
||||
* than misconfigured interpreter or execution error in order to inspect the output manually.
|
||||
*/
|
||||
@NotNull
|
||||
protected ProcessOutput runProcess(@NotNull Builder builder, boolean ensureSuccess) throws InvalidSdkException {
|
||||
ProcessOutput output = builder.runProcess();
|
||||
if (ensureSuccess && output.getExitCode() != 0) {
|
||||
throw new InvalidSdkException(formatGeneratorFailureMessage(output));
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
// [targets-api] this should be left as-is because it deals with non-structured `commandLine`
|
||||
protected @NotNull ProcessOutput getProcessOutput(@Nullable String homePath,
|
||||
String @NotNull [] commandLine,
|
||||
@Nullable String stdin,
|
||||
@Nullable Map<String, String> extraEnv,
|
||||
int timeout) throws InvalidSdkException {
|
||||
final byte[] bytes = stdin != null ? stdin.getBytes(StandardCharsets.UTF_8) : null;
|
||||
return PySdkUtil.getProcessOutput(homePath, commandLine, extraEnv, timeout, bytes, true);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
protected ProcessOutput runProcessWithLineOutputListener(@NotNull String homePath,
|
||||
@NotNull List<String> cmd,
|
||||
@NotNull Map<String, String> env,
|
||||
@Nullable String stdin,
|
||||
int timeout,
|
||||
@NotNull LineWiseProcessOutputListener listener)
|
||||
throws ExecutionException, InvalidSdkException {
|
||||
final GeneralCommandLine commandLine = new GeneralCommandLine(cmd)
|
||||
.withWorkDirectory(homePath)
|
||||
.withEnvironment(env);
|
||||
final CapturingProcessHandler handler = new CapturingProcessHandler(commandLine);
|
||||
if (stdin != null) {
|
||||
sendLineToProcessInput(handler, stdin);
|
||||
}
|
||||
handler.addProcessListener(new LineWiseProcessOutputListener.Adapter(listener));
|
||||
return handler.runProcess(timeout);
|
||||
}
|
||||
|
||||
private final class LegacyBuilder extends Builder {
|
||||
|
||||
@NotNull
|
||||
public List<String> getCommandLine() {
|
||||
final List<String> commandLine = new ArrayList<>();
|
||||
commandLine.add(mySdk.getHomePath());
|
||||
commandLine.add(PythonHelpersLocator.getHelperPath(GENERATOR3));
|
||||
commandLine.add("-d");
|
||||
commandLine.add(mySkeletonsPath);
|
||||
if (!ContainerUtil.isEmpty(myAssemblyRefs)) {
|
||||
commandLine.add("-c");
|
||||
commandLine.add(StringUtil.join(myAssemblyRefs, ";"));
|
||||
}
|
||||
if (!ContainerUtil.isEmpty(myExtraSysPath)) {
|
||||
commandLine.add("-s");
|
||||
commandLine.add(StringUtil.join(myExtraSysPath, File.pathSeparator));
|
||||
}
|
||||
commandLine.addAll(myExtraArgs);
|
||||
if (StringUtil.isNotEmpty(myTargetModuleName)) {
|
||||
commandLine.add(myTargetModuleName);
|
||||
if (StringUtil.isNotEmpty(myTargetModulePath)) {
|
||||
commandLine.add(myTargetModulePath);
|
||||
}
|
||||
}
|
||||
return commandLine;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Map<String, String> getEnvironment() {
|
||||
Map<String, String> env = new HashMap<>();
|
||||
final PythonSdkFlavor flavor = PythonSdkFlavor.getFlavor(mySdk);
|
||||
final String flavorPathParam = flavor != null ? flavor.envPathParam() : null;
|
||||
// TODO Investigate whether it's possible to pass this directory as an ordinary "extraSysPath" entry
|
||||
if (myWorkingDir != null && flavorPathParam != null) {
|
||||
env = PySdkUtil.mergeEnvVariables(env, ImmutableMap.of(flavorPathParam, myWorkingDir));
|
||||
}
|
||||
env = PySdkUtil.mergeEnvVariables(env, PySdkUtil.activateVirtualEnv(mySdk));
|
||||
PythonEnvUtil.setPythonDontWriteBytecode(env);
|
||||
if (myPrebuilt) {
|
||||
env.put("IS_PREGENERATED_SKELETONS", "1");
|
||||
}
|
||||
return env;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getWorkingDir() throws InvalidSdkException {
|
||||
if (myWorkingDir != null) {
|
||||
return myWorkingDir;
|
||||
}
|
||||
final String binaryPath = mySdk.getHomePath();
|
||||
if (binaryPath == null) throw new InvalidSdkException("Broken home path for " + mySdk.getName());
|
||||
return new File(binaryPath).getParent();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public ProcessOutput runProcess() throws InvalidSdkException {
|
||||
return getProcessOutput(getWorkingDir(),
|
||||
ArrayUtil.toStringArray(getCommandLine()),
|
||||
getStdin(),
|
||||
getEnvironment(),
|
||||
getTimeout(Time.MINUTE * 10));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull ProcessOutput runProcessWithLineOutputListener(@NotNull LineWiseProcessOutputListener listener)
|
||||
throws InvalidSdkException, ExecutionException {
|
||||
return PyLegacySkeletonGenerator.this.runProcessWithLineOutputListener(getWorkingDir(),
|
||||
getCommandLine(),
|
||||
getEnvironment(),
|
||||
myStdin,
|
||||
getTimeout(Time.MINUTE * 20),
|
||||
listener);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,10 @@
|
||||
// Copyright 2000-2019 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.jetbrains.python.sdk.skeletons;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.gson.*;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import com.intellij.execution.ExecutionException;
|
||||
import com.intellij.execution.configurations.GeneralCommandLine;
|
||||
import com.intellij.execution.process.BaseProcessHandler;
|
||||
import com.intellij.execution.process.CapturingProcessHandler;
|
||||
import com.intellij.execution.process.ProcessOutput;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.progress.ProgressIndicator;
|
||||
@@ -16,22 +13,17 @@ import com.intellij.openapi.util.NlsSafe;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.openapi.vfs.LocalFileSystem;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.util.ArrayUtil;
|
||||
import com.intellij.util.Time;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.jetbrains.python.PySdkBundle;
|
||||
import com.jetbrains.python.PythonHelpersLocator;
|
||||
import com.jetbrains.python.sdk.InvalidSdkException;
|
||||
import com.jetbrains.python.sdk.PySdkUtil;
|
||||
import com.jetbrains.python.sdk.PythonEnvUtil;
|
||||
import com.jetbrains.python.sdk.flavors.PythonSdkFlavor;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* This class serves two purposes. First, it's a wrapper around "generator3" helper script
|
||||
@@ -83,7 +75,7 @@ import java.util.*;
|
||||
*
|
||||
* @see Builder
|
||||
*/
|
||||
public class PySkeletonGenerator {
|
||||
public abstract class PySkeletonGenerator {
|
||||
private static class Run {
|
||||
static final Logger LOG = Logger.getInstance(Run.class);
|
||||
}
|
||||
@@ -97,8 +89,8 @@ public class PySkeletonGenerator {
|
||||
private static final Gson ourGson = new GsonBuilder().create();
|
||||
|
||||
protected final Sdk mySdk;
|
||||
@Nullable private final String myCurrentFolder;
|
||||
private final String mySkeletonsPath;
|
||||
@Nullable protected final String myCurrentFolder;
|
||||
protected final String mySkeletonsPath;
|
||||
|
||||
/**
|
||||
* @param skeletonPath path where skeletons should be generated
|
||||
@@ -113,13 +105,7 @@ public class PySkeletonGenerator {
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public final Builder commandBuilder() {
|
||||
final Builder builder = new Builder();
|
||||
if (myCurrentFolder != null) {
|
||||
builder.workingDir(myCurrentFolder);
|
||||
}
|
||||
return builder;
|
||||
}
|
||||
public abstract Builder commandBuilder();
|
||||
|
||||
|
||||
/**
|
||||
@@ -127,18 +113,18 @@ public class PySkeletonGenerator {
|
||||
* allowing to additionally customize how it's going to be launched and performing the
|
||||
* default initialization before the run.
|
||||
*/
|
||||
public final class Builder {
|
||||
private final List<String> myExtraSysPath = new ArrayList<>();
|
||||
private final List<String> myAssemblyRefs = new ArrayList<>();
|
||||
private final List<String> myExtraArgs = new ArrayList<>();
|
||||
private String myWorkingDir;
|
||||
private String myTargetModuleName;
|
||||
private String myTargetModulePath;
|
||||
private boolean myPrebuilt = false;
|
||||
private int myTimeout;
|
||||
private String myStdin;
|
||||
public abstract class Builder {
|
||||
protected final List<String> myExtraSysPath = new ArrayList<>();
|
||||
protected final List<String> myAssemblyRefs = new ArrayList<>();
|
||||
protected final List<String> myExtraArgs = new ArrayList<>();
|
||||
protected String myWorkingDir;
|
||||
protected String myTargetModuleName;
|
||||
protected String myTargetModulePath;
|
||||
protected boolean myPrebuilt = false;
|
||||
protected int myTimeout;
|
||||
protected String myStdin;
|
||||
|
||||
private Builder() {
|
||||
protected Builder() {
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -200,78 +186,20 @@ public class PySkeletonGenerator {
|
||||
return myStdin;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public List<String> getCommandLine() {
|
||||
final List<String> commandLine = new ArrayList<>();
|
||||
commandLine.add(mySdk.getHomePath());
|
||||
commandLine.add(PythonHelpersLocator.getHelperPath(GENERATOR3));
|
||||
commandLine.add("-d");
|
||||
commandLine.add(mySkeletonsPath);
|
||||
if (!ContainerUtil.isEmpty(myAssemblyRefs)) {
|
||||
commandLine.add("-c");
|
||||
commandLine.add(StringUtil.join(myAssemblyRefs, ";"));
|
||||
}
|
||||
if (!ContainerUtil.isEmpty(myExtraSysPath)) {
|
||||
commandLine.add("-s");
|
||||
commandLine.add(StringUtil.join(myExtraSysPath, File.pathSeparator));
|
||||
}
|
||||
commandLine.addAll(myExtraArgs);
|
||||
if (StringUtil.isNotEmpty(myTargetModuleName)) {
|
||||
commandLine.add(myTargetModuleName);
|
||||
if (StringUtil.isNotEmpty(myTargetModulePath)) {
|
||||
commandLine.add(myTargetModulePath);
|
||||
}
|
||||
}
|
||||
return commandLine;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Map<String, String> getEnvironment() {
|
||||
Map<String, String> env = new HashMap<>();
|
||||
final PythonSdkFlavor flavor = PythonSdkFlavor.getFlavor(mySdk);
|
||||
final String flavorPathParam = flavor != null ? flavor.envPathParam() : null;
|
||||
// TODO Investigate whether it's possible to pass this directory as an ordinary "extraSysPath" entry
|
||||
if (myWorkingDir != null && flavorPathParam != null) {
|
||||
env = PySdkUtil.mergeEnvVariables(env, ImmutableMap.of(flavorPathParam, myWorkingDir));
|
||||
}
|
||||
env = PySdkUtil.mergeEnvVariables(env, PySdkUtil.activateVirtualEnv(mySdk));
|
||||
PythonEnvUtil.setPythonDontWriteBytecode(env);
|
||||
if (myPrebuilt) {
|
||||
env.put("IS_PREGENERATED_SKELETONS", "1");
|
||||
}
|
||||
return env;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getWorkingDir() throws InvalidSdkException {
|
||||
if (myWorkingDir != null) {
|
||||
return myWorkingDir;
|
||||
}
|
||||
final String binaryPath = mySdk.getHomePath();
|
||||
if (binaryPath == null) {
|
||||
throw new InvalidSdkException(PySdkBundle.message("python.skeleton.generator.broken.home.path", mySdk.getName()));
|
||||
}
|
||||
return new File(binaryPath).getParent();
|
||||
}
|
||||
|
||||
public int getTimeout(int defaultTimeout) {
|
||||
return myTimeout > 0 ? myTimeout : defaultTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ensureSuccess throw {@link InvalidSdkException} containing additional diagnostic on process non-zero exit code.
|
||||
* You might want to disable it for commands where non-zero exit code is possible for situations other
|
||||
* than misconfigured interpreter or execution error in order to inspect the output manually.
|
||||
*/
|
||||
@NotNull
|
||||
public ProcessOutput runProcess(boolean ensureSuccess) throws InvalidSdkException {
|
||||
return PySkeletonGenerator.this.runProcess(this, ensureSuccess);
|
||||
}
|
||||
public abstract ProcessOutput runProcess() throws InvalidSdkException;
|
||||
|
||||
@NotNull
|
||||
public List<GenerationResult> runGeneration(@Nullable ProgressIndicator indicator) throws InvalidSdkException, ExecutionException {
|
||||
return PySkeletonGenerator.this.runGeneration(this, indicator);
|
||||
}
|
||||
|
||||
public abstract @NotNull ProcessOutput runProcessWithLineOutputListener(@NotNull LineWiseProcessOutputListener listener)
|
||||
throws InvalidSdkException, ExecutionException;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -337,31 +265,13 @@ public class PySkeletonGenerator {
|
||||
}
|
||||
};
|
||||
|
||||
final ProcessOutput output = runProcessWithLineOutputListener(builder.getWorkingDir(),
|
||||
builder.getCommandLine(),
|
||||
builder.getEnvironment(),
|
||||
builder.myStdin,
|
||||
builder.getTimeout(Time.MINUTE * 20),
|
||||
listener);
|
||||
final ProcessOutput output = builder.runProcessWithLineOutputListener(listener);
|
||||
if (output.getExitCode() != 0) {
|
||||
throw new InvalidSdkException(formatGeneratorFailureMessage(output));
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
protected ProcessOutput runProcess(@NotNull Builder builder, boolean ensureSuccess) throws InvalidSdkException {
|
||||
final ProcessOutput output = getProcessOutput(builder.getWorkingDir(),
|
||||
ArrayUtil.toStringArray(builder.getCommandLine()),
|
||||
builder.getStdin(),
|
||||
builder.getEnvironment(),
|
||||
builder.getTimeout(Time.MINUTE * 10));
|
||||
if (ensureSuccess && output.getExitCode() != 0) {
|
||||
throw new InvalidSdkException(formatGeneratorFailureMessage(output));
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
public void finishSkeletonsGeneration() {
|
||||
}
|
||||
|
||||
@@ -369,25 +279,6 @@ public class PySkeletonGenerator {
|
||||
return new File(name).exists();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
protected ProcessOutput runProcessWithLineOutputListener(@NotNull String homePath,
|
||||
@NotNull List<String> cmd,
|
||||
@NotNull Map<String, String> env,
|
||||
@Nullable String stdin,
|
||||
int timeout,
|
||||
@NotNull LineWiseProcessOutputListener listener)
|
||||
throws ExecutionException, InvalidSdkException {
|
||||
final GeneralCommandLine commandLine = new GeneralCommandLine(cmd)
|
||||
.withWorkDirectory(homePath)
|
||||
.withEnvironment(env);
|
||||
final CapturingProcessHandler handler = new CapturingProcessHandler(commandLine);
|
||||
if (stdin != null) {
|
||||
sendLineToProcessInput(handler, stdin);
|
||||
}
|
||||
handler.addProcessListener(new LineWiseProcessOutputListener.Adapter(listener));
|
||||
return handler.runProcess(timeout);
|
||||
}
|
||||
|
||||
public String getSkeletonsPath() {
|
||||
return mySkeletonsPath;
|
||||
}
|
||||
@@ -395,17 +286,8 @@ public class PySkeletonGenerator {
|
||||
public void prepare() {
|
||||
}
|
||||
|
||||
protected @NotNull ProcessOutput getProcessOutput(@Nullable String homePath,
|
||||
String @NotNull [] commandLine,
|
||||
@Nullable String stdin,
|
||||
@Nullable Map<String, String> extraEnv,
|
||||
int timeout) throws InvalidSdkException {
|
||||
final byte[] bytes = stdin != null ? stdin.getBytes(StandardCharsets.UTF_8) : null;
|
||||
return PySdkUtil.getProcessOutput(homePath, commandLine, extraEnv, timeout, bytes, true);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private @NlsSafe String formatGeneratorFailureMessage(@NotNull ProcessOutput process) {
|
||||
protected final @NlsSafe String formatGeneratorFailureMessage(@NotNull ProcessOutput process) {
|
||||
final StringBuilder sb = new StringBuilder("failed to run ").append(GENERATOR3).append(" for ").append(mySdk.getHomePath());
|
||||
if (process.isTimeout()) {
|
||||
sb.append(": timed out.");
|
||||
|
||||
Reference in New Issue
Block a user