blob: 7e099041e16acb3a59f838ebfa5240b4ffef4e98 [file] [log] [blame]
/*
* Copyright 2000-2013 JetBrains s.r.o.
*
* 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.jetbrains.python.sdk.skeletons;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.intellij.execution.process.ProcessOutput;
import com.intellij.openapi.application.ex.ApplicationManagerEx;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.projectRoots.Sdk;
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.Consumer;
import com.jetbrains.python.PythonHelpersLocator;
import com.jetbrains.python.sdk.InvalidSdkException;
import com.jetbrains.python.sdk.PySdkUtil;
import com.jetbrains.python.sdk.PythonSdkType;
import com.jetbrains.python.sdk.flavors.IronPythonSdkFlavor;
import com.jetbrains.python.sdk.flavors.PythonSdkFlavor;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.util.*;
import static com.jetbrains.python.sdk.skeletons.SkeletonVersionChecker.fromVersionString;
/**
* @author traff
*/
public class PySkeletonGenerator {
// Some flavors need current folder to be passed as param. Here are they.
private static final Map<Class<? extends PythonSdkFlavor>, String> ENV_PATH_PARAM =
new HashMap<Class<? extends PythonSdkFlavor>, String>();
static {
ENV_PATH_PARAM.put(IronPythonSdkFlavor.class, "IRONPYTHONPATH"); // TODO: Make strategy and move to PythonSdkFlavor?
}
protected static final Logger LOG = Logger.getInstance("#" + PySkeletonGenerator.class.getName());
protected static final int MINUTE = 60 * 1000;
protected static final String GENERATOR3 = "generator3.py";
private final String mySkeletonsPath;
@NotNull private final Map<String, String> myEnv;
public void finishSkeletonsGeneration() {
}
public boolean exists(String name) {
return new File(name).exists();
}
public static class ListBinariesResult {
public final int generatorVersion;
public final Map<String, PySkeletonRefresher.PyBinaryItem> modules;
public ListBinariesResult(int generatorVersion, Map<String, PySkeletonRefresher.PyBinaryItem> modules) {
this.generatorVersion = generatorVersion;
this.modules = modules;
}
}
/**
* @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 PySkeletonGenerator(String skeletonPath, @NotNull final Sdk pySdk, @Nullable final String currentFolder) {
mySkeletonsPath = skeletonPath;
final PythonSdkFlavor flavor = PythonSdkFlavor.getFlavor(pySdk);
if (currentFolder != null && flavor != null && ENV_PATH_PARAM.containsKey(flavor.getClass())) {
myEnv = ImmutableMap.of(ENV_PATH_PARAM.get(flavor.getClass()), currentFolder);
}
else {
myEnv = Collections.emptyMap();
}
}
public String getSkeletonsPath() {
return mySkeletonsPath;
}
public void prepare() {
}
protected void generateSkeleton(String modname,
String modfilename,
List<String> assemblyRefs,
String syspath,
String sdkHomePath,
Consumer<Boolean> resultConsumer)
throws InvalidSdkException {
final ProcessOutput genResult = runSkeletonGeneration(modname, modfilename, assemblyRefs, sdkHomePath,
syspath);
if (genResult.getStderrLines().size() > 0) {
StringBuilder sb = new StringBuilder("Skeleton for ");
sb.append(modname);
if (genResult.getExitCode() != 0) {
sb.append(" failed on ");
}
else {
sb.append(" had some minor errors on ");
}
sb.append(sdkHomePath).append(". stderr: --\n");
for (String err_line : genResult.getStderrLines()) {
sb.append(err_line).append("\n");
}
sb.append("--");
if (ApplicationManagerEx.getApplicationEx().isInternal()) {
LOG.warn(sb.toString());
}
else {
LOG.info(sb.toString());
}
}
resultConsumer.consume(genResult.getExitCode() == 0);
}
public ProcessOutput runSkeletonGeneration(String modname,
String modfilename,
List<String> assemblyRefs,
String binaryPath, String extraSyspath)
throws InvalidSdkException {
final String parent_dir = new File(binaryPath).getParent();
List<String> commandLine = new ArrayList<String>();
commandLine.add(binaryPath);
commandLine.add(PythonHelpersLocator.getHelperPath(GENERATOR3));
commandLine.add("-d");
commandLine.add(getSkeletonsPath());
if (assemblyRefs != null && !assemblyRefs.isEmpty()) {
commandLine.add("-c");
commandLine.add(StringUtil.join(assemblyRefs, ";"));
}
if (ApplicationManagerEx.getApplicationEx().isInternal()) {
commandLine.add("-x");
}
if (!StringUtil.isEmpty(extraSyspath)) {
commandLine.add("-s");
commandLine.add(extraSyspath);
}
commandLine.add(modname);
if (modfilename != null) {
commandLine.add(modfilename);
}
final Map<String, String> extraEnv = PythonSdkType.getVirtualEnvExtraEnv(binaryPath);
final Map<String, String> env = extraEnv != null ? PySdkUtil.mergeEnvVariables(myEnv, extraEnv) : myEnv;
return getProcessOutput(parent_dir, ArrayUtil.toStringArray(commandLine), env, MINUTE * 10);
}
protected ProcessOutput getProcessOutput(String homePath, String[] commandLine, Map<String, String> extraEnv,
int timeout) throws InvalidSdkException {
return PySdkUtil.getProcessOutput(
homePath,
commandLine,
extraEnv,
timeout
);
}
public void generateBuiltinSkeletons(@NotNull Sdk sdk) throws InvalidSdkException {
//noinspection ResultOfMethodCallIgnored
new File(mySkeletonsPath).mkdirs();
String binaryPath = sdk.getHomePath();
if (binaryPath == null) throw new InvalidSdkException("Broken home path for " + sdk.getName());
long startTime = System.currentTimeMillis();
final ProcessOutput runResult = getProcessOutput(
new File(binaryPath).getParent(),
new String[]{
binaryPath,
PythonHelpersLocator.getHelperPath(GENERATOR3),
"-d", mySkeletonsPath, // output dir
"-b", // for builtins
},
PythonSdkType.getVirtualEnvExtraEnv(binaryPath), MINUTE * 5
);
runResult.checkSuccess(LOG);
LOG.info("Rebuilding builtin skeletons took " + (System.currentTimeMillis() - startTime) + " ms");
}
@NotNull
public ListBinariesResult listBinaries(@NotNull Sdk sdk, @NotNull String extraSysPath) throws InvalidSdkException {
final String homePath = sdk.getHomePath();
final long startTime = System.currentTimeMillis();
if (homePath == null) throw new InvalidSdkException("Broken home path for " + sdk.getName());
final String parentDir = new File(homePath).getParent();
List<String> cmd = new ArrayList<String>(Arrays.asList(homePath, PythonHelpersLocator.getHelperPath(GENERATOR3), "-v", "-L"));
if (!StringUtil.isEmpty(extraSysPath)) {
cmd.add("-s");
cmd.add(extraSysPath);
}
final ProcessOutput process = getProcessOutput(parentDir,
ArrayUtil.toStringArray(cmd),
PythonSdkType.getVirtualEnvExtraEnv(homePath),
MINUTE * 4); // see PY-3898
LOG.info("Retrieving binary module list took " + (System.currentTimeMillis() - startTime) + " ms");
if (process.getExitCode() != 0) {
final StringBuilder sb = new StringBuilder("failed to run ").append(GENERATOR3).append(" for ").append(homePath);
if (process.isTimeout()) {
sb.append(": timed out.");
}
else {
sb.append(", exit code ")
.append(process.getExitCode())
.append(", stderr: \n-----\n");
for (String line : process.getStderrLines()) {
sb.append(line).append("\n");
}
sb.append("-----");
}
throw new InvalidSdkException(sb.toString());
}
final List<String> lines = process.getStdoutLines();
if (lines.size() < 1) {
throw new InvalidSdkException("Empty output from " + GENERATOR3 + " for " + homePath);
}
final Iterator<String> iter = lines.iterator();
final int generatorVersion = fromVersionString(iter.next().trim());
final Map<String, PySkeletonRefresher.PyBinaryItem> binaries = Maps.newHashMap();
while (iter.hasNext()) {
final String line = iter.next();
int cutpos = line.indexOf('\t');
if (cutpos >= 0) {
String[] strs = line.split("\t");
String moduleName = strs[0];
String path = strs[1];
int length = Integer.parseInt(strs[2]);
int lastModified = Integer.parseInt(strs[3]);
binaries.put(moduleName, new PySkeletonRefresher.PyBinaryItem(moduleName, path, length, lastModified));
}
else {
LOG.error("Bad binaries line: '" + line + "', SDK " + homePath); // but don't die yet
}
}
return new ListBinariesResult(generatorVersion, binaries);
}
public boolean deleteOrLog(@NotNull File item) {
boolean deleted = item.delete();
if (!deleted) LOG.warn("Failed to delete skeleton file " + item.getAbsolutePath());
return deleted;
}
public void refreshGeneratedSkeletons() {
VirtualFile skeletonsVFile = LocalFileSystem.getInstance().refreshAndFindFileByPath(getSkeletonsPath());
assert skeletonsVFile != null;
skeletonsVFile.refresh(false, true);
}
}