mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
Rewritten PythonSdkUpdater to become more readable and modifiable
This rewrite should fix several bugs with Python paths missing after updating the SDK, a performance problem with numerous refreshes to the VFS due to many SDK commits, problems with running several skeleton refresher tasks simultaneously for the same SDK.
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright 2000-2015 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.intellij.util.concurrency;
|
||||
|
||||
import com.intellij.util.containers.HashSet;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.locks.*;
|
||||
|
||||
/**
|
||||
* Naive implementation of blocking set: class that allows {@link #put(Object)} lock by the {@code key} specified and
|
||||
* {@link #remove(Object)} it. The main feature is that another invocation of {@link #put(Object)} with the same {@code key} could not
|
||||
* be proceeded while one won't call {@link #remove(Object)} with this {@code key}.
|
||||
*
|
||||
* @author Alexander Koshevoy
|
||||
*/
|
||||
public class BlockingSet<T> {
|
||||
private final Set<T> set;
|
||||
|
||||
private final Condition unlock;
|
||||
private final Lock lock;
|
||||
|
||||
public BlockingSet() {
|
||||
set = new HashSet<T>();
|
||||
lock = new ReentrantLock();
|
||||
unlock = lock.newCondition();
|
||||
}
|
||||
|
||||
/**
|
||||
* Acquires lock by {@code key}. If lock by {@code key} has been already acquired wait until it is released. Acquire is <b>not</b> reentrant.
|
||||
*
|
||||
* @param key
|
||||
*/
|
||||
public void put(@NotNull T key) {
|
||||
lock.lock();
|
||||
try {
|
||||
while (set.contains(key)) {
|
||||
unlock.awaitUninterruptibly();
|
||||
}
|
||||
set.add(key);
|
||||
}
|
||||
finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Releases lock by {@code key}. If lock has not been acquired throws {@link IllegalStateException}.
|
||||
*
|
||||
* @param key
|
||||
* @throws IllegalStateException if lock by {@code key} has not been acquired.
|
||||
*/
|
||||
public void remove(@NotNull T key) throws IllegalStateException {
|
||||
lock.lock();
|
||||
try {
|
||||
if (!set.contains(key)) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
set.remove(key);
|
||||
unlock.signalAll();
|
||||
}
|
||||
finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> BlockingSet<T> newInstance() {
|
||||
return new BlockingSet<T>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package com.intellij.util.concurrency;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
/**
|
||||
* @author Alexander Koshevoy
|
||||
*/
|
||||
public class BlockingSetTest {
|
||||
@Test
|
||||
public void testSingleThreadLock() throws Exception {
|
||||
BlockingSet<String> lock = new BlockingSet<String>();
|
||||
lock.put("eins");
|
||||
lock.put("zwei");
|
||||
lock.remove("zwei");
|
||||
lock.put("polizei");
|
||||
lock.remove("polizei");
|
||||
lock.remove("eins");
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void testReleaseNotAcquired() throws Exception {
|
||||
BlockingSet<String> lock = new BlockingSet<String>();
|
||||
lock.put("eins");
|
||||
lock.put("zwei");
|
||||
lock.remove("eins");
|
||||
lock.remove("polizei");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultipleThreads() throws Exception {
|
||||
final BlockingSet<String> lock = new BlockingSet<String>();
|
||||
int threads = 10;
|
||||
int tasks = 100;
|
||||
ExecutorService service = Executors.newFixedThreadPool(threads);
|
||||
List<Callable<Void>> taskList = new ArrayList<Callable<Void>>(tasks);
|
||||
final AtomicBoolean check = new AtomicBoolean(false);
|
||||
for (int i = 0; i < tasks; i++) {
|
||||
taskList.add(new Callable<Void>() {
|
||||
@Override
|
||||
public Void call() throws Exception {
|
||||
lock.put("key");
|
||||
try {
|
||||
Assert.assertFalse(check.get());
|
||||
check.set(true);
|
||||
Thread.sleep(10L);
|
||||
check.set(false);
|
||||
}
|
||||
finally {
|
||||
lock.remove("key");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
List<Future<Void>> futures = service.invokeAll(taskList);
|
||||
service.shutdown();
|
||||
for (Future<Void> future : futures) {
|
||||
future.get();
|
||||
}
|
||||
Assert.assertTrue(service.awaitTermination(100, TimeUnit.SECONDS));
|
||||
}
|
||||
}
|
||||
@@ -268,15 +268,8 @@ public class PyActiveSdkConfigurable implements UnnamedConfigurable {
|
||||
myProjectSdksModel.addSdk(selectedSdk);
|
||||
}
|
||||
}
|
||||
else if (myInitialSdkSet.contains(selectedSdk)) {
|
||||
final Sdk finalSelectedSdk = selectedSdk;
|
||||
ApplicationManager.getApplication().invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
assert finalSelectedSdk != null;
|
||||
PythonSdkType.getInstance().setupSdkPaths(finalSelectedSdk, myProject, null);
|
||||
}
|
||||
});
|
||||
else if (myInitialSdkSet.contains(selectedSdk) && selectedSdk != null) {
|
||||
PythonSdkUpdater.updateOrShowError(selectedSdk, null, myProject, null);
|
||||
}
|
||||
if (selectedSdk != null) {
|
||||
updateSdkList(false);
|
||||
|
||||
@@ -57,6 +57,7 @@ import com.jetbrains.python.remote.PyRemoteSourceItem;
|
||||
import com.jetbrains.python.remote.PythonRemoteInterpreterManager;
|
||||
import com.jetbrains.python.sdk.*;
|
||||
import com.jetbrains.python.sdk.flavors.PythonSdkFlavor;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.swing.*;
|
||||
@@ -429,9 +430,8 @@ public class PythonSdkDetailsDialog extends DialogWrapper {
|
||||
}
|
||||
}
|
||||
|
||||
private void reloadSdk(Sdk currentSdk) {
|
||||
PythonSdkType.getInstance()
|
||||
.setupSdkPaths(currentSdk, myProject, null, myModificators.get(currentSdk)); // or must it be a RunWriteAction?
|
||||
private void reloadSdk(@NotNull Sdk currentSdk) {
|
||||
PythonSdkUpdater.update(currentSdk, myModificators.get(currentSdk), myProject, null);
|
||||
}
|
||||
|
||||
private class ToggleVirtualEnvFilterButton extends ToggleActionButton implements DumbAware {
|
||||
|
||||
@@ -36,10 +36,7 @@ import com.intellij.util.NullableConsumer;
|
||||
import com.jetbrains.python.configuration.PyConfigurableInterpreterList;
|
||||
import com.jetbrains.python.newProject.PyNewProjectSettings;
|
||||
import com.jetbrains.python.newProject.PythonProjectGenerator;
|
||||
import com.jetbrains.python.sdk.PyDetectedSdk;
|
||||
import com.jetbrains.python.sdk.PySdkService;
|
||||
import com.jetbrains.python.sdk.PythonSdkAdditionalData;
|
||||
import com.jetbrains.python.sdk.PythonSdkType;
|
||||
import com.jetbrains.python.sdk.*;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -68,7 +65,7 @@ public class GenerateProjectCallback implements NullableConsumer<ProjectSettings
|
||||
PySdkService.getInstance().solidifySdk(sdk);
|
||||
sdk = SdkConfigurationUtil.createAndAddSDK(sdkHome.getPath(), PythonSdkType.getInstance());
|
||||
if (sdk != null) {
|
||||
PythonSdkType.getInstance().setupSdkPathsImmediately(sdk, project);
|
||||
PythonSdkUpdater.updateOrShowError(sdk, null, project, null);
|
||||
}
|
||||
|
||||
model.addSdk(sdk);
|
||||
|
||||
@@ -878,7 +878,7 @@ sdk.gen.reloading=Reloading generated skeletons...
|
||||
sdk.gen.reading.versions.file=Reading versions file...
|
||||
sdk.gen.notify.converting.old.skels=Converting old skeletons
|
||||
sdk.gen.notify.converting.text=Skeletons of binary modules seem to be from an older version.<br/>These will be fully re-generated, which will take some time, but will happen <i>only once</i>.<br/>Next time you open the project, only skeletons of new or updated binary modules will be re-generated.
|
||||
sdk.gen.updating.skels=Updating Skeletons
|
||||
sdk.gen.updating.skeletons=Updating Skeletons
|
||||
sdk.gen.stubs.for.binary.modules=Generate stubs for binary module {0}
|
||||
|
||||
# Active SDK configurable and related dialogs
|
||||
|
||||
@@ -20,7 +20,6 @@ import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.extensions.Extensions;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.projectRoots.Sdk;
|
||||
import com.intellij.openapi.roots.OrderRootType;
|
||||
import com.intellij.openapi.util.Key;
|
||||
import com.intellij.openapi.vfs.StandardFileSystems;
|
||||
import com.intellij.openapi.vfs.VfsUtilCore;
|
||||
@@ -38,7 +37,6 @@ import com.jetbrains.python.psi.resolve.*;
|
||||
import com.jetbrains.python.psi.types.PyClassLikeType;
|
||||
import com.jetbrains.python.psi.types.PyType;
|
||||
import com.jetbrains.python.psi.types.TypeEvalContext;
|
||||
import com.jetbrains.python.sdk.PySdkUpdater;
|
||||
import com.jetbrains.python.sdk.PythonSdkType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -148,13 +146,6 @@ public class PyUserSkeletonsUtil {
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void addUserSkeletonsRoot(@NotNull PySdkUpdater sdkUpdater) {
|
||||
final VirtualFile root = getUserSkeletonsDirectory();
|
||||
if (root != null) {
|
||||
sdkUpdater.addRoot(root, OrderRootType.CLASSES);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static PsiElement getUserSkeleton(@NotNull PyElement element, @NotNull PyFile skeletonFile, @Nullable TypeEvalContext context) {
|
||||
if (element instanceof PyFile) {
|
||||
|
||||
@@ -1,231 +0,0 @@
|
||||
/*
|
||||
* Copyright 2000-2015 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;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.projectRoots.Sdk;
|
||||
import com.intellij.openapi.projectRoots.SdkModificator;
|
||||
import com.intellij.openapi.roots.OrderRootType;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* This sdk updater class is a facade to Sdk to make changes in it in a reliable way.
|
||||
* Working with sdk instance instead of this class can be wrong, because an instance can become
|
||||
* obsolete being substituted in sdk table by a new one. Or already created sdk modificator can be committed,
|
||||
* discarding the changes that we doing with the sdk.
|
||||
* <p/>
|
||||
* <p/>
|
||||
* There are two ways of creation of the facade:
|
||||
* 1) by sdk path - in this case we'll get the current actual sdk instance
|
||||
* from sdk table by that path, creating and committing SdkModificator on every change
|
||||
* 2) by sdkModificator - in that case we'll make changes to that modificator, but it is not committed,
|
||||
* because it has been created outside of the updater.
|
||||
*
|
||||
* @author traff
|
||||
*/
|
||||
public abstract class PySdkUpdater {
|
||||
private static final Logger LOG = Logger.getInstance("#" + PySdkUpdater.class.getName());
|
||||
|
||||
private static Map<String, PySdkUpdater> mySdkUpdaters = Maps.newHashMap();
|
||||
|
||||
@NotNull
|
||||
public abstract Sdk getSdk();
|
||||
|
||||
public abstract void modifySdk(@NotNull SdkModificationProcessor processor);
|
||||
|
||||
public void addRoot(@NotNull final VirtualFile root, @NotNull final OrderRootType rootType) {
|
||||
modifySdk(new SdkModificationProcessor() {
|
||||
@Override
|
||||
public void process(@NotNull Sdk sdk, @NotNull SdkModificator sdkModificator) {
|
||||
sdkModificator.addRoot(root, rootType);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void removeRoots(@NotNull final OrderRootType rootType) {
|
||||
modifySdk(new PySdkUpdater.SdkModificationProcessor() {
|
||||
@Override
|
||||
public void process(@NotNull Sdk sdk, @NotNull SdkModificator sdkModificator) {
|
||||
sdkModificator.removeRoots(rootType);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public abstract void commit();
|
||||
|
||||
public static synchronized PySdkUpdater fromSdkPath(@Nullable String sdkPath) {
|
||||
checkSingleton(sdkPath);
|
||||
return new JdkTableUpdater(sdkPath);
|
||||
}
|
||||
|
||||
public static synchronized PySdkUpdater fromSdkModificator(@NotNull Sdk sdk, @NotNull SdkModificator sdkModificator) {
|
||||
checkSingleton(sdk.getHomePath());
|
||||
return new SdkModificatorUpdater(sdk, sdkModificator);
|
||||
}
|
||||
|
||||
public static synchronized PySdkUpdater singletonJdkTableUpdater(@NotNull String sdkPath) {
|
||||
if (mySdkUpdaters.get(sdkPath) == null) {
|
||||
Sdk sdk = PythonSdkType.findSdkByPath(sdkPath);
|
||||
if (sdk != null) {
|
||||
mySdkUpdaters.put(sdkPath, new SingletonSdkModificatorUpdater(sdk, sdk.getSdkModificator()));
|
||||
} else {
|
||||
throw new PySdkNotFoundException();
|
||||
}
|
||||
}
|
||||
return mySdkUpdaters.get(sdkPath);
|
||||
}
|
||||
|
||||
|
||||
private static synchronized void checkSingleton(String sdkPath) {
|
||||
if (mySdkUpdaters.get(sdkPath) != null) {
|
||||
LOG.error("Changing more then one sdkModificator at a time");
|
||||
}
|
||||
}
|
||||
|
||||
private static synchronized void checkNotDisposed(String sdkPath) {
|
||||
if (mySdkUpdaters.get(sdkPath) == null) {
|
||||
LOG.error("sdk modificator is already committed, further changes will be discarded");
|
||||
}
|
||||
}
|
||||
|
||||
private static synchronized void disposeSingleton(String sdkPath) {
|
||||
mySdkUpdaters.put(sdkPath, null);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public abstract String getHomePath();
|
||||
|
||||
public abstract VirtualFile[] getRoots(OrderRootType rootType);
|
||||
|
||||
private static class JdkTableUpdater extends PySdkUpdater {
|
||||
private final String mySdkPath;
|
||||
|
||||
private JdkTableUpdater(@Nullable String path) {
|
||||
mySdkPath = path;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Sdk getSdk() {
|
||||
Sdk sdk = PythonSdkType.findSdkByPath(mySdkPath);
|
||||
if (sdk != null) {
|
||||
return sdk;
|
||||
}
|
||||
else {
|
||||
throw new PySdkNotFoundException();
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String getHomePath() {
|
||||
return mySdkPath;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VirtualFile[] getRoots(OrderRootType rootType) {
|
||||
return getSdk().getRootProvider().getFiles(rootType);
|
||||
}
|
||||
|
||||
public void modifySdk(@NotNull SdkModificationProcessor processor) {
|
||||
checkSingleton(mySdkPath);
|
||||
|
||||
ApplicationManager.getApplication().assertIsDispatchThread();
|
||||
|
||||
Sdk sdk = PythonSdkType.findSdkByPath(mySdkPath);
|
||||
|
||||
if (sdk != null) {
|
||||
SdkModificator modificator = getSdk().getSdkModificator();
|
||||
processor.process(sdk, modificator);
|
||||
modificator.commitChanges();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void commit() {
|
||||
// all changes are already committed
|
||||
}
|
||||
}
|
||||
|
||||
private static class SdkModificatorUpdater extends PySdkUpdater {
|
||||
private SdkModificator myModificator;
|
||||
private Sdk mySdk;
|
||||
|
||||
public SdkModificatorUpdater(@NotNull Sdk sdk, @NotNull SdkModificator modificator) {
|
||||
mySdk = sdk;
|
||||
myModificator = modificator;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Sdk getSdk() {
|
||||
return mySdk;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getHomePath() {
|
||||
return mySdk.getHomePath();
|
||||
}
|
||||
|
||||
@Override
|
||||
public VirtualFile[] getRoots(OrderRootType rootType) {
|
||||
return myModificator.getRoots(rootType);
|
||||
}
|
||||
|
||||
public void modifySdk(@NotNull SdkModificationProcessor processor) {
|
||||
processor.process(getSdk(), myModificator);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void commit() {
|
||||
checkSingleton(getHomePath());
|
||||
myModificator.commitChanges();
|
||||
}
|
||||
}
|
||||
|
||||
private static class SingletonSdkModificatorUpdater extends SdkModificatorUpdater {
|
||||
public SingletonSdkModificatorUpdater(@NotNull Sdk sdk, @NotNull SdkModificator modificator) {
|
||||
super(sdk, modificator);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void modifySdk(@NotNull SdkModificationProcessor processor) {
|
||||
checkNotDisposed(getHomePath());
|
||||
super.modifySdk(processor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void commit() {
|
||||
disposeSingleton(getHomePath());
|
||||
super.commit();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public interface SdkModificationProcessor {
|
||||
void process(@NotNull Sdk sdk, @NotNull SdkModificator sdkModificator);
|
||||
}
|
||||
|
||||
public static class PySdkNotFoundException extends RuntimeException {
|
||||
}
|
||||
}
|
||||
@@ -31,23 +31,18 @@ import com.intellij.notification.Notifications;
|
||||
import com.intellij.openapi.actionSystem.CommonDataKeys;
|
||||
import com.intellij.openapi.application.Application;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.application.ModalityState;
|
||||
import com.intellij.openapi.application.PathManager;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.fileChooser.FileChooserDescriptor;
|
||||
import com.intellij.openapi.module.Module;
|
||||
import com.intellij.openapi.module.ModuleUtilCore;
|
||||
import com.intellij.openapi.progress.ProgressIndicator;
|
||||
import com.intellij.openapi.progress.ProgressManager;
|
||||
import com.intellij.openapi.progress.Task;
|
||||
import com.intellij.openapi.project.DumbModePermission;
|
||||
import com.intellij.openapi.project.DumbService;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.projectRoots.*;
|
||||
import com.intellij.openapi.roots.ModuleRootManager;
|
||||
import com.intellij.openapi.roots.OrderRootType;
|
||||
import com.intellij.openapi.ui.Messages;
|
||||
import com.intellij.openapi.util.*;
|
||||
import com.intellij.openapi.util.Comparing;
|
||||
import com.intellij.openapi.util.Key;
|
||||
import com.intellij.openapi.util.Ref;
|
||||
import com.intellij.openapi.util.SystemInfo;
|
||||
import com.intellij.openapi.util.io.FileSystemUtil;
|
||||
import com.intellij.openapi.util.io.FileUtil;
|
||||
import com.intellij.openapi.util.text.CharFilter;
|
||||
@@ -64,12 +59,10 @@ import com.intellij.util.Consumer;
|
||||
import com.intellij.util.ExceptionUtil;
|
||||
import com.intellij.util.NullableConsumer;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.intellij.util.ui.UIUtil;
|
||||
import com.jetbrains.python.PyBundle;
|
||||
import com.jetbrains.python.PyNames;
|
||||
import com.jetbrains.python.PythonFileType;
|
||||
import com.jetbrains.python.PythonHelper;
|
||||
import com.jetbrains.python.codeInsight.userSkeletons.PyUserSkeletonsUtil;
|
||||
import com.jetbrains.python.facet.PythonFacetSettings;
|
||||
import com.jetbrains.python.packaging.PyCondaPackageManagerImpl;
|
||||
import com.jetbrains.python.psi.LanguageLevel;
|
||||
@@ -93,7 +86,6 @@ import java.io.IOException;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* @author yole
|
||||
@@ -111,8 +103,6 @@ public class PythonSdkType extends SdkType {
|
||||
|
||||
private static final Key<WeakReference<Component>> SDK_CREATOR_COMPONENT_KEY = Key.create("#com.jetbrains.python.sdk.creatorComponent");
|
||||
|
||||
private Set<String> scheduledToRefresh = ContainerUtil.newConcurrentSet();
|
||||
|
||||
public static PythonSdkType getInstance() {
|
||||
return SdkType.findInstance(PythonSdkType.class);
|
||||
}
|
||||
@@ -505,7 +495,7 @@ public class PythonSdkType extends SdkType {
|
||||
return FileUtil.toSystemDependentName(path);
|
||||
}
|
||||
|
||||
public void setupSdkPaths(@NotNull final Sdk sdk) {
|
||||
public void setupSdkPaths(@NotNull Sdk sdk) {
|
||||
final Project project;
|
||||
final WeakReference<Component> ownerComponentRef = sdk.getUserData(SDK_CREATOR_COMPONENT_KEY);
|
||||
final Component ownerComponent = SoftReference.dereference(ownerComponentRef);
|
||||
@@ -515,13 +505,7 @@ public class PythonSdkType extends SdkType {
|
||||
else {
|
||||
project = CommonDataKeys.PROJECT.getData(DataManager.getInstance().getDataContext());
|
||||
}
|
||||
|
||||
DumbService.allowStartingDumbModeInside(DumbModePermission.MAY_START_BACKGROUND, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
setupSdkPaths(sdk, project, ownerComponent);
|
||||
}
|
||||
});
|
||||
PythonSdkUpdater.updateOrShowError(sdk, null, project, ownerComponent);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -529,131 +513,6 @@ public class PythonSdkType extends SdkType {
|
||||
return true; // run setupSdkPaths only once (from PythonSdkDetailsStep). Skip this from showCustomCreateUI
|
||||
}
|
||||
|
||||
public void setupSdkPaths(@NotNull final Sdk sdk,
|
||||
@Nullable final Project project,
|
||||
@Nullable final Component ownerComponent,
|
||||
@NotNull final SdkModificator sdkModificator) {
|
||||
scheduledToRefresh.add(sdk.getHomePath());
|
||||
doSetupSdkPaths(project, ownerComponent, PySdkUpdater.fromSdkModificator(sdk, sdkModificator));
|
||||
}
|
||||
|
||||
|
||||
public void setupSdkPaths(final Sdk sdk, @Nullable final Project project, @Nullable final Component ownerComponent) {
|
||||
scheduledToRefresh.add(sdk.getHomePath());
|
||||
ApplicationManager.getApplication().invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
PySdkUpdater updater = PySdkUpdater.singletonJdkTableUpdater(sdk.getHomePath());
|
||||
final boolean success = doSetupSdkPaths(project, ownerComponent, updater);
|
||||
|
||||
if (!success) {
|
||||
Messages.showErrorDialog(
|
||||
project,
|
||||
PyBundle.message("MSG.cant.setup.sdk.$0", FileUtil.toSystemDependentName(sdk.getSdkModificator().getHomePath())),
|
||||
PyBundle.message("MSG.title.bad.sdk")
|
||||
);
|
||||
}
|
||||
}
|
||||
catch (PySdkUpdater.PySdkNotFoundException e) {
|
||||
// sdk was removed from sdk table so no need to setup paths
|
||||
}
|
||||
}
|
||||
}, ModalityState.NON_MODAL);
|
||||
}
|
||||
|
||||
public void setupSdkPathsImmediately(final Sdk sdk, @Nullable final Project project) {
|
||||
scheduledToRefresh.add(sdk.getHomePath());
|
||||
final boolean success = doSetupSdkPaths(project, null, PySdkUpdater.fromSdkPath(sdk.getHomePath()));
|
||||
|
||||
if (!success) {
|
||||
Messages.showErrorDialog(
|
||||
project,
|
||||
PyBundle.message("MSG.cant.setup.sdk.$0", FileUtil.toSystemDependentName(sdk.getSdkModificator().getHomePath())),
|
||||
PyBundle.message("MSG.title.bad.sdk")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean doSetupSdkPaths(@Nullable final Project project,
|
||||
@Nullable final Component ownerComponent,
|
||||
@NotNull final PySdkUpdater sdkUpdater) {
|
||||
if (isRemote(sdkUpdater.getSdk()) && project == null && ownerComponent == null) {
|
||||
LOG.error("For refreshing skeletons of remote SDK, either project or owner component must be specified");
|
||||
}
|
||||
final ProgressManager progressManager = ProgressManager.getInstance();
|
||||
boolean sdkPathsUpdated = UIUtil.<Boolean>invokeAndWaitIfNeeded(
|
||||
new Computable<Boolean>() {
|
||||
@Override
|
||||
public Boolean compute() {
|
||||
return updateSdkPaths(sdkUpdater);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
final Application application = ApplicationManager.getApplication();
|
||||
if (sdkPathsUpdated && !application.isUnitTestMode()) {
|
||||
application.invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (!scheduledToRefresh.contains(sdkUpdater.getHomePath())) {
|
||||
return;
|
||||
}
|
||||
scheduledToRefresh.remove(sdkUpdater.getHomePath());
|
||||
progressManager.run(new Task.Backgroundable(project, PyBundle.message("sdk.gen.updating.skels"), false) {
|
||||
@Override
|
||||
public void run(@NotNull ProgressIndicator indicator) {
|
||||
try {
|
||||
PythonSdkUpdater.updateSdk(project, ownerComponent, sdkUpdater);
|
||||
}
|
||||
catch (InvalidSdkException e) {
|
||||
// If the SDK is invalid, the user should worry about the SDK itself, not about skeletons generation errors
|
||||
if (isVagrant(sdkUpdater.getSdk()) || isDocker(sdkUpdater.getSdk())) {
|
||||
notifyRemoteSdkSkeletonsFail(e, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
setupSdkPaths(sdkUpdater.getSdk(), project, ownerComponent);
|
||||
}
|
||||
});
|
||||
}
|
||||
else if (!isInvalid(sdkUpdater.getSdk())) {
|
||||
LOG.error(e);
|
||||
}
|
||||
} finally {
|
||||
UIUtil.invokeAndWaitIfNeeded(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
sdkUpdater.commit();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
return sdkPathsUpdated;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static Boolean updateSdkPaths(@NotNull PySdkUpdater sdkUpdater) {
|
||||
sdkUpdater.modifySdk(new PySdkUpdater.SdkModificationProcessor() {
|
||||
@Override
|
||||
public void process(@NotNull Sdk sdk,
|
||||
@NotNull SdkModificator sdkModificator) {
|
||||
sdkModificator.removeAllRoots();
|
||||
}
|
||||
});
|
||||
try {
|
||||
updateSdkRootsFromSysPath(sdkUpdater);
|
||||
updateUserAddedPaths(sdkUpdater);
|
||||
return true;
|
||||
}
|
||||
catch (InvalidSdkException ignored) {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static void notifyRemoteSdkSkeletonsFail(final InvalidSdkException e, @Nullable final Runnable restartAction) {
|
||||
NotificationListener notificationListener;
|
||||
String notificationMessage;
|
||||
@@ -718,95 +577,6 @@ public class PythonSdkType extends SdkType {
|
||||
*/
|
||||
public static final OrderRootType BUILTIN_ROOT_TYPE = OrderRootType.CLASSES;
|
||||
|
||||
private final static Pattern PYTHON_NN_RE = Pattern.compile("python\\d\\.\\d.*");
|
||||
|
||||
public static void updateSdkRootsFromSysPath(PySdkUpdater sdkUpdater)
|
||||
throws InvalidSdkException {
|
||||
Application application = ApplicationManager.getApplication();
|
||||
boolean not_in_unit_test_mode = (application != null && !application.isUnitTestMode());
|
||||
|
||||
String sdkHome = sdkUpdater.getHomePath();
|
||||
assert sdkHome != null;
|
||||
final String sep = File.separator;
|
||||
// Add folders from sys.path
|
||||
if (!PySdkUtil.isRemote(sdkUpdater.getSdk())) { //no sense to add roots of remote sdk
|
||||
final List<String> paths = getSysPath(sdkHome);
|
||||
if (paths.size() > 0) {
|
||||
// add every path as root.
|
||||
for (String path : paths) {
|
||||
if (!path.contains(sep)) continue; // TODO: interpret possible 'special' paths reasonably
|
||||
addSdkRoot(sdkUpdater, path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PyUserSkeletonsUtil.addUserSkeletonsRoot(sdkUpdater);
|
||||
addSkeletonsRoot(sdkUpdater, sdkHome);
|
||||
|
||||
if (not_in_unit_test_mode) {
|
||||
File venv_root = getVirtualEnvRoot(sdkHome);
|
||||
if (venv_root != null && venv_root.isDirectory()) {
|
||||
File lib_root = new File(venv_root, "lib");
|
||||
if (lib_root.isDirectory()) {
|
||||
String[] inside = lib_root.list();
|
||||
for (String s : inside) {
|
||||
if (PYTHON_NN_RE.matcher(s).matches()) {
|
||||
File py_lib_root = new File(lib_root, s);
|
||||
if (new File(py_lib_root, "no-global-site-packages.txt").exists()) return; // don't add hardcoded paths
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
addHardcodedPaths(sdkUpdater);
|
||||
}
|
||||
}
|
||||
|
||||
public static void updateUserAddedPaths(PySdkUpdater sdkUpdater)
|
||||
throws InvalidSdkException {
|
||||
SdkAdditionalData data = sdkUpdater.getSdk().getSdkAdditionalData();
|
||||
if (data instanceof PythonSdkAdditionalData) {
|
||||
for (VirtualFile file : ((PythonSdkAdditionalData)data).getAddedPathFiles()) {
|
||||
addSdkRoot(sdkUpdater, file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void addSkeletonsRoot(@NotNull PySdkUpdater sdkUpdater, String sdkHome) {
|
||||
@NonNls final String skeletonsPath = getSkeletonsPath(PathManager.getSystemPath(), sdkHome);
|
||||
new File(skeletonsPath).mkdirs();
|
||||
final VirtualFile builtins_root = LocalFileSystem.getInstance().refreshAndFindFileByPath(skeletonsPath);
|
||||
assert builtins_root != null : "Cannot find skeletons path " + skeletonsPath + " in VFS";
|
||||
sdkUpdater.addRoot(builtins_root, BUILTIN_ROOT_TYPE);
|
||||
}
|
||||
|
||||
protected static void addHardcodedPaths(PySdkUpdater sdkUpdater) {
|
||||
// Add python-django installed as package in Linux
|
||||
// NOTE: fragile and arbitrary
|
||||
if (SystemInfo.isLinux) {
|
||||
final VirtualFile file = LocalFileSystem.getInstance().findFileByPath("/usr/lib/python-django");
|
||||
if (file != null) {
|
||||
sdkUpdater.addRoot(file, OrderRootType.CLASSES);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void addSdkRoot(PySdkUpdater sdkUpdater, String path) {
|
||||
final VirtualFile file = LocalFileSystem.getInstance().refreshAndFindFileByPath(path);
|
||||
if (file != null) {
|
||||
addSdkRoot(sdkUpdater, file);
|
||||
}
|
||||
else {
|
||||
LOG.info("Bogus sys.path entry " + path);
|
||||
}
|
||||
}
|
||||
|
||||
private static void addSdkRoot(@NotNull PySdkUpdater sdkUpdater, final @NotNull VirtualFile child) {
|
||||
// NOTE: Files marked as library sources are not considered part of project source. Since the directory of the project the
|
||||
// user is working on is included in PYTHONPATH with many configurations (e.g. virtualenv), we must not mark SDK paths as
|
||||
// library sources, only as classes.
|
||||
sdkUpdater.addRoot(getSdkRootVirtualFile(child), OrderRootType.CLASSES);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static VirtualFile getSdkRootVirtualFile(@NotNull VirtualFile path) {
|
||||
String suffix = path.getExtension();
|
||||
|
||||
@@ -15,12 +15,14 @@
|
||||
*/
|
||||
package com.jetbrains.python.sdk;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.intellij.openapi.application.Application;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.application.ModalityState;
|
||||
import com.intellij.openapi.application.PathManager;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.extensions.Extensions;
|
||||
import com.intellij.openapi.module.Module;
|
||||
import com.intellij.openapi.module.ModuleManager;
|
||||
import com.intellij.openapi.progress.ProgressIndicator;
|
||||
@@ -28,17 +30,20 @@ import com.intellij.openapi.progress.ProgressManager;
|
||||
import com.intellij.openapi.progress.Task;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.projectRoots.Sdk;
|
||||
import com.intellij.openapi.projectRoots.SdkTypeId;
|
||||
import com.intellij.openapi.projectRoots.SdkAdditionalData;
|
||||
import com.intellij.openapi.projectRoots.SdkModificator;
|
||||
import com.intellij.openapi.roots.OrderRootType;
|
||||
import com.intellij.openapi.startup.StartupActivity;
|
||||
import com.intellij.openapi.ui.Messages;
|
||||
import com.intellij.openapi.util.io.FileUtil;
|
||||
import com.intellij.openapi.util.io.FileUtilRt;
|
||||
import com.intellij.openapi.vfs.LocalFileSystem;
|
||||
import com.intellij.openapi.vfs.StandardFileSystems;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.util.PathMappingSettings;
|
||||
import com.intellij.util.ui.UIUtil;
|
||||
import com.intellij.util.concurrency.BlockingSet;
|
||||
import com.jetbrains.python.PyBundle;
|
||||
import com.jetbrains.python.codeInsight.userSkeletons.PyUserSkeletonsUtil;
|
||||
import com.jetbrains.python.psi.PyUtil;
|
||||
import com.jetbrains.python.remote.PyRemoteSdkAdditionalDataBase;
|
||||
import com.jetbrains.python.sdk.skeletons.PySkeletonRefresher;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -50,72 +55,48 @@ import java.util.*;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A component that initiates a refresh of all project's Python SDKs.
|
||||
* Delegates most of the work to PythonSdkType.
|
||||
* <br/>
|
||||
* Refreshes all project's Python SDKs.
|
||||
*
|
||||
* @author vlan
|
||||
* @author yole
|
||||
*/
|
||||
public class PythonSdkUpdater implements StartupActivity {
|
||||
private static final Logger LOG = Logger.getInstance("#com.jetbrains.python.sdk.PythonSdkUpdater");
|
||||
public static final int INITIAL_ACTIVITY_DELAY = 7000;
|
||||
|
||||
public static PythonSdkUpdater getInstance() {
|
||||
final StartupActivity[] extensions = Extensions.getExtensions(StartupActivity.POST_STARTUP_ACTIVITY);
|
||||
for (StartupActivity extension : extensions) {
|
||||
if (extension instanceof PythonSdkUpdater) {
|
||||
return (PythonSdkUpdater)extension;
|
||||
}
|
||||
}
|
||||
throw new UnsupportedOperationException("could not find self");
|
||||
}
|
||||
private static final Object ourLock = new Object();
|
||||
private static final Set<String> ourScheduledToRefresh = Sets.newHashSet();
|
||||
private static final BlockingSet<String> ourUnderRefresh = new BlockingSet<String>();
|
||||
|
||||
/**
|
||||
* Refreshes the SDKs of the modules for the open project after some delay.
|
||||
*/
|
||||
@Override
|
||||
public void runActivity(@NotNull final Project project) {
|
||||
final Application application = ApplicationManager.getApplication();
|
||||
if (application.isUnitTestMode()) {
|
||||
return;
|
||||
}
|
||||
|
||||
updateActiveSdks(project, 7000);
|
||||
}
|
||||
|
||||
public static void updateActiveSdks(@NotNull final Project project, final int delay) {
|
||||
final Set<Sdk> sdksToUpdate = new HashSet<Sdk>();
|
||||
for (Module module : ModuleManager.getInstance(project).getModules()) {
|
||||
final Sdk sdk = PythonSdkType.findPythonSdk(module);
|
||||
if (sdk != null) {
|
||||
final SdkTypeId sdkType = sdk.getSdkType();
|
||||
if (sdkType instanceof PythonSdkType) {
|
||||
sdksToUpdate.add(sdk);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: everything is run later on the AWT thread
|
||||
if (!sdksToUpdate.isEmpty()) {
|
||||
updateSdks(project, delay, sdksToUpdate);
|
||||
}
|
||||
}
|
||||
|
||||
private static void updateSdks(final Project project, final int delay, final Set<Sdk> sdksToUpdate) {
|
||||
ApplicationManager.getApplication().executeOnPooledThread(new Runnable() {
|
||||
application.executeOnPooledThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (delay > 0) {
|
||||
try {
|
||||
Thread.sleep(delay); // wait until all short-term disk-hitting activity ceases
|
||||
}
|
||||
catch (InterruptedException ignore) {
|
||||
}
|
||||
try {
|
||||
Thread.sleep(INITIAL_ACTIVITY_DELAY);
|
||||
}
|
||||
// update skeletons
|
||||
ApplicationManager.getApplication().invokeLater(new Runnable() {
|
||||
catch (InterruptedException ignored) {
|
||||
}
|
||||
application.invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
ProgressManager.getInstance().run(new Task.Backgroundable(project, PyBundle.message("sdk.gen.updating.skels"), false) {
|
||||
ProgressManager.getInstance().run(new Task.Backgroundable(project, "Updating Python Paths", false) {
|
||||
@Override
|
||||
public void run(@NotNull ProgressIndicator indicator) {
|
||||
for (final Sdk sdk : sdksToUpdate) {
|
||||
updateSdk(sdk, project);
|
||||
final Project project = getProject();
|
||||
if (project.isDisposed()) {
|
||||
return;
|
||||
}
|
||||
for (final Sdk sdk : getPythonSdks(project)) {
|
||||
update(sdk, null, project, null);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -125,180 +106,299 @@ public class PythonSdkUpdater implements StartupActivity {
|
||||
});
|
||||
}
|
||||
|
||||
public static void updateSdk(final Sdk sdk, final Project project) {
|
||||
try {
|
||||
LOG.info("Performing background update of skeletons for SDK " + sdk.getHomePath());
|
||||
updateSdk(project, null, PySdkUpdater.fromSdkPath(sdk.getHomePath()));
|
||||
/**
|
||||
* Updates the paths of an SDK and regenerates its skeletons as a background task.
|
||||
*
|
||||
* May be invoked from any thread. May freeze the current thread while evaluating sys.path.
|
||||
*
|
||||
* For a local SDK it commits all the SDK paths and runs a background task for updating skeletons. For a remote SDK it runs a background
|
||||
* task for updating skeletons that saves path mappings in the additional SDK data and then commits all the SDK paths.
|
||||
*
|
||||
* The commit of the changes in the SDK happens in the AWT thread while the current thread is waiting the result.
|
||||
*
|
||||
* @param sdkModificator if null then it tries to get an SDK modifier from the SDK table, falling back to the modifier of the SDK
|
||||
* passed as an argument accessed from the AWT thread
|
||||
* @return false if there was an immediate problem updating the SDK. Other problems are reported as log entries and balloons.
|
||||
*/
|
||||
public static boolean update(@NotNull Sdk sdk, @Nullable SdkModificator sdkModificator, @Nullable final Project project,
|
||||
@Nullable final Component ownerComponent) {
|
||||
final String homePath = sdk.getHomePath();
|
||||
synchronized (ourLock) {
|
||||
ourScheduledToRefresh.add(homePath);
|
||||
}
|
||||
catch (PySdkUpdater.PySdkNotFoundException e) {
|
||||
LOG.info("Sdk " + sdk.getName() + " was removed during update process.");
|
||||
if (!updateLocalSdkPaths(sdk, sdkModificator)) {
|
||||
return false;
|
||||
}
|
||||
catch (InvalidSdkException e) {
|
||||
if (PythonSdkType.isVagrant(sdk) || PythonSdkType.isDocker(sdk)) {
|
||||
PythonSdkType.notifyRemoteSdkSkeletonsFail(e, new Runnable() {
|
||||
ApplicationManager.getApplication().invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
synchronized (ourLock) {
|
||||
if (!ourScheduledToRefresh.contains(homePath)) {
|
||||
return;
|
||||
}
|
||||
ourScheduledToRefresh.remove(homePath);
|
||||
}
|
||||
ProgressManager.getInstance().run(new Task.Backgroundable(project, PyBundle.message("sdk.gen.updating.skeletons"), false) {
|
||||
@Override
|
||||
public void run() {
|
||||
updateSdk(sdk, project);
|
||||
public void run(@NotNull ProgressIndicator indicator) {
|
||||
final Project project = getProject();
|
||||
final Sdk sdk = PythonSdkType.findSdkByPath(homePath);
|
||||
if (sdk != null) {
|
||||
ourUnderRefresh.put(homePath);
|
||||
try {
|
||||
final String skeletonsPath = PythonSdkType.getSkeletonsPath(PathManager.getSystemPath(), homePath);
|
||||
try {
|
||||
if (PythonSdkType.isRemote(sdk) && project == null && ownerComponent == null) {
|
||||
LOG.error("For refreshing skeletons of remote SDK, either project or owner component must be specified");
|
||||
}
|
||||
LOG.info("Performing background update of skeletons for SDK " + sdk.getHomePath());
|
||||
PySkeletonRefresher.refreshSkeletonsOfSdk(project, ownerComponent, skeletonsPath, sdk);
|
||||
updateRemoteSdkPaths(sdk);
|
||||
}
|
||||
catch (InvalidSdkException e) {
|
||||
if (PythonSdkType.isVagrant(sdk) || PythonSdkType.isDocker(sdk)) {
|
||||
PythonSdkType.notifyRemoteSdkSkeletonsFail(e, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
final Sdk sdk = PythonSdkType.findSdkByPath(homePath);
|
||||
if (sdk != null) {
|
||||
update(sdk, null, project, ownerComponent);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
else if (!PythonSdkType.isInvalid(sdk)) {
|
||||
LOG.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally {
|
||||
ourUnderRefresh.remove(homePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
else if (!PythonSdkType.isInvalid(sdk)) {
|
||||
LOG.error(e);
|
||||
}
|
||||
}, ModalityState.NON_MODAL);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the paths of an SDK and regenerates its skeletons as a background task. Shows an error message if the update fails.
|
||||
*
|
||||
* @see {@link #update(Sdk, SdkModificator, Project, Component)}
|
||||
*/
|
||||
public static void updateOrShowError(@NotNull Sdk sdk, @Nullable SdkModificator sdkModificator, @Nullable Project project,
|
||||
@Nullable Component ownerComponent) {
|
||||
final boolean success = update(sdk, sdkModificator, project, ownerComponent);
|
||||
if (!success) {
|
||||
final String homePath = sdk.getHomePath();
|
||||
final String sdkName = homePath != null ? homePath : sdk.getName();
|
||||
Messages.showErrorDialog(project,
|
||||
PyBundle.message("MSG.cant.setup.sdk.$0", FileUtil.toSystemDependentName(sdkName)),
|
||||
PyBundle.message("MSG.title.bad.sdk"));
|
||||
}
|
||||
}
|
||||
|
||||
public static void updateSdk(@Nullable Project project, @Nullable Component ownerComponent, @NotNull final PySdkUpdater sdkUpdater)
|
||||
throws InvalidSdkException {
|
||||
String skeletonsPath = PythonSdkType.getSkeletonsPath(PathManager.getSystemPath(), sdkUpdater.getHomePath());
|
||||
|
||||
PySkeletonRefresher.refreshSkeletonsOfSdk(project, ownerComponent, skeletonsPath, sdkUpdater); // NOTE: whole thing would need a rename
|
||||
if (!PySdkUtil.isRemote(sdkUpdater.getSdk())) {
|
||||
updateSysPath(sdkUpdater);
|
||||
/**
|
||||
* Updates the paths of a local SDK.
|
||||
*
|
||||
* May be invoked from any thread. May freeze the current thread while evaluating sys.path.
|
||||
*/
|
||||
private static boolean updateLocalSdkPaths(@NotNull Sdk sdk, @Nullable SdkModificator sdkModificator) {
|
||||
if (!PythonSdkType.isRemote(sdk)) {
|
||||
final List<VirtualFile> localSdkPaths;
|
||||
try {
|
||||
localSdkPaths = getLocalSdkPaths(sdk);
|
||||
}
|
||||
catch (InvalidSdkException e) {
|
||||
if (!PythonSdkType.isInvalid(sdk)) {
|
||||
LOG.error(e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
commitSdkPathsIfChanged(sdk, sdkModificator, localSdkPaths);
|
||||
}
|
||||
else {
|
||||
PyRemoteSdkAdditionalDataBase remoteSdkData = (PyRemoteSdkAdditionalDataBase)sdkUpdater.getSdk().getSdkAdditionalData();
|
||||
assert remoteSdkData != null;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the paths of a remote SDK.
|
||||
*
|
||||
* Requires the skeletons refresh steps to be run before it in order to get remote paths mappings in the additional SDK data.
|
||||
*
|
||||
* You may invoke it from any thread. Blocks until the commit is done in the AWT thread.
|
||||
*/
|
||||
private static void updateRemoteSdkPaths(Sdk sdk) {
|
||||
if (PythonSdkType.isRemote(sdk)) {
|
||||
final List<VirtualFile> remoteSdkPaths = getRemoteSdkPaths(sdk);
|
||||
commitSdkPathsIfChanged(sdk, null, remoteSdkPaths);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all the paths for a local SDK.
|
||||
*/
|
||||
@NotNull
|
||||
private static List<VirtualFile> getLocalSdkPaths(@NotNull Sdk sdk) throws InvalidSdkException {
|
||||
return ImmutableList.<VirtualFile>builder()
|
||||
.addAll(evaluateSysPath(sdk))
|
||||
.addAll(getSkeletonsPaths(sdk))
|
||||
.addAll(getUserAddedPaths(sdk))
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all the paths for a remote SDK.
|
||||
*
|
||||
* Requires the skeletons refresh steps to be run before it in order to get remote paths mappings in the additional SDK data.
|
||||
*/
|
||||
@NotNull
|
||||
private static List<VirtualFile> getRemoteSdkPaths(@NotNull Sdk sdk) {
|
||||
return ImmutableList.<VirtualFile>builder()
|
||||
.addAll(getRemoteSdkMappedPaths(sdk))
|
||||
.addAll(getSkeletonsPaths(sdk))
|
||||
.addAll(getUserAddedPaths(sdk))
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all the paths manually added to an SDK by the user.
|
||||
*/
|
||||
@NotNull
|
||||
private static List<VirtualFile> getUserAddedPaths(@NotNull Sdk sdk) {
|
||||
final SdkAdditionalData additionalData = sdk.getSdkAdditionalData();
|
||||
final PythonSdkAdditionalData pythonAdditionalData = PyUtil.as(additionalData, PythonSdkAdditionalData.class);
|
||||
return pythonAdditionalData != null ? Lists.newArrayList(pythonAdditionalData.getAddedPathFiles()) :
|
||||
Collections.<VirtualFile>emptyList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns local paths for a remote SDK that have been mapped to remote paths during the skeleton refresh step.
|
||||
*
|
||||
* Returns all the existing paths except those manually excluded by the user.
|
||||
*/
|
||||
@NotNull
|
||||
private static List<VirtualFile> getRemoteSdkMappedPaths(@NotNull Sdk sdk) {
|
||||
final SdkAdditionalData additionalData = sdk.getSdkAdditionalData();
|
||||
if (additionalData instanceof PyRemoteSdkAdditionalDataBase) {
|
||||
final PyRemoteSdkAdditionalDataBase remoteSdkData = (PyRemoteSdkAdditionalDataBase)additionalData;
|
||||
final List<String> paths = Lists.newArrayList();
|
||||
for (PathMappingSettings.PathMapping mapping : remoteSdkData.getPathMappings().getPathMappings()) {
|
||||
paths.add(mapping.getLocalRoot());
|
||||
}
|
||||
return filterRootPaths(sdk, paths);
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
UIUtil.invokeAndWaitIfNeeded(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
updateSdkPath(sdkUpdater, paths);
|
||||
/**
|
||||
* Filters valid paths from an initial set of Python paths and returns them as virtual files.
|
||||
*/
|
||||
@NotNull
|
||||
private static List<VirtualFile> filterRootPaths(@NotNull Sdk sdk, @NotNull List<String> paths) {
|
||||
final PythonSdkAdditionalData pythonAdditionalData = PyUtil.as(sdk.getSdkAdditionalData(), PythonSdkAdditionalData.class);
|
||||
final Collection<VirtualFile> excludedPaths = pythonAdditionalData != null ? pythonAdditionalData.getExcludedPathFiles() :
|
||||
Collections.<VirtualFile>emptyList();
|
||||
final List<VirtualFile> results = Lists.newArrayList();
|
||||
for (String path : paths) {
|
||||
if (path != null && !FileUtilRt.extensionEquals(path, "egg-info")) {
|
||||
final VirtualFile virtualFile = StandardFileSystems.local().refreshAndFindFileByPath(path);
|
||||
if (virtualFile != null) {
|
||||
final VirtualFile rootFile = PythonSdkType.getSdkRootVirtualFile(virtualFile);
|
||||
if (!excludedPaths.contains(rootFile)) {
|
||||
results.add(virtualFile);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static void updateSysPath(@NotNull final PySdkUpdater sdkUpdater) throws InvalidSdkException {
|
||||
long start_time = System.currentTimeMillis();
|
||||
final List<String> sysPath = PythonSdkType.getSysPath(sdkUpdater.getHomePath());
|
||||
final VirtualFile file = PyUserSkeletonsUtil.getUserSkeletonsDirectory();
|
||||
if (file != null) {
|
||||
sysPath.add(file.getPath());
|
||||
}
|
||||
UIUtil.invokeAndWaitIfNeeded(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
updateSdkPath(sdkUpdater, sysPath);
|
||||
}
|
||||
});
|
||||
LOG.info("Updating sys.path took " + (System.currentTimeMillis() - start_time) + " ms");
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates SDK based on sys.path and cleans legacy information up.
|
||||
*/
|
||||
private static void updateSdkPath(@NotNull PySdkUpdater sdkUpdater, @NotNull List<String> sysPath) {
|
||||
addNewSysPathEntries(sdkUpdater, sysPath);
|
||||
removeSourceRoots(sdkUpdater);
|
||||
removeDuplicateClassRoots(sdkUpdater);
|
||||
updateBinarySkeletonsPath(sdkUpdater);
|
||||
updateUserSkeletonsPath(sdkUpdater);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds new CLASSES entries found in sys.path.
|
||||
*/
|
||||
private static boolean addNewSysPathEntries(@NotNull PySdkUpdater sdkUpdater, @NotNull List<String> sysPath) {
|
||||
final List<VirtualFile> oldRoots = Arrays.asList(sdkUpdater.getSdk().getRootProvider().getFiles(OrderRootType.CLASSES));
|
||||
PythonSdkAdditionalData additionalData = sdkUpdater.getSdk().getSdkAdditionalData() instanceof PythonSdkAdditionalData
|
||||
? (PythonSdkAdditionalData)sdkUpdater.getSdk().getSdkAdditionalData()
|
||||
: null;
|
||||
List<String> newRoots = new ArrayList<String>();
|
||||
for (String root : sysPath) {
|
||||
if (new File(root).exists() &&
|
||||
!FileUtilRt.extensionEquals(root, "egg-info") &&
|
||||
(additionalData == null || !wasOldRoot(root, additionalData.getExcludedPathFiles())) &&
|
||||
!wasOldRoot(root, oldRoots)) {
|
||||
newRoots.add(root);
|
||||
}
|
||||
LOG.info("Bogus sys.path entry " + path);
|
||||
}
|
||||
if (!newRoots.isEmpty()) {
|
||||
for (String root : newRoots) {
|
||||
PythonSdkType.addSdkRoot(sdkUpdater, root);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes duplicate roots that have been added as the result of a bug with *.egg handling.
|
||||
* Returns the paths of the binary skeletons and user skeletons for an SDK.
|
||||
*/
|
||||
private static boolean removeDuplicateClassRoots(@NotNull PySdkUpdater sdkUpdater) {
|
||||
final List<VirtualFile> sourceRoots = Arrays.asList(sdkUpdater.getRoots(OrderRootType.CLASSES));
|
||||
final LinkedHashSet<VirtualFile> uniqueRoots = new LinkedHashSet<VirtualFile>(sourceRoots);
|
||||
if (uniqueRoots.size() != sourceRoots.size()) {
|
||||
sdkUpdater.removeRoots(OrderRootType.CLASSES);
|
||||
for (VirtualFile root : uniqueRoots) {
|
||||
sdkUpdater.addRoot(root, OrderRootType.CLASSES);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes legacy SOURCES entries in Python SDK tables (PY-2891).
|
||||
*/
|
||||
private static boolean removeSourceRoots(@NotNull PySdkUpdater sdkUpdater) {
|
||||
final VirtualFile[] sourceRoots = sdkUpdater.getRoots(OrderRootType.SOURCES);
|
||||
if (sourceRoots.length > 0) {
|
||||
sdkUpdater.removeRoots(OrderRootType.SOURCES);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates user skeletons path in the Python SDK table.
|
||||
*/
|
||||
private static void updateUserSkeletonsPath(@NotNull PySdkUpdater sdkUpdater) {
|
||||
updateSkeletonsPath(sdkUpdater, PyUserSkeletonsUtil.getUserSkeletonsDirectory(), PyUserSkeletonsUtil.USER_SKELETONS_DIR,
|
||||
"User skeletons");
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates binary skeletons path in the Python SDK table.
|
||||
*/
|
||||
private static void updateBinarySkeletonsPath(@NotNull PySdkUpdater sdkUpdater) {
|
||||
final String skeletonsPath = PythonSdkType.getSkeletonsPath(PathManager.getSystemPath(), sdkUpdater.getHomePath());
|
||||
@NotNull
|
||||
private static List<VirtualFile> getSkeletonsPaths(@NotNull Sdk sdk) {
|
||||
final List<VirtualFile> results = Lists.newArrayList();
|
||||
final String skeletonsPath = PythonSdkType.getSkeletonsPath(PathManager.getSystemPath(), sdk.getHomePath());
|
||||
if (skeletonsPath != null) {
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
new File(skeletonsPath).mkdirs();
|
||||
final VirtualFile skeletonsDir = StandardFileSystems.local().refreshAndFindFileByPath(skeletonsPath);
|
||||
if (skeletonsDir != null) {
|
||||
updateSkeletonsPath(sdkUpdater, skeletonsDir, PythonSdkType.SKELETON_DIR_NAME, "Binary skeletons");
|
||||
results.add(skeletonsDir);
|
||||
LOG.info("Binary skeletons directory for SDK \"" + sdk.getName() + "\" (" + sdk.getHomePath() + "): " +
|
||||
skeletonsDir.getPath());
|
||||
}
|
||||
}
|
||||
final VirtualFile userSkeletonsDir = PyUserSkeletonsUtil.getUserSkeletonsDirectory();
|
||||
if (userSkeletonsDir != null) {
|
||||
results.add(userSkeletonsDir);
|
||||
LOG.info("User skeletons directory for SDK \"" + sdk.getName() + "\" (" + sdk.getHomePath() + "): " +
|
||||
userSkeletonsDir.getPath());
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
private static void updateSkeletonsPath(@NotNull PySdkUpdater sdkUpdater,
|
||||
@Nullable VirtualFile skeletonsDir,
|
||||
@NotNull String skeletonsDirPattern,
|
||||
@NotNull String skeletonsTitle) {
|
||||
if (skeletonsDir != null) {
|
||||
LOG.info(skeletonsTitle + " directory for SDK \"" + sdkUpdater.getSdk().getName() + "\" (" + sdkUpdater.getHomePath() + "): " +
|
||||
skeletonsDir.getPath());
|
||||
final List<VirtualFile> sourceRoots = Arrays.asList(sdkUpdater.getRoots(OrderRootType.CLASSES));
|
||||
sdkUpdater.removeRoots(OrderRootType.CLASSES);
|
||||
for (final VirtualFile root : sourceRoots) {
|
||||
if (!root.getPath().contains(skeletonsDirPattern)) {
|
||||
sdkUpdater.addRoot(root, OrderRootType.CLASSES);
|
||||
/**
|
||||
* Evaluates sys.path by running the Python interpreter from a local SDK.
|
||||
*
|
||||
* Returns all the existing paths except those manually excluded by the user.
|
||||
*/
|
||||
@NotNull
|
||||
private static List<VirtualFile> evaluateSysPath(@NotNull Sdk sdk) throws InvalidSdkException {
|
||||
if (PythonSdkType.isRemote(sdk)) {
|
||||
throw new IllegalArgumentException("Cannot evaluate sys.path for remote Python interpreter " + sdk);
|
||||
}
|
||||
final long startTime = System.currentTimeMillis();
|
||||
final List<String> sysPath = PythonSdkType.getSysPath(sdk.getHomePath());
|
||||
LOG.info("Updating sys.path took " + (System.currentTimeMillis() - startTime) + " ms");
|
||||
return filterRootPaths(sdk, sysPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Commits new SDK paths using an SDK modificator if the paths have been changed.
|
||||
*
|
||||
* You may invoke it from any thread. Blocks until the commit is done in the AWT thread.
|
||||
*/
|
||||
private static void commitSdkPathsIfChanged(@NotNull Sdk sdk,
|
||||
@Nullable final SdkModificator sdkModificator,
|
||||
@NotNull final List<VirtualFile> sdkPaths) {
|
||||
final String homePath = sdk.getHomePath();
|
||||
final SdkModificator modificatorToGetRoots = sdkModificator != null ? sdkModificator : sdk.getSdkModificator();
|
||||
final List<VirtualFile> currentSdkPaths = Arrays.asList(modificatorToGetRoots.getRoots(OrderRootType.CLASSES));
|
||||
if (!Sets.difference(Sets.newHashSet(sdkPaths), Sets.newHashSet(currentSdkPaths)).isEmpty()) {
|
||||
ApplicationManager.getApplication().invokeAndWait(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
final Sdk sdk = PythonSdkType.findSdkByPath(homePath);
|
||||
final SdkModificator modificatorToCommit = sdkModificator != null ? sdkModificator :
|
||||
sdk != null ? sdk.getSdkModificator() : modificatorToGetRoots;
|
||||
modificatorToCommit.removeAllRoots();
|
||||
for (VirtualFile sdkPath : sdkPaths) {
|
||||
modificatorToCommit.addRoot(PythonSdkType.getSdkRootVirtualFile(sdkPath), OrderRootType.CLASSES);
|
||||
}
|
||||
modificatorToCommit.commitChanges();
|
||||
}
|
||||
}
|
||||
sdkUpdater.addRoot(skeletonsDir, OrderRootType.CLASSES);
|
||||
}, ModalityState.defaultModalityState());
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean wasOldRoot(@NotNull String root, @NotNull Collection<VirtualFile> oldRoots) {
|
||||
final VirtualFile file = LocalFileSystem.getInstance().refreshAndFindFileByPath(root);
|
||||
if (file != null) {
|
||||
final VirtualFile rootFile = PythonSdkType.getSdkRootVirtualFile(file);
|
||||
return oldRoots.contains(rootFile);
|
||||
/**
|
||||
* Returns unique Python SDKs for the open modules of the project.
|
||||
*/
|
||||
@NotNull
|
||||
private static Set<Sdk> getPythonSdks(@NotNull Project project) {
|
||||
final Set<Sdk> pythonSdks = Sets.newLinkedHashSet();
|
||||
for (Module module : ModuleManager.getInstance(project).getModules()) {
|
||||
final Sdk sdk = PythonSdkType.findPythonSdk(module);
|
||||
if (sdk != null && sdk.getSdkType() instanceof PythonSdkType) {
|
||||
pythonSdks.add(sdk);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return pythonSdks;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,6 @@ 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.PySdkUpdater;
|
||||
import com.jetbrains.python.sdk.PySdkUtil;
|
||||
import com.jetbrains.python.sdk.PythonSdkType;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
@@ -112,25 +111,25 @@ public class PySkeletonRefresher {
|
||||
public static void refreshSkeletonsOfSdk(@Nullable Project project,
|
||||
Component ownerComponent,
|
||||
String skeletonsPath,
|
||||
@NotNull PySdkUpdater sdkUpdater)
|
||||
@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 = sdkUpdater.getHomePath();
|
||||
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, sdkUpdater.getSdk(), skeletonsPath, indicator, null);
|
||||
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 = sdkUpdater.getSdk().getName();
|
||||
String sdkName = sdk.getName();
|
||||
List<String> knownErrors = errors.get(sdkName);
|
||||
if (knownErrors == null) {
|
||||
errors.put(sdkName, sdkErrors);
|
||||
|
||||
@@ -27,7 +27,6 @@ import com.intellij.psi.stubs.StubUpdatingIndex;
|
||||
import com.intellij.util.indexing.FileBasedIndex;
|
||||
import com.jetbrains.python.codeInsight.userSkeletons.PyUserSkeletonsUtil;
|
||||
import com.jetbrains.python.psi.stubs.PyModuleNameIndex;
|
||||
import com.jetbrains.python.sdk.PySdkUpdater;
|
||||
import com.jetbrains.python.sdk.PythonSdkType;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -75,7 +74,7 @@ public class PythonMockSdk {
|
||||
sdkModificator.addRoot(LocalFileSystem.getInstance().refreshAndFindFileByIoFile(libPath), OrderRootType.CLASSES);
|
||||
}
|
||||
|
||||
PyUserSkeletonsUtil.addUserSkeletonsRoot(PySdkUpdater.fromSdkModificator(sdk, sdkModificator));
|
||||
sdkModificator.addRoot(PyUserSkeletonsUtil.getUserSkeletonsDirectory(), OrderRootType.CLASSES);
|
||||
|
||||
String mock_stubs_path = mock_path + PythonSdkType.SKELETON_DIR_NAME;
|
||||
sdkModificator.addRoot(LocalFileSystem.getInstance().refreshAndFindFileByPath(mock_stubs_path), PythonSdkType.BUILTIN_ROOT_TYPE);
|
||||
|
||||
@@ -11,10 +11,10 @@ import com.intellij.openapi.roots.OrderRootType;
|
||||
import com.intellij.openapi.roots.ProjectRootManager;
|
||||
import com.intellij.openapi.util.Ref;
|
||||
import com.intellij.openapi.util.io.FileUtil;
|
||||
import com.intellij.openapi.vfs.LocalFileSystem;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.testFramework.UsefulTestCase;
|
||||
import com.jetbrains.python.sdk.InvalidSdkException;
|
||||
import com.jetbrains.python.sdk.PySdkUpdater;
|
||||
import com.jetbrains.python.sdk.PythonSdkType;
|
||||
import com.jetbrains.python.sdk.skeletons.PySkeletonRefresher;
|
||||
import com.jetbrains.python.sdk.skeletons.SkeletonVersionChecker;
|
||||
@@ -115,7 +115,7 @@ public final class PyTestSdkTools {
|
||||
modificator.removeRoots(OrderRootType.CLASSES);
|
||||
|
||||
for (final String path : PythonSdkType.getSysPathsFromScript(sdk.getHomePath())) {
|
||||
PythonSdkType.addSdkRoot(PySdkUpdater.fromSdkModificator(sdk, modificator), path);
|
||||
addTestSdkRoot(modificator, path);
|
||||
}
|
||||
if (!addSkeletons) {
|
||||
UsefulTestCase.edt(new Runnable() {
|
||||
@@ -131,7 +131,7 @@ public final class PyTestSdkTools {
|
||||
final File skeletonsDir = new File(tempDir, PythonSdkType.SKELETON_DIR_NAME);
|
||||
FileUtil.createDirectory(skeletonsDir);
|
||||
final String skeletonsPath = skeletonsDir.toString();
|
||||
PythonSdkType.addSdkRoot(PySdkUpdater.fromSdkModificator(sdk, modificator), skeletonsPath);
|
||||
addTestSdkRoot(modificator, skeletonsPath);
|
||||
|
||||
UsefulTestCase.edt(new Runnable() {
|
||||
@Override
|
||||
@@ -146,4 +146,11 @@ public final class PyTestSdkTools {
|
||||
final List<String> errors = refresher.regenerateSkeletons(checker);
|
||||
Assert.assertThat("Errors found", errors, Matchers.empty());
|
||||
}
|
||||
|
||||
public static void addTestSdkRoot(@NotNull SdkModificator sdkModificator, @NotNull String path) {
|
||||
final VirtualFile file = LocalFileSystem.getInstance().refreshAndFindFileByPath(path);
|
||||
if (file != null) {
|
||||
sdkModificator.addRoot(PythonSdkType.getSdkRootVirtualFile(file), OrderRootType.CLASSES);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user