2 * Copyright 2000-2014 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com.jetbrains.python.inspections.quickfix;
18 import com.google.common.collect.Lists;
19 import com.intellij.codeInspection.LocalQuickFix;
20 import com.intellij.codeInspection.ProblemDescriptor;
21 import com.intellij.execution.configurations.GeneralCommandLine;
22 import com.intellij.execution.process.ProcessOutput;
23 import com.intellij.openapi.application.ReadAction;
24 import com.intellij.openapi.application.Result;
25 import com.intellij.openapi.diagnostic.Logger;
26 import com.intellij.openapi.module.Module;
27 import com.intellij.openapi.module.ModuleUtilCore;
28 import com.intellij.openapi.progress.ProgressIndicator;
29 import com.intellij.openapi.progress.ProgressManager;
30 import com.intellij.openapi.progress.Task;
31 import com.intellij.openapi.progress.Task.Backgroundable;
32 import com.intellij.openapi.progress.impl.BackgroundableProcessIndicator;
33 import com.intellij.openapi.project.Project;
34 import com.intellij.openapi.projectRoots.Sdk;
35 import com.intellij.openapi.vfs.LocalFileSystem;
36 import com.intellij.openapi.vfs.VirtualFile;
37 import com.intellij.psi.PsiElement;
38 import com.intellij.psi.PsiFile;
39 import com.intellij.psi.util.QualifiedName;
40 import com.intellij.util.Consumer;
41 import com.jetbrains.python.PyBundle;
42 import com.jetbrains.python.PythonHelper;
43 import com.jetbrains.python.psi.*;
44 import com.jetbrains.python.sdk.InvalidSdkException;
45 import com.jetbrains.python.sdk.PySdkUtil;
46 import com.jetbrains.python.sdk.PythonSdkType;
47 import com.jetbrains.python.sdk.flavors.IronPythonSdkFlavor;
48 import com.jetbrains.python.sdk.flavors.PythonSdkFlavor;
49 import com.jetbrains.python.sdk.skeletons.PySkeletonGenerator;
50 import com.jetbrains.python.sdk.skeletons.PySkeletonRefresher;
51 import org.jetbrains.annotations.NotNull;
52 import org.jetbrains.annotations.Nullable;
55 import java.util.ArrayList;
56 import java.util.Collection;
57 import java.util.Collections;
58 import java.util.List;
63 public class GenerateBinaryStubsFix implements LocalQuickFix {
64 private static final Logger LOG = Logger.getInstance("#" + GenerateBinaryStubsFix.class.getName());
66 private final String myQualifiedName;
67 private final Sdk mySdk;
70 * Generates pack of fixes available for some unresolved import statement.
71 * Be sure to call {@link #isApplicable(com.jetbrains.python.psi.PyImportStatementBase)} first to make sure this statement is supported
73 * @param importStatementBase statement to fix
74 * @return pack of fixes
77 public static Collection<GenerateBinaryStubsFix> generateFixes(@NotNull final PyImportStatementBase importStatementBase) {
78 final List<String> names = importStatementBase.getFullyQualifiedObjectNames();
79 final List<GenerateBinaryStubsFix> result = new ArrayList<>(names.size());
80 if (importStatementBase instanceof PyFromImportStatement && names.isEmpty()) {
81 final QualifiedName qName = ((PyFromImportStatement)importStatementBase).getImportSourceQName();
83 result.add(new GenerateBinaryStubsFix(importStatementBase, qName.toString()));
86 for (final String qualifiedName : names) {
87 result.add(new GenerateBinaryStubsFix(importStatementBase, qualifiedName));
93 * @param importStatementBase statement to fix
94 * @param qualifiedName name should be fixed (one of {@link com.jetbrains.python.psi.PyImportStatementBase#getFullyQualifiedObjectNames()})
96 private GenerateBinaryStubsFix(@NotNull final PyImportStatementBase importStatementBase, @NotNull final String qualifiedName) {
97 myQualifiedName = qualifiedName;
98 mySdk = getPythonSdk(importStatementBase);
103 public String getName() {
104 return PyBundle.message("sdk.gen.stubs.for.binary.modules", myQualifiedName);
109 public String getFamilyName() {
110 return "Generate binary stubs";
114 public boolean startInWriteAction() {
119 public void applyFix(@NotNull final Project project, @NotNull final ProblemDescriptor descriptor) {
120 final PsiFile file = descriptor.getPsiElement().getContainingFile();
121 final Backgroundable backgroundable = getFixTask(file);
122 ProgressManager.getInstance().runProcessWithProgressAsynchronously(backgroundable, new BackgroundableProcessIndicator(backgroundable));
127 * Returns fix task that is used to generate stubs
129 * @param fileToRunTaskIn file where task should run
130 * @return task itself
133 public Backgroundable getFixTask(@NotNull final PsiFile fileToRunTaskIn) {
134 final Project project = fileToRunTaskIn.getProject();
135 final String folder = fileToRunTaskIn.getContainingDirectory().getVirtualFile().getCanonicalPath();
136 return new Task.Backgroundable(project, "Generating skeletons for binary module", false) {
139 public void run(@NotNull ProgressIndicator indicator) {
140 indicator.setIndeterminate(true);
143 final List<String> assemblyRefs = new ReadAction<List<String>>() {
145 protected void run(@NotNull Result<List<String>> result) throws Throwable {
146 result.setResult(collectAssemblyReferences(fileToRunTaskIn));
148 }.execute().getResultObject();
152 final PySkeletonRefresher refresher = new PySkeletonRefresher(project, null, mySdk, null, null, folder);
154 if (needBinaryList(myQualifiedName)) {
155 if (!generateSkeletonsForList(refresher, indicator, folder)) return;
158 //noinspection unchecked
159 refresher.generateSkeleton(myQualifiedName, "", assemblyRefs, Consumer.EMPTY_CONSUMER);
161 final VirtualFile skeletonDir;
162 skeletonDir = LocalFileSystem.getInstance().findFileByPath(refresher.getSkeletonsPath());
163 if (skeletonDir != null) {
164 skeletonDir.refresh(true, true);
167 catch (InvalidSdkException e) {
174 private boolean generateSkeletonsForList(@NotNull final PySkeletonRefresher refresher,
175 ProgressIndicator indicator,
176 @Nullable final String currentBinaryFilesPath) throws InvalidSdkException {
177 final PySkeletonGenerator generator = new PySkeletonGenerator(refresher.getSkeletonsPath(), mySdk, currentBinaryFilesPath);
178 indicator.setIndeterminate(false);
179 final String homePath = mySdk.getHomePath();
180 if (homePath == null) return false;
181 GeneralCommandLine cmd = PythonHelper.EXTRA_SYSPATH.newCommandLine(homePath, Lists.newArrayList(myQualifiedName));
182 final ProcessOutput runResult = PySdkUtil.getProcessOutput(cmd,
183 new File(homePath).getParent(),
184 PythonSdkType.getVirtualEnvExtraEnv(homePath), 5000
186 if (runResult.getExitCode() == 0 && !runResult.isTimeout()) {
187 final String extraPath = runResult.getStdout();
188 final PySkeletonGenerator.ListBinariesResult binaries = generator.listBinaries(mySdk, extraPath);
189 final List<String> names = Lists.newArrayList(binaries.modules.keySet());
190 Collections.sort(names);
191 final int size = names.size();
192 for (int i = 0; i != size; ++i) {
193 final String name = names.get(i);
194 indicator.setFraction((double)i / size);
195 if (needBinaryList(name)) {
196 indicator.setText2(name);
197 final PySkeletonRefresher.PyBinaryItem item = binaries.modules.get(name);
198 final String modulePath = item != null ? item.getPath() : "";
199 //noinspection unchecked
200 refresher.generateSkeleton(name, modulePath, new ArrayList<>(), Consumer.EMPTY_CONSUMER);
207 private static boolean needBinaryList(@NotNull final String qualifiedName) {
208 return qualifiedName.startsWith("gi.repository");
211 private List<String> collectAssemblyReferences(PsiFile file) {
212 if (!(PythonSdkFlavor.getFlavor(mySdk) instanceof IronPythonSdkFlavor)) {
213 return Collections.emptyList();
215 final List<String> result = new ArrayList<>();
216 file.accept(new PyRecursiveElementVisitor() {
218 public void visitPyCallExpression(PyCallExpression node) {
219 super.visitPyCallExpression(node);
220 // TODO: What if user loads it not by literal? We need to ask user for list of DLLs
221 if (node.isCalleeText("AddReference", "AddReferenceByPartialName", "AddReferenceByName")) {
222 final PyExpression[] args = node.getArguments();
223 if (args.length == 1 && args[0] instanceof PyStringLiteralExpression) {
224 result.add(((PyStringLiteralExpression)args[0]).getStringValue());
233 * Checks if this fix can help you to generate binary stubs
235 * @param importStatementBase statement to fix
236 * @return true if this fix could work
238 public static boolean isApplicable(@NotNull final PyImportStatementBase importStatementBase) {
239 if (importStatementBase.getFullyQualifiedObjectNames().isEmpty() &&
240 !(importStatementBase instanceof PyFromImportStatement && ((PyFromImportStatement)importStatementBase).isStarImport())) {
243 final Sdk sdk = getPythonSdk(importStatementBase);
247 final PythonSdkFlavor flavor = PythonSdkFlavor.getFlavor(sdk);
248 if (flavor instanceof IronPythonSdkFlavor) {
251 return isGtk(importStatementBase);
254 private static boolean isGtk(@NotNull final PyImportStatementBase importStatementBase) {
255 if (importStatementBase instanceof PyFromImportStatement) {
256 final QualifiedName qName = ((PyFromImportStatement)importStatementBase).getImportSourceQName();
257 if (qName != null && qName.matches("gi", "repository")) {
265 private static Sdk getPythonSdk(@NotNull final PsiElement element) {
266 final Module module = ModuleUtilCore.findModuleForPsiElement(element);
267 return (module == null) ? null : PythonSdkType.findPythonSdk(module);