blob: b1e8ca19acf8bcdc9fea3dcf3405404c1fe7c6d8 [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.base.Joiner;
import com.google.common.collect.Lists;
import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer;
import com.intellij.execution.ExecutionException;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.PathManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.roots.OrderRootType;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.JarFileSystem;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.Consumer;
import com.intellij.util.Function;
import com.intellij.util.SmartList;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.io.ZipUtil;
import com.jetbrains.python.PyBundle;
import com.jetbrains.python.PyNames;
import com.jetbrains.python.codeInsight.userSkeletons.PyUserSkeletonsUtil;
import com.jetbrains.python.packaging.PyExternalProcessException;
import com.jetbrains.python.packaging.PyPackageManager;
import com.jetbrains.python.psi.resolve.PythonSdkPathCache;
import com.jetbrains.python.remote.PythonRemoteInterpreterManager;
import com.jetbrains.python.sdk.InvalidSdkException;
import com.jetbrains.python.sdk.PySdkUtil;
import com.jetbrains.python.sdk.PythonSdkType;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.awt.*;
import java.io.*;
import java.util.*;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static com.jetbrains.python.sdk.skeletons.SkeletonVersionChecker.fromVersionString;
/**
* Handles a refresh of SDK's skeletons.
* Does all the heavy lifting calling skeleton generator, managing blacklists, etc.
* One-time, non-reusable instances.
* <br/>
* User: dcheryasov
* Date: 4/15/11 5:38 PM
*/
public class PySkeletonRefresher {
private static final Logger LOG = Logger.getInstance("#" + PySkeletonRefresher.class.getName());
@Nullable private Project myProject;
private @Nullable final ProgressIndicator myIndicator;
@NotNull private final Sdk mySdk;
private String mySkeletonsPath;
@NonNls public static final String BLACKLIST_FILE_NAME = ".blacklist";
private final static Pattern BLACKLIST_LINE = Pattern.compile("^([^=]+) = (\\d+\\.\\d+) (\\d+)\\s*$");
// we use the equals sign after filename so that we can freely include space in the filename
// Path (the first component) may contain spaces, this header spec is deprecated
private static final Pattern VERSION_LINE_V1 = Pattern.compile("# from (\\S+) by generator (\\S+)\\s*");
// Skeleton header spec v2
private static final Pattern FROM_LINE_V2 = Pattern.compile("# from (.*)$");
private static final Pattern BY_LINE_V2 = Pattern.compile("# by generator (.*)$");
private static int ourGeneratingCount = 0;
private String myExtraSyspath;
private VirtualFile myPregeneratedSkeletons;
private int myGeneratorVersion;
private Map<String, Pair<Integer, Long>> myBlacklist;
private SkeletonVersionChecker myVersionChecker;
private PySkeletonGenerator mySkeletonsGenerator;
public static synchronized boolean isGeneratingSkeletons() {
return ourGeneratingCount > 0;
}
private static synchronized void changeGeneratingSkeletons(int increment) {
ourGeneratingCount += increment;
}
public static void refreshSkeletonsOfSdk(@Nullable Project project,
Component ownerComponent,
String skeletonsPath,
@NotNull Sdk sdk)
throws InvalidSdkException {
final Map<String, List<String>> errors = new TreeMap<String, List<String>>();
final List<String> failedSdks = new SmartList<String>();
final ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator();
final String homePath = sdk.getHomePath();
if (skeletonsPath == null) {
LOG.info("Could not find skeletons path for SDK path " + homePath);
}
else {
LOG.info("Refreshing skeletons for " + homePath);
SkeletonVersionChecker checker = new SkeletonVersionChecker(0); // this default version won't be used
final PySkeletonRefresher refresher = new PySkeletonRefresher(project, ownerComponent, sdk, skeletonsPath, indicator, null);
changeGeneratingSkeletons(1);
try {
List<String> sdkErrors = refresher.regenerateSkeletons(checker);
if (sdkErrors.size() > 0) {
String sdkName = sdk.getName();
List<String> knownErrors = errors.get(sdkName);
if (knownErrors == null) {
errors.put(sdkName, sdkErrors);
}
else {
knownErrors.addAll(sdkErrors);
}
}
}
finally {
changeGeneratingSkeletons(-1);
}
}
if (failedSdks.size() > 0 || errors.size() > 0) {
int module_errors = 0;
for (String sdk_name : errors.keySet()) module_errors += errors.get(sdk_name).size();
String message;
if (failedSdks.size() > 0) {
message = PyBundle.message("sdk.errorlog.$0.mods.fail.in.$1.sdks.$2.completely", module_errors, errors.size(), failedSdks.size());
}
else {
message = PyBundle.message("sdk.errorlog.$0.mods.fail.in.$1.sdks", module_errors, errors.size());
}
logErrors(errors, failedSdks, message);
}
}
private static void logErrors(@NotNull final Map<String, List<String>> errors, @NotNull final List<String> failedSdks,
@NotNull final String message) {
LOG.warn(PyBundle.message("sdk.some.skeletons.failed"));
LOG.warn(message);
if (failedSdks.size() > 0) {
LOG.warn(PyBundle.message("sdk.error.dialog.failed.sdks"));
LOG.warn(StringUtil.join(failedSdks, ", "));
}
if (errors.size() > 0) {
LOG.warn(PyBundle.message("sdk.error.dialog.failed.modules"));
for (String sdkName : errors.keySet()) {
for (String moduleName : errors.get(sdkName)) {
LOG.warn(moduleName);
}
}
}
}
/**
* Creates a new object that refreshes skeletons of given SDK.
*
* @param sdk a Python SDK
* @param skeletonsPath if known; null means 'determine and create as needed'.
* @param indicator to report progress of long operations
*/
public PySkeletonRefresher(@Nullable Project project,
@Nullable Component ownerComponent,
@NotNull Sdk sdk,
@Nullable String skeletonsPath,
@Nullable ProgressIndicator indicator,
@Nullable String folder)
throws InvalidSdkException {
myProject = project;
myIndicator = indicator;
mySdk = sdk;
mySkeletonsPath = skeletonsPath;
final PythonRemoteInterpreterManager remoteInterpreterManager = PythonRemoteInterpreterManager.getInstance();
if (PySdkUtil.isRemote(sdk) && remoteInterpreterManager != null) {
try {
mySkeletonsGenerator = remoteInterpreterManager.createRemoteSkeletonGenerator(myProject, ownerComponent, sdk, getSkeletonsPath());
}
catch (ExecutionException e) {
throw new InvalidSdkException(e.getMessage(), e.getCause());
}
}
else {
mySkeletonsGenerator = new PySkeletonGenerator(getSkeletonsPath(), mySdk, folder);
}
}
private void indicate(String msg) {
if (myIndicator != null) {
myIndicator.checkCanceled();
myIndicator.setText(msg);
myIndicator.setText2("");
}
}
private void indicateMinor(String msg) {
if (myIndicator != null) {
myIndicator.setText2(msg);
}
}
private void checkCanceled() {
if (myIndicator != null) {
myIndicator.checkCanceled();
}
}
private static String calculateExtraSysPath(@NotNull final Sdk sdk, @Nullable final String skeletonsPath) {
final File skeletons = skeletonsPath != null ? new File(skeletonsPath) : null;
final VirtualFile userSkeletonsDir = PyUserSkeletonsUtil.getUserSkeletonsDirectory();
final File userSkeletons = userSkeletonsDir != null ? new File(userSkeletonsDir.getPath()) : null;
final VirtualFile remoteSourcesDir = PySdkUtil.findAnyRemoteLibrary(sdk);
final File remoteSources = remoteSourcesDir != null ? new File(remoteSourcesDir.getPath()) : null;
final VirtualFile[] classDirs = sdk.getRootProvider().getFiles(OrderRootType.CLASSES);
return Joiner.on(File.pathSeparator).join(ContainerUtil.mapNotNull(classDirs, new Function<VirtualFile, Object>() {
@Override
public Object fun(VirtualFile file) {
if (file.isInLocalFileSystem()) {
// We compare canonical files, not strings because "c:/some/folder" equals "c:\\some\\bin\\..\\folder\\"
final File canonicalFile = new File(file.getPath());
if (canonicalFile.exists() &&
!FileUtil.filesEqual(canonicalFile, skeletons) &&
!FileUtil.filesEqual(canonicalFile, userSkeletons) &&
!FileUtil.filesEqual(canonicalFile, remoteSources)) {
return file.getPath();
}
}
return null;
}
}));
}
/**
* Creates if needed all path(s) used to store skeletons of its SDK.
*
* @return path name of skeleton dir for the SDK, guaranteed to be already created.
*/
@NotNull
public String getSkeletonsPath() throws InvalidSdkException {
if (mySkeletonsPath == null) {
mySkeletonsPath = PythonSdkType.getSkeletonsPath(PathManager.getSystemPath(), mySdk.getHomePath());
final File skeletonsDir = new File(mySkeletonsPath);
if (!skeletonsDir.exists() && !skeletonsDir.mkdirs()) {
throw new InvalidSdkException("Can't create skeleton dir " + String.valueOf(mySkeletonsPath));
}
}
return mySkeletonsPath;
}
public List<String> regenerateSkeletons(@Nullable SkeletonVersionChecker cachedChecker) throws InvalidSdkException {
final List<String> errorList = new SmartList<String>();
final String homePath = mySdk.getHomePath();
final String skeletonsPath = getSkeletonsPath();
final File skeletonsDir = new File(skeletonsPath);
if (!skeletonsDir.exists()) {
//noinspection ResultOfMethodCallIgnored
skeletonsDir.mkdirs();
}
final String readablePath = FileUtil.getLocationRelativeToUserHome(homePath);
mySkeletonsGenerator.prepare();
myBlacklist = loadBlacklist();
indicate(PyBundle.message("sdk.gen.querying.$0", readablePath));
// get generator version and binary libs list in one go
final String extraSysPath = calculateExtraSysPath(mySdk, getSkeletonsPath());
final PySkeletonGenerator.ListBinariesResult binaries = mySkeletonsGenerator.listBinaries(mySdk, extraSysPath);
myGeneratorVersion = binaries.generatorVersion;
myPregeneratedSkeletons = findPregeneratedSkeletons();
indicate(PyBundle.message("sdk.gen.reading.versions.file"));
if (cachedChecker != null) {
myVersionChecker = cachedChecker.withDefaultVersionIfUnknown(myGeneratorVersion);
}
else {
myVersionChecker = new SkeletonVersionChecker(myGeneratorVersion);
}
// check builtins
final String builtinsFileName = PythonSdkType.getBuiltinsFileName(mySdk);
final File builtinsFile = new File(skeletonsPath, builtinsFileName);
final SkeletonHeader oldHeader = readSkeletonHeader(builtinsFile);
final boolean oldOrNonExisting = oldHeader == null || oldHeader.getVersion() == 0;
if (myPregeneratedSkeletons != null && oldOrNonExisting) {
unpackPreGeneratedSkeletons();
}
if (oldOrNonExisting) {
copyBaseSdkSkeletonsToVirtualEnv(skeletonsPath, binaries);
}
final boolean builtinsUpdated = updateSkeletonsForBuiltins(readablePath, builtinsFile);
if (!binaries.modules.isEmpty()) {
indicate(PyBundle.message("sdk.gen.updating.$0", readablePath));
final List<UpdateResult> updateErrors = updateOrCreateSkeletons(binaries.modules);
if (updateErrors.size() > 0) {
indicateMinor(BLACKLIST_FILE_NAME);
for (UpdateResult error : updateErrors) {
if (error.isFresh()) errorList.add(error.getName());
myBlacklist.put(error.getPath(), new Pair<Integer, Long>(myGeneratorVersion, error.getTimestamp()));
}
storeBlacklist(skeletonsDir, myBlacklist);
}
else {
removeBlacklist(skeletonsDir);
}
}
indicate(PyBundle.message("sdk.gen.reloading"));
mySkeletonsGenerator.refreshGeneratedSkeletons();
if (!oldOrNonExisting) {
indicate(PyBundle.message("sdk.gen.cleaning.$0", readablePath));
cleanUpSkeletons(skeletonsDir);
}
if (PySdkUtil.isRemote(mySdk)) {
try {
// Force loading packages
PyPackageManager.getInstance(mySdk).getPackages(false);
}
catch (PyExternalProcessException e) {
// ignore - already logged
}
}
if ((builtinsUpdated || PySdkUtil.isRemote(mySdk)) && myProject != null) {
ApplicationManager.getApplication().invokeLater(new Runnable() {
@Override
public void run() {
DaemonCodeAnalyzer.getInstance(myProject).restart();
}
}, myProject.getDisposed());
}
return errorList;
}
private boolean updateSkeletonsForBuiltins(String readablePath, File builtinsFile) throws InvalidSdkException {
final SkeletonHeader newHeader = readSkeletonHeader(builtinsFile);
final boolean mustUpdateBuiltins = myPregeneratedSkeletons == null &&
(newHeader == null || newHeader.getVersion() < myVersionChecker.getBuiltinVersion());
if (mustUpdateBuiltins) {
indicate(PyBundle.message("sdk.gen.updating.builtins.$0", readablePath));
mySkeletonsGenerator.generateBuiltinSkeletons(mySdk);
if (myProject != null) {
PythonSdkPathCache.getInstance(myProject, mySdk).clearBuiltins();
}
}
return mustUpdateBuiltins;
}
private void copyBaseSdkSkeletonsToVirtualEnv(String skeletonsPath, PySkeletonGenerator.ListBinariesResult binaries)
throws InvalidSdkException {
final Sdk base = PythonSdkType.getInstance().getVirtualEnvBaseSdk(mySdk);
if (base != null) {
indicate("Copying base SDK skeletons for virtualenv...");
final String baseSkeletonsPath = PythonSdkType.getSkeletonsPath(PathManager.getSystemPath(), base.getHomePath());
final PySkeletonGenerator.ListBinariesResult baseBinaries =
mySkeletonsGenerator.listBinaries(base, calculateExtraSysPath(base, baseSkeletonsPath));
for (Map.Entry<String, PyBinaryItem> entry : binaries.modules.entrySet()) {
final String module = entry.getKey();
final PyBinaryItem binary = entry.getValue();
final PyBinaryItem baseBinary = baseBinaries.modules.get(module);
final File fromFile = getSkeleton(module, baseSkeletonsPath);
if (baseBinaries.modules.containsKey(module) &&
fromFile.exists() &&
binary.length() == baseBinary.length()) { // Weak binary modules equality check
final File toFile = fromFile.isDirectory() ?
getPackageSkeleton(module, skeletonsPath) :
getModuleSkeleton(module, skeletonsPath);
try {
FileUtil.copy(fromFile, toFile);
}
catch (IOException e) {
LOG.info("Error copying base virtualenv SDK skeleton for " + module, e);
}
}
}
}
}
private void unpackPreGeneratedSkeletons() throws InvalidSdkException {
indicate("Unpacking pregenerated skeletons...");
try {
final VirtualFile jar = JarFileSystem.getInstance().getVirtualFileForJar(myPregeneratedSkeletons);
if (jar != null) {
ZipUtil.extract(new File(jar.getPath()),
new File(getSkeletonsPath()), null);
}
}
catch (IOException e) {
LOG.info("Error unpacking pregenerated skeletons", e);
}
}
@Nullable
public static SkeletonHeader readSkeletonHeader(@NotNull File file) {
try {
final LineNumberReader reader = new LineNumberReader(new FileReader(file));
try {
String line = null;
// Read 3 lines, skip first 2: encoding, module name
for (int i = 0; i < 3; i++) {
line = reader.readLine();
if (line == null) {
return null;
}
}
// Try the old whitespace-unsafe header format v1 first
final Matcher v1Matcher = VERSION_LINE_V1.matcher(line);
if (v1Matcher.matches()) {
return new SkeletonHeader(v1Matcher.group(1), fromVersionString(v1Matcher.group(2)));
}
final Matcher fromMatcher = FROM_LINE_V2.matcher(line);
if (fromMatcher.matches()) {
final String binaryFile = fromMatcher.group(1);
line = reader.readLine();
if (line != null) {
final Matcher byMatcher = BY_LINE_V2.matcher(line);
if (byMatcher.matches()) {
final int version = fromVersionString(byMatcher.group(1));
return new SkeletonHeader(binaryFile, version);
}
}
}
}
finally {
reader.close();
}
}
catch (IOException ignored) {
}
return null;
}
public static class SkeletonHeader {
@NotNull private final String myFile;
private final int myVersion;
public SkeletonHeader(@NotNull String binaryFile, int version) {
myFile = binaryFile;
myVersion = version;
}
@NotNull
public String getBinaryFile() {
return myFile;
}
public int getVersion() {
return myVersion;
}
}
private Map<String, Pair<Integer, Long>> loadBlacklist() {
Map<String, Pair<Integer, Long>> ret = new HashMap<String, Pair<Integer, Long>>();
File blacklistFile = new File(mySkeletonsPath, BLACKLIST_FILE_NAME);
if (blacklistFile.exists() && blacklistFile.canRead()) {
Reader input;
try {
input = new FileReader(blacklistFile);
LineNumberReader lines = new LineNumberReader(input);
try {
String line;
do {
line = lines.readLine();
if (line != null && line.length() > 0 && line.charAt(0) != '#') { // '#' begins a comment
Matcher matcher = BLACKLIST_LINE.matcher(line);
boolean notParsed = true;
if (matcher.matches()) {
final int version = fromVersionString(matcher.group(2));
if (version > 0) {
try {
final long timestamp = Long.parseLong(matcher.group(3));
final String filename = matcher.group(1);
ret.put(filename, new Pair<Integer, Long>(version, timestamp));
notParsed = false;
}
catch (NumberFormatException ignore) {
}
}
}
if (notParsed) LOG.warn("In blacklist at " + mySkeletonsPath + " strange line '" + line + "'");
}
}
while (line != null);
}
catch (IOException ex) {
LOG.warn("Failed to read blacklist in " + mySkeletonsPath, ex);
}
finally {
lines.close();
}
}
catch (IOException ignore) {
}
}
return ret;
}
private static void storeBlacklist(File skeletonDir, Map<String, Pair<Integer, Long>> blacklist) {
File blacklistFile = new File(skeletonDir, BLACKLIST_FILE_NAME);
PrintWriter output;
try {
output = new PrintWriter(blacklistFile);
try {
output.println("# PyCharm failed to generate skeletons for these modules.");
output.println("# These skeletons will be re-generated automatically");
output.println("# when a newer module version or an updated generator becomes available.");
// each line: filename = version.string timestamp
for (String fname : blacklist.keySet()) {
Pair<Integer, Long> data = blacklist.get(fname);
output.print(fname);
output.print(" = ");
output.print(SkeletonVersionChecker.toVersionString(data.getFirst()));
output.print(" ");
output.print(data.getSecond());
output.println();
}
}
finally {
output.close();
}
}
catch (IOException ex) {
LOG.warn("Failed to store blacklist in " + skeletonDir.getPath(), ex);
}
}
private static void removeBlacklist(File skeletonDir) {
File blacklistFile = new File(skeletonDir, BLACKLIST_FILE_NAME);
if (blacklistFile.exists()) {
boolean okay = blacklistFile.delete();
if (!okay) LOG.warn("Could not delete blacklist file in " + skeletonDir.getPath());
}
}
/**
* For every existing skeleton file, take its module file name,
* and remove the skeleton if the module file does not exist.
* Works recursively starting from dir. Removes dirs that become empty.
*/
private void cleanUpSkeletons(final File dir) {
indicateMinor(dir.getPath());
final File[] files = dir.listFiles();
if (files == null) {
return;
}
for (File item : files) {
if (item.isDirectory()) {
cleanUpSkeletons(item);
// was the dir emptied?
File[] remaining = item.listFiles();
if (remaining != null && remaining.length == 0) {
mySkeletonsGenerator.deleteOrLog(item);
}
else if (remaining != null && remaining.length == 1) { //clean also if contains only __init__.py
File lastFile = remaining[0];
if (PyNames.INIT_DOT_PY.equals(lastFile.getName()) && lastFile.length() == 0) {
boolean deleted = mySkeletonsGenerator.deleteOrLog(lastFile);
if (deleted) mySkeletonsGenerator.deleteOrLog(item);
}
}
}
else if (item.isFile()) {
// clean up an individual file
final String itemName = item.getName();
if (PyNames.INIT_DOT_PY.equals(itemName) && item.length() == 0) continue; // these are versionless
if (BLACKLIST_FILE_NAME.equals(itemName)) continue; // don't touch the blacklist
if (PythonSdkType.getBuiltinsFileName(mySdk).equals(itemName)) {
continue;
}
final SkeletonHeader header = readSkeletonHeader(item);
boolean canLive = header != null;
if (canLive) {
final String binaryFile = header.getBinaryFile();
canLive = SkeletonVersionChecker.BUILTIN_NAME.equals(binaryFile) || mySkeletonsGenerator.exists(binaryFile);
}
if (!canLive) {
mySkeletonsGenerator.deleteOrLog(item);
}
}
}
}
private static class UpdateResult {
private final String myPath;
private final String myName;
private final long myTimestamp;
public boolean isFresh() {
return myIsFresh;
}
private final boolean myIsFresh;
private UpdateResult(String name, String path, long timestamp, boolean fresh) {
myName = name;
myPath = path;
myTimestamp = timestamp;
myIsFresh = fresh;
}
public String getName() {
return myName;
}
public String getPath() {
return myPath;
}
public Long getTimestamp() {
return myTimestamp;
}
}
/**
* (Re-)generates skeletons for all binary python modules. Up-to-date skeletons are not regenerated.
* Does one module at a time: slower, but avoids certain conflicts.
*
* @param modules output of generator3 -L
* @return blacklist data; whatever was not generated successfully is put here.
*/
private List<UpdateResult> updateOrCreateSkeletons(Map<String, PyBinaryItem> modules) throws InvalidSdkException {
long startTime = System.currentTimeMillis();
final List<String> names = Lists.newArrayList(modules.keySet());
Collections.sort(names);
final List<UpdateResult> results = new ArrayList<UpdateResult>();
final int count = names.size();
for (int i = 0; i < count; i++) {
checkCanceled();
if (myIndicator != null) {
myIndicator.setFraction((double)i / count);
}
final String name = names.get(i);
final PyBinaryItem module = modules.get(name);
if (module != null) {
updateOrCreateSkeleton(module, results);
}
}
finishSkeletonsGeneration();
long doneInMs = System.currentTimeMillis() - startTime;
LOG.info("Rebuilding skeletons for binaries took " + doneInMs + " ms");
return results;
}
private void finishSkeletonsGeneration() {
mySkeletonsGenerator.finishSkeletonsGeneration();
}
private static File getSkeleton(String moduleName, String skeletonsPath) {
final File module = getModuleSkeleton(moduleName, skeletonsPath);
return module.exists() ? module : getPackageSkeleton(moduleName, skeletonsPath);
}
private static File getModuleSkeleton(String module, String skeletonsPath) {
final String modulePath = module.replace('.', '/');
return new File(skeletonsPath, modulePath + ".py");
}
private static File getPackageSkeleton(String pkg, String skeletonsPath) {
final String packagePath = pkg.replace('.', '/');
return new File(new File(skeletonsPath, packagePath), PyNames.INIT_DOT_PY);
}
private boolean updateOrCreateSkeleton(final PyBinaryItem binaryItem,
final List<UpdateResult> errorList) throws InvalidSdkException {
final String moduleName = binaryItem.getModule();
final File skeleton = getSkeleton(moduleName, getSkeletonsPath());
final SkeletonHeader header = readSkeletonHeader(skeleton);
boolean mustRebuild = true; // guilty unless proven fresh enough
if (header != null) {
int requiredVersion = myVersionChecker.getRequiredVersion(moduleName);
mustRebuild = header.getVersion() < requiredVersion;
}
if (!mustRebuild) { // ...but what if the lib was updated?
mustRebuild = (skeleton.exists() && binaryItem.lastModified() > skeleton.lastModified());
// really we can omit both exists() calls but I keep these to make the logic clear
}
if (myBlacklist != null) {
Pair<Integer, Long> versionInfo = myBlacklist.get(binaryItem.getPath());
if (versionInfo != null) {
int failedGeneratorVersion = versionInfo.getFirst();
long failedTimestamp = versionInfo.getSecond();
mustRebuild &= failedGeneratorVersion < myGeneratorVersion || failedTimestamp < binaryItem.lastModified();
if (!mustRebuild) { // we're still failing to rebuild, it, keep it in blacklist
errorList.add(new UpdateResult(moduleName, binaryItem.getPath(), binaryItem.lastModified(), false));
}
}
}
if (mustRebuild) {
indicateMinor(moduleName);
if (myPregeneratedSkeletons != null && copyPregeneratedSkeleton(moduleName)) {
return true;
}
LOG.info("Skeleton for " + moduleName);
generateSkeleton(moduleName, binaryItem.getPath(), null, new Consumer<Boolean>() {
@Override
public void consume(Boolean generated) {
if (!generated) {
errorList.add(new UpdateResult(moduleName, binaryItem.getPath(), binaryItem.lastModified(), true));
}
}
});
}
return false;
}
public static class PyBinaryItem {
private String myPath;
private String myModule;
private long myLength;
private long myLastModified;
PyBinaryItem(String module, String path, long length, long lastModified) {
myPath = path;
myModule = module;
myLength = length;
myLastModified = lastModified * 1000;
}
public String getPath() {
return myPath;
}
public String getModule() {
return myModule;
}
public long length() {
return myLength;
}
public long lastModified() {
return myLastModified;
}
}
private boolean copyPregeneratedSkeleton(String moduleName) throws InvalidSdkException {
File targetDir;
final String modulePath = moduleName.replace('.', '/');
File skeletonsDir = new File(getSkeletonsPath());
VirtualFile pregenerated = myPregeneratedSkeletons.findFileByRelativePath(modulePath + ".py");
if (pregenerated == null) {
pregenerated = myPregeneratedSkeletons.findFileByRelativePath(modulePath + "/" + PyNames.INIT_DOT_PY);
targetDir = new File(skeletonsDir, modulePath);
}
else {
int pos = modulePath.lastIndexOf('/');
if (pos < 0) {
targetDir = skeletonsDir;
}
else {
final String moduleParentPath = modulePath.substring(0, pos);
targetDir = new File(skeletonsDir, moduleParentPath);
}
}
if (pregenerated != null && (targetDir.exists() || targetDir.mkdirs())) {
LOG.info("Pregenerated skeleton for " + moduleName);
File target = new File(targetDir, pregenerated.getName());
try {
FileOutputStream fos = new FileOutputStream(target);
try {
FileUtil.copy(pregenerated.getInputStream(), fos);
}
finally {
fos.close();
}
}
catch (IOException e) {
LOG.info("Error copying pregenerated skeleton", e);
return false;
}
return true;
}
return false;
}
@Nullable
private VirtualFile findPregeneratedSkeletons() {
final File root = findPregeneratedSkeletonsRoot();
if (root == null) {
return null;
}
LOG.info("Pregenerated skeletons root is " + root);
@NonNls final String versionString = mySdk.getVersionString();
if (versionString == null) {
return null;
}
if (PySdkUtil.isRemote(mySdk)) {
return null;
}
String version = versionString.toLowerCase().replace(" ", "-");
File f;
if (SystemInfo.isMac) {
String osVersion = SystemInfo.OS_VERSION;
int dot = osVersion.indexOf('.');
if (dot >= 0) {
int secondDot = osVersion.indexOf('.', dot + 1);
if (secondDot >= 0) {
osVersion = osVersion.substring(0, secondDot);
}
}
f = new File(root, "skeletons-mac-" + myGeneratorVersion + "-" + osVersion + "-" + version + ".zip");
}
else {
String os = SystemInfo.isWindows ? "win" : "nix";
f = new File(root, "skeletons-" + os + "-" + myGeneratorVersion + "-" + version + ".zip");
}
if (f.exists()) {
LOG.info("Found pregenerated skeletons at " + f.getPath());
final VirtualFile virtualFile = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(f);
if (virtualFile == null) {
LOG.info("Could not find pregenerated skeletons in VFS");
return null;
}
return JarFileSystem.getInstance().getJarRootForLocalFile(virtualFile);
}
else {
LOG.info("Not found pregenerated skeletons at " + f.getPath());
return null;
}
}
@Nullable
private static File findPregeneratedSkeletonsRoot() {
final String path = PathManager.getHomePath();
LOG.info("Home path is " + path);
File f = new File(path, "python/skeletons"); // from sources
if (f.exists()) return f;
f = new File(path, "skeletons"); // compiled binary
if (f.exists()) return f;
return null;
}
/**
* Generates a skeleton for a particular binary module.
*
* @param modname name of the binary module as known to Python (e.g. 'foo.bar')
* @param modfilename name of file which defines the module, null for built-in modules
* @param assemblyRefs refs that generator wants to know in .net environment, if applicable
* @param resultConsumer accepts true if generation completed successfully
*/
public void generateSkeleton(@NotNull String modname, @Nullable String modfilename,
@Nullable List<String> assemblyRefs, Consumer<Boolean> resultConsumer) throws InvalidSdkException {
mySkeletonsGenerator.generateSkeleton(modname, modfilename, assemblyRefs, getExtraSyspath(), mySdk.getHomePath(), resultConsumer);
}
private String getExtraSyspath() {
if (myExtraSyspath == null) {
myExtraSyspath = calculateExtraSysPath(mySdk, mySkeletonsPath);
}
return myExtraSyspath;
}
}