From 7cf9883735bb789e5cdebfeb516465a091b8fa91 Mon Sep 17 00:00:00 2001 From: Dmitry Jemerov Date: Tue, 6 Apr 2010 21:40:15 +0400 Subject: [PATCH] initial version of quickfix to trigger generation of IronPython binary stubs --- .../actions/GenerateBinaryStubsFix.java | 111 ++++++++++++++++++ .../PyUnresolvedReferencesInspection.java | 8 +- .../python/psi/PyCallExpression.java | 7 ++ .../python/psi/impl/PyCallExpressionImpl.java | 8 ++ .../python/psi/impl/PyDecoratorImpl.java | 8 ++ .../jetbrains/python/sdk/PythonSdkType.java | 65 +++++----- .../python/sdk/SkeletonVersionUpdater.java | 7 +- 7 files changed, 178 insertions(+), 36 deletions(-) create mode 100644 python/src/com/jetbrains/python/actions/GenerateBinaryStubsFix.java diff --git a/python/src/com/jetbrains/python/actions/GenerateBinaryStubsFix.java b/python/src/com/jetbrains/python/actions/GenerateBinaryStubsFix.java new file mode 100644 index 000000000000..a95bc63073a5 --- /dev/null +++ b/python/src/com/jetbrains/python/actions/GenerateBinaryStubsFix.java @@ -0,0 +1,111 @@ +package com.jetbrains.python.actions; + +import com.intellij.codeInspection.LocalQuickFix; +import com.intellij.codeInspection.ProblemDescriptor; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.module.ModuleUtil; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.projectRoots.Sdk; +import com.intellij.openapi.vfs.LocalFileSystem; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiReference; +import com.jetbrains.python.psi.*; +import com.jetbrains.python.psi.impl.PyImportReferenceImpl; +import com.jetbrains.python.sdk.IronPythonSdkFlavor; +import com.jetbrains.python.sdk.PythonSdkFlavor; +import com.jetbrains.python.sdk.PythonSdkType; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * @author yole + */ +public class GenerateBinaryStubsFix implements LocalQuickFix { + private String myQualifiedName; + private Sdk mySdk; + + public GenerateBinaryStubsFix(PsiReference reference) { + myQualifiedName = getReferenceText(reference); + mySdk = getPythonSdk(reference); + } + + private static String getReferenceText(PsiReference reference) { + PsiElement element = reference.getElement(); + while (element.getParent() instanceof PyReferenceExpression) { + element = element.getParent(); + } + return element.getText(); + } + + @NotNull + public String getName() { + return "Generate stubs for binary module " + myQualifiedName; + } + + @NotNull + public String getFamilyName() { + return "GenerateBinaryStubs"; + } + + public void applyFix(@NotNull Project project, @NotNull final ProblemDescriptor descriptor) { + ProgressManager.getInstance().runProcessWithProgressSynchronously(new Runnable() { + public void run() { + List assemblyRefs = collectAssemblyReferences(descriptor.getPsiElement().getContainingFile()); + final String skeletonPath = PythonSdkType.findSkeletonsPath(mySdk); + PythonSdkType.generateSkeleton(mySdk.getHomePath(), skeletonPath, myQualifiedName, assemblyRefs); + final VirtualFile skeletonDir = LocalFileSystem.getInstance().findFileByPath(skeletonPath); + if (skeletonDir != null) { + skeletonDir.refresh(true, true); + } + } + }, "Generating stubs for binary module", false, project); + } + + private List collectAssemblyReferences(PsiFile file) { + if (!(PythonSdkFlavor.getFlavor(mySdk.getHomePath()) instanceof IronPythonSdkFlavor)) { + return Collections.emptyList(); + } + final List result = new ArrayList(); + file.accept(new PyRecursiveElementVisitor() { + @Override + public void visitPyCallExpression(PyCallExpression node) { + super.visitPyCallExpression(node); + if (node.isCalleeText("AddReference") || node.isCalleeText("AddReferenceByPartialName")) { + final PyExpression[] args = node.getArguments(); + if (args.length == 1 && args [0] instanceof PyStringLiteralExpression) { + result.add(((PyStringLiteralExpression) args [0]).getStringValue()); + } + } + } + }); + return result; + } + + public static boolean isApplicable(PsiReference ref) { + if (!(ref instanceof PyImportReferenceImpl)) { + return false; + } + final Sdk sdk = getPythonSdk(ref); + if (sdk == null) { + return false; + } + final PythonSdkFlavor flavor = PythonSdkFlavor.getFlavor(sdk.getHomePath()); + if (flavor instanceof IronPythonSdkFlavor) { + return getReferenceText(ref).contains("System"); + } + return false; + } + + @Nullable + private static Sdk getPythonSdk(PsiReference ref) { + final Module module = ModuleUtil.findModuleForPsiElement(ref.getElement()); + return module == null ? null : PythonSdkType.findPythonSdk(module); + } +} diff --git a/python/src/com/jetbrains/python/inspections/PyUnresolvedReferencesInspection.java b/python/src/com/jetbrains/python/inspections/PyUnresolvedReferencesInspection.java index a6fdd3f44a44..758a9fe9f674 100644 --- a/python/src/com/jetbrains/python/inspections/PyUnresolvedReferencesInspection.java +++ b/python/src/com/jetbrains/python/inspections/PyUnresolvedReferencesInspection.java @@ -16,10 +16,7 @@ import com.intellij.psi.util.PsiTreeUtil; import com.intellij.util.Consumer; import com.jetbrains.python.PyBundle; import com.jetbrains.python.PyNames; -import com.jetbrains.python.actions.AddFieldQuickFix; -import com.jetbrains.python.actions.AddImportAction; -import com.jetbrains.python.actions.AddMethodQuickFix; -import com.jetbrains.python.actions.ImportFromExistingFix; +import com.jetbrains.python.actions.*; import com.jetbrains.python.codeInsight.PyCodeInsightSettings; import com.jetbrains.python.psi.*; import com.jetbrains.python.psi.impl.PyBuiltinCache; @@ -373,6 +370,9 @@ public class PyUnresolvedReferencesInspection extends LocalInspectionTool { hl_type = ProblemHighlightType.LIKE_UNKNOWN_SYMBOL; } + if (GenerateBinaryStubsFix.isApplicable(reference)) { + actions.add(new GenerateBinaryStubsFix(reference)); + } addPluginQuickFixes(reference, actions); PsiElement point = node.getLastChild(); // usually the identifier at the end of qual ref diff --git a/python/src/com/jetbrains/python/psi/PyCallExpression.java b/python/src/com/jetbrains/python/psi/PyCallExpression.java index bad20f2d0cec..2ed97eba8c30 100644 --- a/python/src/com/jetbrains/python/psi/PyCallExpression.java +++ b/python/src/com/jetbrains/python/psi/PyCallExpression.java @@ -38,6 +38,13 @@ public interface PyCallExpression extends PyExpression { @Nullable PyMarkedFunction resolveCallee(); + /** + * Checks if the unqualified name of the callee matches the specified text. + * + * @param name the text to check + * @return true if matches, false otherwise + */ + boolean isCalleeText(@NotNull String name); /** * Couples function with a flag describing the way it is called. diff --git a/python/src/com/jetbrains/python/psi/impl/PyCallExpressionImpl.java b/python/src/com/jetbrains/python/psi/impl/PyCallExpressionImpl.java index 8ffc1d701f88..fbd9f7ea78e6 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyCallExpressionImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyCallExpressionImpl.java @@ -49,6 +49,14 @@ public class PyCallExpressionImpl extends PyElementImpl implements PyCallExpress return PyCallExpressionHelper.resolveCallee(this); } + public boolean isCalleeText(@NotNull String name) { + final PyExpression callee = getCallee(); + if (!(callee instanceof PyReferenceExpression)) { + return false; + } + return name.equals(((PyReferenceExpression)callee).getReferencedName()); + } + @Override public String toString() { return "PyCallExpression: " + PyUtil.getReadableRepr(getCallee(), true); //or: getCalledFunctionReference().getReferencedName(); diff --git a/python/src/com/jetbrains/python/psi/impl/PyDecoratorImpl.java b/python/src/com/jetbrains/python/psi/impl/PyDecoratorImpl.java index c38540518c7f..1e9e657ad43c 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyDecoratorImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyDecoratorImpl.java @@ -102,6 +102,14 @@ public class PyDecoratorImpl extends PyPresentableElementImpl i return callee; } + public boolean isCalleeText(@NotNull String name) { + final PyExpression callee = getCallee(); + if (!(callee instanceof PyReferenceExpression)) { + return false; + } + return name.equals(((PyReferenceExpression)callee).getReferencedName()); + } + @Override public String toString() { return "PyDecorator: @" + PyUtil.getReadableRepr(getCallee(), true); //getCalledFunctionReference().getReferencedName(); diff --git a/python/src/com/jetbrains/python/sdk/PythonSdkType.java b/python/src/com/jetbrains/python/sdk/PythonSdkType.java index e83e914a47e9..588f3bb3b6cc 100644 --- a/python/src/com/jetbrains/python/sdk/PythonSdkType.java +++ b/python/src/com/jetbrains/python/sdk/PythonSdkType.java @@ -48,10 +48,7 @@ import java.io.File; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Comparator; -import java.util.List; -import java.util.TreeSet; +import java.util.*; import static com.jetbrains.python.psi.PyUtil.sure; @@ -98,8 +95,6 @@ public class PythonSdkType extends SdkType { return PyBuiltinCache.BUILTIN_FILE; } - @NonNls static final private String PYTHON_STR = "python"; - @NonNls @Nullable public String suggestHomePath() { @@ -352,9 +347,8 @@ public class PythonSdkType extends SdkType { } } // fix skeletons as needed - String url = findSkeletonsUrl(currentSdk); - if (url != null) { - final String path = VfsUtil.urlToPath(url); + final String path = findSkeletonsPath(currentSdk); + if (path != null) { File stubs_dir = new File(path); if (!stubs_dir.exists()) { final ProgressManager progman = ProgressManager.getInstance(); @@ -364,7 +358,7 @@ public class PythonSdkType extends SdkType { public void run(@NotNull final ProgressIndicator indicator) { try { generateBuiltinStubs(currentSdk.getHomePath(), path); - generateBinaryStubs(currentSdk.getHomePath(), path, indicator); + generateBinarySkeletons(currentSdk.getHomePath(), path, indicator); } catch (Exception e) { LOG.error(e); @@ -410,11 +404,11 @@ public class PythonSdkType extends SdkType { } @Nullable - public static String findSkeletonsUrl(Sdk sdk) { + public static String findSkeletonsPath(Sdk sdk) { final String[] urls = sdk.getRootProvider().getUrls(BUILTIN_ROOT_TYPE); for (String url : urls) { if (url.contains(SKELETON_DIR_NAME)) { - return url; + return VfsUtil.urlToPath(url); } } return null; @@ -528,7 +522,7 @@ public class PythonSdkType extends SdkType { // regenerate stubs, existing or not final File stubs_dir = new File(stubs_path); if (!stubs_dir.exists()) stubs_dir.mkdirs(); - generateBinaryStubs(bin_path, stubs_path, indicator); + generateBinarySkeletons(bin_path, stubs_path, indicator); } } @@ -603,8 +597,7 @@ public class PythonSdkType extends SdkType { * @param stubsRoot where to put results (expected to exist). * @param indicator ProgressIndicator to update, or null. */ - public static void generateBinaryStubs(final String binaryPath, final String stubsRoot, ProgressIndicator indicator) - { + public static void generateBinarySkeletons(final String binaryPath, final String stubsRoot, ProgressIndicator indicator) { if (indicator != null) { indicator.setText("Generating skeletons of binary libs"); } @@ -635,19 +628,7 @@ public class PythonSdkType extends SdkType { indicator.setText2(modname); } LOG.info("Skeleton for " + modname); - final ProcessOutput gen_result = SdkUtil.getProcessOutput( - parent_dir, - new String[]{binaryPath, PythonHelpersLocator.getHelperPath(GENERATOR3), "-d", stubsRoot, modname}, - getVirtualEnvAdditionalEnv(binaryPath), - RUN_TIMEOUT - ); - if (gen_result.getExitCode() != 0) { - StringBuffer sb = new StringBuffer("Skeleton for "); - sb.append(modname).append(" failed. stderr: --"); - for (String err_line : gen_result.getStderrLines()) sb.append(err_line).append("\n"); - sb.append("--"); - LOG.warn(sb.toString()); - } + generateSkeleton(binaryPath, stubsRoot, modname, Collections.emptyList()); } } } @@ -661,6 +642,34 @@ public class PythonSdkType extends SdkType { } } + public static void generateSkeleton(String binaryPath, String stubsRoot, String modname, List assemblyRefs) { + final String parent_dir = new File(binaryPath).getParent(); + List commandLine = new ArrayList(); + commandLine.add(binaryPath); + commandLine.add(PythonHelpersLocator.getHelperPath(GENERATOR3)); + commandLine.add("-d"); + commandLine.add(stubsRoot); + if (!assemblyRefs.isEmpty()) { + commandLine.add("-c"); + commandLine.add(StringUtil.join(assemblyRefs, ";")); + } + commandLine.add(modname); + + final ProcessOutput gen_result = SdkUtil.getProcessOutput( + parent_dir, + commandLine.toArray(new String[commandLine.size()]), + getVirtualEnvAdditionalEnv(binaryPath), + RUN_TIMEOUT*10 + ); + if (gen_result.getExitCode() != 0) { + StringBuffer sb = new StringBuffer("Skeleton for "); + sb.append(modname).append(" failed. stderr: --"); + for (String err_line : gen_result.getStderrLines()) sb.append(err_line).append("\n"); + sb.append("--"); + LOG.warn(sb.toString()); + } + } + public static List getAllSdks() { return ProjectJdkTable.getInstance().getSdksOfType(getInstance()); } diff --git a/python/src/com/jetbrains/python/sdk/SkeletonVersionUpdater.java b/python/src/com/jetbrains/python/sdk/SkeletonVersionUpdater.java index 1e7f31cd9795..7cea7f88da2b 100644 --- a/python/src/com/jetbrains/python/sdk/SkeletonVersionUpdater.java +++ b/python/src/com/jetbrains/python/sdk/SkeletonVersionUpdater.java @@ -6,7 +6,7 @@ import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.projectRoots.Sdk; import com.intellij.openapi.startup.StartupManager; -import com.intellij.openapi.vfs.VfsUtil; +import com.intellij.openapi.vfs.LocalFileSystem; import org.jetbrains.annotations.NotNull; import java.io.*; @@ -30,10 +30,9 @@ public class SkeletonVersionUpdater implements ProjectComponent { writeVersion(versionFile, SKELETONS_VERSION); final List sdkList = PythonSdkType.getAllSdks(); for (Sdk sdk : sdkList) { - final String url = PythonSdkType.findSkeletonsUrl(sdk); - final String path = VfsUtil.urlToPath(url); + final String path = PythonSdkType.findSkeletonsPath(sdk); PythonSdkType.generateBuiltinStubs(sdk.getHomePath(), path); - PythonSdkType.generateBinaryStubs(sdk.getHomePath(), path, ProgressManager.getInstance().getProgressIndicator()); + PythonSdkType.generateBinarySkeletons(sdk.getHomePath(), path, ProgressManager.getInstance().getProgressIndicator()); } } }