PY-34719 PY-34718 PY-33754 PY-33701 Fixed updating lists of conda channels, packages, environments

These are several interconnected fixes, mostly related to using more modern conda API & CLI and making things work
asynchronously.
This commit is contained in:
Andrey Vlasovskikh
2019-04-01 17:30:09 +03:00
parent eb90b1897a
commit 42f4e29850
16 changed files with 370 additions and 299 deletions
@@ -278,7 +278,7 @@ public class InstalledPackagesPanel extends JPanel {
return ObjectUtils.tryCast(myPackageManagementService, PackageManagementServiceEx.class);
}
private void updateUninstallUpgrade() {
protected void updateUninstallUpgrade() {
final int[] selected = myPackagesTable.getSelectedRows();
boolean upgradeAvailable = false;
boolean canUninstall = selected.length != 0;
@@ -171,7 +171,7 @@ public class ManagePackagesDialog extends DialogWrapper {
}
private void addManageAction() {
if (myController.getAllRepositories() != null) {
if (myController.canManageRepositories()) {
myManageButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent event) {
@@ -15,6 +15,9 @@
*/
package com.intellij.webcore.packaging;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.ui.InputValidator;
@@ -22,30 +25,52 @@ import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.ui.ToolbarDecorator;
import com.intellij.ui.components.JBList;
import com.intellij.util.CatchingConsumer;
import com.intellij.util.ui.JBUI;
import javax.swing.*;
import java.util.List;
public class ManageRepoDialog extends DialogWrapper {
private JPanel myMainPanel;
private final JBList myList;
private final JBList<String> myList;
private boolean myEnabled;
private static final Logger LOG = Logger.getInstance(ManageRepoDialog.class);
public ManageRepoDialog(Project project, final PackageManagementService controller) {
super(project, false);
init();
setTitle("Manage Repositories");
final DefaultListModel repoModel = new DefaultListModel();
for(String repoUrl: controller.getAllRepositories()) {
repoModel.addElement(repoUrl);
}
myList = new JBList();
myList = new JBList<>();
myList.setPaintBusy(true);
final DefaultListModel<String> repoModel = new DefaultListModel<>();
controller.fetchAllRepositories(new CatchingConsumer<List<String>, Exception>() {
@Override
public void consume(List<String> repoUrls) {
ApplicationManager.getApplication().invokeLater(() -> {
if (isDisposed()) return;
myList.setPaintBusy(false);
for (String repoUrl: repoUrls) {
repoModel.addElement(repoUrl);
}
}, ModalityState.any());
}
@Override
public void consume(Exception e) {
ApplicationManager.getApplication().invokeLater(() -> {
if (isDisposed()) return;
myList.setPaintBusy(false);
LOG.warn(e);
});
}
});
myList.setModel(repoModel);
myList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
myList.addListSelectionListener(event -> {
final Object selected = myList.getSelectedValue();
myEnabled = controller.canModifyRepository((String) selected);
final String selected = myList.getSelectedValue();
myEnabled = controller.canModifyRepository(selected);
});
final ToolbarDecorator decorator = ToolbarDecorator.createDecorator(myList).disableUpDownActions();
@@ -61,7 +86,7 @@ public class ManageRepoDialog extends DialogWrapper {
}
});
decorator.setEditAction(button -> {
final String oldValue = (String)myList.getSelectedValue();
final String oldValue = myList.getSelectedValue();
String url = Messages.showInputDialog("Please edit repository URL", "Repository URL", null, oldValue, new InputValidator() {
@Override
@@ -82,7 +107,7 @@ public class ManageRepoDialog extends DialogWrapper {
}
});
decorator.setRemoveAction(button -> {
String selected = (String)myList.getSelectedValue();
String selected = myList.getSelectedValue();
controller.removeRepository(selected);
repoModel.removeElement(selected);
button.setEnabled(false);
@@ -23,6 +23,20 @@ public abstract class PackageManagementService {
return null;
}
/**
* An async version of {@link #getAllRepositories()}.
*/
public void fetchAllRepositories(@NotNull CatchingConsumer<? super List<String>, ? super Exception> consumer) {
consumer.consume(getAllRepositories());
}
/**
* Returns true if the service supports managing repositories.
*/
public boolean canManageRepositories() {
return getAllRepositories() != null;
}
/**
* Checks if the user can change the URL of the specified repository or remove it from the list.
*
+51 -10
View File
@@ -4,11 +4,13 @@ import traceback
ERROR_WRONG_USAGE = 1
ERROR_EXCEPTION = 4
def usage():
sys.stderr.write('Usage: conda_packaging_tool.py <listall>\n')
sys.stderr.write('Usage: conda_packaging_tool.py listall | channels | versions PACKAGE\n')
sys.stderr.flush()
exit(ERROR_WRONG_USAGE)
def do_list_available_packages():
import conda
version = conda.__version__
@@ -23,6 +25,7 @@ def do_list_available_packages():
minor_version = int(version_splitted[1])
if major_version >= 4 and minor_version >= 4:
init_context()
from conda.core.index import get_index
index = get_index()
elif major_version == 4 and minor_version >= 2:
@@ -36,20 +39,52 @@ def do_list_available_packages():
index = common.get_index_trap()
for pkg in index.values():
sys.stdout.write("\t".join([pkg["name"], pkg["version"], ":".join(pkg["depends"])])+chr(10))
sys.stdout.write("\t".join([pkg["name"], pkg["version"], ":".join(pkg["depends"])]) + chr(10))
sys.stdout.flush()
def do_list_channels():
import conda.config as config
if hasattr(config, "get_channel_urls"):
channels = config.get_channel_urls()
context = init_context()
if context:
channels = context.channels
else:
channels = config.context.channels
for channel in channels:
if channel != 'defaults':
sys.stdout.write(channel+chr(10))
sys.stdout.flush()
import conda.config as config
if hasattr(config, "get_channel_urls"):
channels = config.get_channel_urls()
else:
channels = config.context.channels
sys.stdout.write('\n'.join(channels))
sys.stdout.write('\n')
sys.stdout.flush()
def fetch_versions(package):
import json
from distutils.version import LooseVersion
from conda.cli.python_api import run_command, Commands
stdout, stderr, ret_code = run_command(Commands.SEARCH, package, '--json')
if ret_code != 0:
raise Exception(stderr)
results = json.loads(stdout)
results = results.get(package, [])
all_versions = (r.get('version') for r in results)
return sorted(set(v for v in all_versions if v), key=LooseVersion, reverse=True)
def do_list_versions(package):
sys.stdout.write('\n'.join(fetch_versions(package)))
sys.stderr.write('\n')
sys.stdout.flush()
def init_context():
try:
from conda.base.context import context
except ImportError:
return None
context.__init__()
return context
def main():
@@ -68,6 +103,11 @@ def main():
usage()
return
do_list_channels()
elif cmd == 'versions':
if len(sys.argv) != 3:
usage()
return
do_list_versions(sys.argv[2])
else:
usage()
except Exception:
@@ -75,5 +115,6 @@ def main():
exit(ERROR_EXCEPTION)
exit(retcode)
if __name__ == '__main__':
main()
@@ -1,8 +1,12 @@
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.jetbrains.python
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.module.Module
import com.intellij.openapi.module.ModuleManager
import com.intellij.openapi.progress.ProgressIndicator
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.impl.SdkConfigurationUtil
@@ -27,13 +31,22 @@ class PythonSdkConfigurator : DirectoryProjectConfigurator {
}
fun findDetectedAssociatedEnvironment(module: Module, existingSdks: List<Sdk>): PyDetectedSdk? {
detectVirtualEnvs(module, existingSdks).firstOrNull { it.isAssociatedWithModule(module) }?.let {
return it
}
detectCondaEnvs(module, existingSdks).firstOrNull { it.isAssociatedWithModule(module) }?.let {
return it
}
return null
// TODO: Move all interpreter detection away from EDT & use proper synchronization for that
val progress = ProgressManager.getInstance()
return progress.run(object : Task.WithResult<PyDetectedSdk?, Exception>(module.project,
"Looking for Virtual Environments",
false) {
override fun compute(indicator: ProgressIndicator): PyDetectedSdk? {
indicator.isIndeterminate = true
detectVirtualEnvs(module, existingSdks).firstOrNull { it.isAssociatedWithModule(module) }?.let {
return it
}
detectCondaEnvs(module, existingSdks).firstOrNull { it.isAssociatedWithModule(module) }?.let {
return it
}
return null
}
})
}
private fun findExistingSystemWideSdk(existingSdks: List<Sdk>) =
@@ -1,40 +0,0 @@
// Copyright 2000-2017 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.jetbrains.python.packaging;
import com.google.common.collect.Multimap;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
/**
* @author Mikhail Golubev
*/
public class PyCondaPackageCache extends PyAbstractPackageCache {
private static final String CACHE_FILE_NAME = "conda-cache.json";
private static PyCondaPackageCache ourInstance;
@NotNull
public static synchronized PyCondaPackageCache getInstance() {
if (ourInstance == null) {
ourInstance = load(PyCondaPackageCache.class, new PyCondaPackageCache(), getDefaultCachePath(CACHE_FILE_NAME));
}
return ourInstance;
}
@NotNull
public static synchronized PyCondaPackageCache reload(@NotNull Multimap<String, String> nameToVersion) {
ourInstance = new PyCondaPackageCache(nameToVersion);
store(ourInstance, CACHE_FILE_NAME);
return ourInstance;
}
private PyCondaPackageCache() {
}
private PyCondaPackageCache(@NotNull Multimap<String, String> nameToVersion) {
for (String name : nameToVersion.keySet()) {
myPackages.put(name, new PyAbstractPackageCache.PackageInfo(new ArrayList<>(nameToVersion.get(name))));
}
}
}
@@ -145,6 +145,11 @@ public class PyCondaPackageManagerImpl extends PyPackageManagerImpl {
}
}
@Override
public boolean hasManagement() throws ExecutionException {
return useConda || super.hasManagement();
}
@NotNull
private List<PyPackage> parseCondaToolOutput(@NotNull String s) throws ExecutionException {
final String[] lines = StringUtil.splitByLines(s);
@@ -3,6 +3,7 @@ package com.jetbrains.python.packaging;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps;
import com.intellij.execution.ExecutionException;
import com.intellij.execution.configurations.PathEnvironmentVariableUtil;
import com.intellij.execution.process.ProcessOutput;
import com.intellij.openapi.components.*;
@@ -13,7 +14,6 @@ import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.StandardFileSystems;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.SystemProperties;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.text.VersionComparatorUtil;
import com.intellij.util.xmlb.XmlSerializerUtil;
import com.jetbrains.python.PythonHelpersLocator;
@@ -30,8 +30,6 @@ import java.util.*;
@State(name = "PyCondaPackageService", storages = @Storage(value="conda_packages.xml", roamingType = RoamingType.DISABLED))
public class PyCondaPackageService implements PersistentStateComponent<PyCondaPackageService> {
private static final Logger LOG = Logger.getInstance(PyCondaPackageService.class);
public Set<String> CONDA_CHANNELS = ContainerUtil.newConcurrentSet();
public long LAST_TIME_CHECKED = 0;
@Nullable @SystemDependent public String PREFERRED_CONDA_PATH = null;
@Override
@@ -48,27 +46,6 @@ public class PyCondaPackageService implements PersistentStateComponent<PyCondaPa
return ServiceManager.getService(PyCondaPackageService.class);
}
public void loadAndGetPackages(boolean force) {
if (PyCondaPackageCache.getInstance().getPackageNames().isEmpty() || force) {
updatePackagesCache();
}
}
public Set<String> loadAndGetChannels() {
if (CONDA_CHANNELS.isEmpty()) {
updateChannels();
}
return CONDA_CHANNELS;
}
public void addChannel(@NotNull final String url) {
CONDA_CHANNELS.add(url);
}
public void removeChannel(@NotNull final String url) {
CONDA_CHANNELS.remove(url);
}
@Nullable
public static String getCondaPython() {
final String conda = getSystemCondaExecutable();
@@ -107,14 +84,11 @@ public class PyCondaPackageService implements PersistentStateComponent<PyCondaPa
@Nullable
public static String getCondaExecutable(@Nullable String sdkPath) {
if (sdkPath == null) {
return null;
if (sdkPath != null) {
String condaPath = findCondaExecutableRelativeToEnv(sdkPath);
if (condaPath != null) return condaPath;
}
String condaPath = findCondaExecutableRelativeToEnv(sdkPath);
if (condaPath != null) return condaPath;
if (StringUtil.isNotEmpty(getInstance().PREFERRED_CONDA_PATH)) {
return getInstance().PREFERRED_CONDA_PATH;
}
@@ -140,7 +114,7 @@ public class PyCondaPackageService implements PersistentStateComponent<PyCondaPa
}
@Nullable
public static String getCondaExecutableByName(@NotNull final String condaName) {
private static String getCondaExecutableByName(@NotNull final String condaName) {
final VirtualFile userHome = LocalFileSystem.getInstance().findFileByPath(SystemProperties.getUserHome().replace('\\', '/'));
if (userHome != null) {
for (String root : CondaEnvSdkFlavor.CONDA_DEFAULT_ROOTS) {
@@ -152,15 +126,13 @@ public class PyCondaPackageService implements PersistentStateComponent<PyCondaPa
executableFile = findExecutable(condaName, appData);
if (executableFile != null) return executableFile;
condaFolder = LocalFileSystem.getInstance().findFileByPath("C:\\" + root);
executableFile = findExecutable(condaName, condaFolder);
if (executableFile != null) return executableFile;
}
else {
final String systemWidePath = "/opt/anaconda";
condaFolder = LocalFileSystem.getInstance().findFileByPath(systemWidePath);
executableFile = findExecutable(condaName, condaFolder);
if (executableFile != null) return executableFile;
}
executableFile = findExecutable(condaName, condaFolder);
if (executableFile != null) return executableFile;
}
}
@@ -185,51 +157,48 @@ public class PyCondaPackageService implements PersistentStateComponent<PyCondaPa
return null;
}
public void updatePackagesCache() {
final String condaPython = getCondaPython();
if (condaPython == null) {
return;
}
final String path = PythonHelpersLocator.getHelperPath("conda_packaging_tool.py");
final ProcessOutput output;
@Nullable
public Multimap<String, String> listAllPackagesAndVersions() {
try {
output = PyCondaRunKt.runCondaPython(condaPython, Arrays.asList(path, "listall"));
final String output = runCondaPackagingHelper("listall");
final Multimap<String, String> nameToVersions =
Multimaps.newSortedSetMultimap(new HashMap<>(), () -> new TreeSet<>(VersionComparatorUtil.COMPARATOR.reversed()));
for (String line : StringUtil.split(output, "\n")) {
final List<String> split = StringUtil.split(line, "\t");
if (split.size() < 2) continue;
nameToVersions.put(split.get(0), split.get(1));
}
return nameToVersions;
}
catch (PyExecutionException e) {
catch (ExecutionException e) {
LOG.warn("Failed to get list of conda packages. " + e);
return;
return null;
}
final Multimap<String, String> nameToVersions =
Multimaps.newSortedSetMultimap(new HashMap<>(), () -> new TreeSet<>(VersionComparatorUtil.COMPARATOR.reversed()));
for (String line : output.getStdoutLines()) {
final List<String> split = StringUtil.split(line, "\t");
if (split.size() < 2) continue;
nameToVersions.put(split.get(0), split.get(1));
}
PyCondaPackageCache.reload(nameToVersions);
LAST_TIME_CHECKED = System.currentTimeMillis();
}
@NotNull
public List<String> getPackageVersions(@NotNull final String packageName) {
return ContainerUtil.notNullize(PyCondaPackageCache.getInstance().getVersions(packageName));
public List<String> listPackageVersions(@NotNull String packageName) throws ExecutionException {
final String output = runCondaPackagingHelper("versions", packageName);
return StringUtil.split(output, "\n");
}
public void updateChannels() {
@Nullable
public List<String> listChannels() throws ExecutionException {
final String output = runCondaPackagingHelper("channels");
return StringUtil.split(output, "\n");
}
@NotNull
private static String runCondaPackagingHelper(@NotNull String... args) throws ExecutionException {
final List<String> commandArgs = new ArrayList<>();
commandArgs.add(PythonHelpersLocator.getHelperPath("conda_packaging_tool.py"));
commandArgs.addAll(Arrays.asList(args));
final String condaPython = getCondaPython();
if (condaPython == null) return;
final String path = PythonHelpersLocator.getHelperPath("conda_packaging_tool.py");
final ProcessOutput output;
try {
output = PyCondaRunKt.runCondaPython(condaPython, Arrays.asList(path, "channels"));
if (condaPython == null) {
throw new PyExecutionException("Cannot find Python executable for conda",
"python", commandArgs, new ProcessOutput());
}
catch (PyExecutionException e) {
LOG.warn("Failed to update conda channels. " + e);
return;
}
final List<String> lines = output.getStdoutLines();
CONDA_CHANNELS.addAll(lines);
LAST_TIME_CHECKED = System.currentTimeMillis();
final ProcessOutput output = PyCondaRunKt.runCondaPython(condaPython, commandArgs);
return output.getStdout();
}
}
@@ -53,17 +53,6 @@ public class PyPackagesUpdater implements StartupActivity {
}
});
}
if (checkCondaUpdateNeeded(project)) {
application.executeOnPooledThread(() -> PyCondaPackageService.getInstance().updatePackagesCache());
}
}
private static boolean checkCondaUpdateNeeded(Project project) {
if (!hasPython(project)) return false;
final long timeDelta = System.currentTimeMillis() - PyCondaPackageService.getInstance().LAST_TIME_CHECKED;
if (Math.abs(timeDelta) < EXPIRATION_TIMEOUT) return false;
LOG.debug("Updating outdated Conda package cache");
return true;
}
private static boolean hasPython(Project project) {
@@ -16,15 +16,18 @@
package com.jetbrains.python.packaging.ui;
import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;
import com.intellij.execution.ExecutionException;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.progress.ProgressIndicator;
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.util.CatchingConsumer;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.webcore.packaging.InstalledPackage;
import com.intellij.webcore.packaging.RepoPackage;
import com.jetbrains.python.packaging.PyCondaPackageCache;
import com.jetbrains.python.packaging.PyCondaPackageManagerImpl;
import com.jetbrains.python.packaging.PyCondaPackageService;
import com.jetbrains.python.packaging.PyPackageManager;
@@ -32,6 +35,8 @@ import com.jetbrains.python.sdk.flavors.PyCondaRunKt;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class PyCondaManagementService extends PyPackageManagementService {
@@ -49,45 +54,77 @@ public class PyCondaManagementService extends PyPackageManagementService {
@NotNull
public List<RepoPackage> getAllPackagesCached() {
if (useConda()) {
return getCachedCondaPackages();
return Collections.emptyList();
}
else {
return super.getAllPackagesCached();
}
return super.getAllPackagesCached();
}
@Override
@NotNull
public List<RepoPackage> getAllPackages() throws IOException {
if (useConda()) {
PyCondaPackageService.getInstance().loadAndGetPackages(false);
return getAllPackagesCached();
return reloadAllPackages();
}
else {
return super.getAllPackages();
}
return super.getAllPackages();
}
@Override
@NotNull
public List<RepoPackage> reloadAllPackages() throws IOException {
if (useConda()) {
PyCondaPackageService.getInstance().loadAndGetPackages(true);
return getAllPackagesCached();
final Multimap<String, String> packages = PyCondaPackageService.getInstance().listAllPackagesAndVersions();
if (packages == null) return Collections.emptyList();
final List<RepoPackage> results = new ArrayList<>();
for (String pkg : packages.keySet()) {
results.add(new RepoPackage(pkg, null, ContainerUtil.getFirstItem(packages.get(pkg))));
}
return results;
}
return super.reloadAllPackages();
}
@Override
public List<String> getAllRepositories() {
return useConda() ? Lists.newArrayList(PyCondaPackageService.getInstance().loadAndGetChannels()) : super.getAllRepositories();
public boolean canManageRepositories() {
return true;
}
@Override
public void fetchAllRepositories(@NotNull CatchingConsumer<? super List<String>, ? super Exception> consumer) {
if (useConda()) {
myExecutorService.submit(() -> {
try {
final List<String> channels = ContainerUtil.notNullize(PyCondaPackageService.getInstance().listChannels());
consumer.consume(channels);
}
catch (ExecutionException e) {
consumer.consume(e);
}
});
}
else {
super.fetchAllRepositories(consumer);
}
}
@Override
public void addRepository(String repositoryUrl) {
if (useConda()) {
try {
PyCondaRunKt.runConda(mySdk, Lists.newArrayList("config", "--add", "channels", repositoryUrl, "--force"));
}
catch (ExecutionException e) {
LOG.warn("Failed to add repository. " + e);
}
ProgressManager.getInstance().run(new Task.Modal(getProject(), "Adding Conda Channel", true) {
@Override
public void run(@NotNull ProgressIndicator indicator) {
indicator.setIndeterminate(true);
try {
PyCondaRunKt.runConda(mySdk, Lists.newArrayList("config", "--add", "channels", repositoryUrl, "--force"));
}
catch (ExecutionException e) {
LOG.warn("Failed to add repository. " + e);
}
}
});
}
else {
super.addRepository(repositoryUrl);
@@ -97,12 +134,18 @@ public class PyCondaManagementService extends PyPackageManagementService {
@Override
public void removeRepository(String repositoryUrl) {
if (useConda()) {
try {
PyCondaRunKt.runConda(mySdk, Lists.newArrayList("config", "--remove", "channels", repositoryUrl, "--force"));
}
catch (ExecutionException e) {
LOG.warn("Failed to remove repository. " + e);
}
ProgressManager.getInstance().run(new Task.Modal(getProject(), "Removing Conda Channel", true) {
@Override
public void run(@NotNull ProgressIndicator indicator) {
indicator.setIndeterminate(true);
try {
PyCondaRunKt.runConda(mySdk, Lists.newArrayList("config", "--remove", "channels", repositoryUrl, "--force"));
}
catch (ExecutionException e) {
LOG.warn("Failed to remove repository. " + e);
}
}
});
}
else {
super.removeRepository(repositoryUrl);
@@ -117,7 +160,14 @@ public class PyCondaManagementService extends PyPackageManagementService {
@Override
public void fetchPackageVersions(String packageName, CatchingConsumer<List<String>, Exception> consumer) {
if (useConda()) {
consumer.consume(PyCondaPackageService.getInstance().getPackageVersions(packageName));
myExecutorService.submit(() -> {
try {
consumer.consume(PyCondaPackageService.getInstance().listPackageVersions(packageName));
}
catch (ExecutionException e) {
LOG.warn("Failed to fetch versions for '" + packageName + "'. " + e);
}
});
}
else {
super.fetchPackageVersions(packageName, consumer);
@@ -128,20 +178,23 @@ public class PyCondaManagementService extends PyPackageManagementService {
public void fetchLatestVersion(@NotNull InstalledPackage pkg, @NotNull CatchingConsumer<String, Exception> consumer) {
final String packageName = pkg.getName();
if (useConda()) {
final String latestVersion = ContainerUtil.getFirstItem(PyCondaPackageCache.getInstance().getVersions(packageName));
consumer.consume(latestVersion);
myExecutorService.submit(() -> {
try {
final String latestVersion = ContainerUtil.getFirstItem(PyCondaPackageService.getInstance().listPackageVersions(packageName));
consumer.consume(latestVersion);
}
catch (ExecutionException e) {
LOG.warn("Failed to fetch versions for '" + packageName + "'. " + e);
}
});
}
else {
super.fetchLatestVersion(pkg, consumer);
}
}
@NotNull
private static List<RepoPackage> getCachedCondaPackages() {
final PyCondaPackageCache instance = PyCondaPackageCache.getInstance();
return ContainerUtil.map(instance.getPackageNames(), name -> {
final String latestVersion = ContainerUtil.getFirstItem(instance.getVersions(name));
return new RepoPackage(name, null, latestVersion);
});
@Override
public boolean shouldFetchLatestVersionsForOnlyInstalledPackages() {
return false;
}
}
@@ -45,7 +45,7 @@ import java.util.Set;
* @author yole
*/
public class PyInstalledPackagesPanel extends InstalledPackagesPanel {
private boolean myHasManagement = false;
private volatile boolean myHasManagement = false;
public PyInstalledPackagesPanel(@NotNull Project project, @NotNull PackagesNotificationPanel area) {
super(project, area);
@@ -98,6 +98,7 @@ public class PyInstalledPackagesPanel extends InstalledPackagesPanel {
PyExecutionException exception = null;
try {
myHasManagement = PyPackageManager.getInstance(selectedSdk).hasManagement();
application.invokeLater(() -> updateUninstallUpgrade(), ModalityState.any());
if (!myHasManagement) {
throw new PyExecutionException("Python packaging tools not found", "pip", Collections.emptyList(), "", "", 0,
ImmutableList.of(new PyInstallPackageManagementFix()));
@@ -15,16 +15,26 @@
*/
package com.jetbrains.python.sdk.add
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.ModalityState
import com.intellij.openapi.module.Module
import com.intellij.openapi.project.Project
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.ui.ValidationInfo
import com.intellij.ui.components.JBCheckBox
import com.intellij.util.ui.AsyncProcessIcon
import com.intellij.util.ui.FormBuilder
import com.jetbrains.python.sdk.*
import com.sun.glass.ui.Application
import icons.PythonIcons
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import java.awt.BorderLayout
import javax.swing.Icon
import javax.swing.SwingUtilities
import javax.swing.SwingWorker
/**
* @author vlan
@@ -34,10 +44,8 @@ class PyAddExistingCondaEnvPanel(private val project: Project?,
private val existingSdks: List<Sdk>,
override var newProjectPath: String?) : PyAddSdkPanel() {
override val panelName: String = "Existing environment"
override val icon: Icon = PythonIcons.Python.Condaenv
private val sdkComboBox = PySdkPathChoosingComboBox(detectCondaEnvs(module, existingSdks)
.filterNot { it.isAssociatedWithAnotherModule(module) },
null)
override val icon: Icon = PythonIcons.Python.Anaconda
private val sdkComboBox = PySdkPathChoosingComboBox(listOf(), null)
private val makeSharedField = JBCheckBox("Make available to all projects")
init {
@@ -47,6 +55,17 @@ class PyAddExistingCondaEnvPanel(private val project: Project?,
.addComponent(makeSharedField)
.panel
add(formPanel, BorderLayout.NORTH)
ApplicationManager.getApplication().executeOnPooledThread(object: Runnable {
override fun run() {
if (module != null && module.isDisposed) return
val sdks = detectCondaEnvs(module, existingSdks)
ApplicationManager.getApplication().invokeLater({
sdks.forEach {
sdkComboBox.childComponent.addItem(it)
}
}, ModalityState.any())
}
})
}
override fun validateAll(): List<ValidationInfo> = listOfNotNull(validateSdkComboBox(sdkComboBox))
@@ -15,26 +15,26 @@
*/
package com.jetbrains.python.sdk.flavors;
import com.intellij.execution.ExecutionException;
import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.vfs.StandardFileSystems;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.SystemProperties;
import com.jetbrains.python.packaging.PyCondaPackageService;
import com.jetbrains.python.sdk.PythonSdkType;
import icons.PythonIcons;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.SystemDependent;
import javax.swing.*;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import static com.jetbrains.python.sdk.flavors.VirtualEnvSdkFlavor.findInDirectory;
import static com.jetbrains.python.sdk.flavors.VirtualEnvSdkFlavor.findInRootDirectory;
public class CondaEnvSdkFlavor extends CPythonSdkFlavor {
private CondaEnvSdkFlavor() {
@@ -47,80 +47,29 @@ public class CondaEnvSdkFlavor extends CPythonSdkFlavor {
@Override
public Collection<String> suggestHomePaths(@Nullable Module module) {
List<String> candidates = new ArrayList<>();
for (VirtualFile file : getCondaDefaultLocations()) {
candidates.addAll(findInDirectory(file));
}
return candidates;
}
public static List<VirtualFile> getCondaDefaultLocations() {
List<VirtualFile> roots = new ArrayList<>();
final VirtualFile userHome = LocalFileSystem.getInstance().findFileByPath(SystemProperties.getUserHome().replace('\\','/'));
if (userHome != null) {
final VirtualFile condaHidden = userHome.findChild(".conda");
if (condaHidden != null) {
addEnvsFolder(roots, condaHidden);
}
for (String root : CONDA_DEFAULT_ROOTS) {
VirtualFile condaFolder = userHome.findChild(root);
addEnvsFolder(roots, condaFolder);
if (SystemInfo.isWindows) {
final VirtualFile appData = userHome.findFileByRelativePath("AppData\\Local\\Continuum\\" + root);
addEnvsFolder(roots, appData);
condaFolder = LocalFileSystem.getInstance().findFileByPath("C:\\" + root);
}
else {
final String systemWidePath = "/opt/anaconda";
condaFolder = LocalFileSystem.getInstance().findFileByPath(systemWidePath);
}
addEnvsFolder(roots, condaFolder);
final List<String> results = new ArrayList<>();
final Sdk sdk = ReadAction.compute(() -> PythonSdkType.findPythonSdk(module));
try {
final List<String> environments = PyCondaRunKt.listCondaEnvironments(sdk);
for (String environment : environments) {
results.addAll(ReadAction.compute(() -> {
final VirtualFile root = StandardFileSystems.local().findFileByPath(environment);
return StreamEx.of(findInRootDirectory(root))
.filter(s -> getCondaEnvRoot(s) != null)
.toList();
}));
}
}
addEnvsFolder(roots, findPreferredCondaEnvsFolder());
return roots;
}
@Nullable
private static VirtualFile findPreferredCondaEnvsFolder() {
@SystemDependent final String path = PyCondaPackageService.getInstance().PREFERRED_CONDA_PATH;
if (path == null) return null;
final VirtualFile conda = StandardFileSystems.local().findFileByPath(path);
if (conda == null) return null;
final VirtualFile binFolder = conda.getParent();
if (binFolder == null) return null;
return binFolder.getParent();
}
private static void addEnvsFolder(@NotNull final List<VirtualFile> roots, @Nullable final VirtualFile condaFolder) {
if (condaFolder != null) {
final VirtualFile envs = condaFolder.findChild("envs");
if (envs != null) {
roots.add(envs);
}
catch (ExecutionException e) {
return Collections.emptyList();
}
return results;
}
@Override
public boolean isValidSdkPath(@NotNull File file) {
if (!super.isValidSdkPath(file)) return false;
final File bin = file.getParentFile();
String condaName = "conda";
if (SystemInfo.isWindows) {
condaName = new File(bin, "envs").exists() ? "conda.exe" : "conda.bat";
}
if (bin != null) {
final File conda = new File(bin, condaName);
if (conda.exists()) {
return true;
}
final File condaFolder = bin.getParentFile();
final File condaExecutable = PythonSdkType.findExecutableFile(condaFolder, condaName);
if (condaExecutable != null) return true;
}
return false;
return PythonSdkType.isConda(file.getPath());
}
@Nullable
@@ -137,6 +86,6 @@ public class CondaEnvSdkFlavor extends CPythonSdkFlavor {
@Override
public Icon getIcon() {
return PythonIcons.Python.Condaenv;
return PythonIcons.Python.Anaconda;
}
}
@@ -1,11 +1,15 @@
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.jetbrains.python.sdk.flavors
import com.google.gson.Gson
import com.google.gson.JsonSyntaxException
import com.google.gson.annotations.SerializedName
import com.intellij.execution.ExecutionException
import com.intellij.execution.RunCanceledByUserException
import com.intellij.execution.configurations.GeneralCommandLine
import com.intellij.execution.process.CapturingProcessHandler
import com.intellij.execution.process.ProcessOutput
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.progress.EmptyProgressIndicator
import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.projectRoots.Sdk
@@ -13,18 +17,27 @@ import com.intellij.openapi.util.text.StringUtil
import com.jetbrains.python.packaging.PyCondaPackageService
import com.jetbrains.python.packaging.PyExecutionException
import com.jetbrains.python.sdk.PythonSdkType
import org.jetbrains.concurrency.AsyncPromise
import org.jetbrains.concurrency.Promise
@Throws(PyExecutionException::class)
@Throws(ExecutionException::class)
fun runConda(condaExecutable: String, arguments: List<String>): ProcessOutput {
return run(condaExecutable, arguments, readCondaEnv(condaExecutable))
}
@Throws(PyExecutionException::class)
fun runConda(sdk: Sdk, arguments: List<String>): ProcessOutput {
return run(findCondaExecutable(sdk), arguments, PythonSdkType.activateVirtualEnv(sdk))
@Throws(ExecutionException::class)
fun runConda(sdk: Sdk?, arguments: List<String>): ProcessOutput {
val condaExecutable = findCondaExecutable(sdk)
val environment = if (sdk != null) {
PythonSdkType.activateVirtualEnv(sdk)
}
else {
readCondaEnv(condaExecutable)
}
return run(condaExecutable, arguments, environment)
}
@Throws(PyExecutionException::class)
@Throws(ExecutionException::class)
fun runCondaPython(condaPythonExecutable: String, arguments: List<String>): ProcessOutput {
return run(condaPythonExecutable, arguments, PythonSdkType.activateVirtualEnv(condaPythonExecutable))
}
@@ -43,8 +56,8 @@ private fun readCondaEnv(condaExecutable: String): Map<String, String>? {
}
@Throws(ExecutionException::class)
private fun findCondaExecutable(sdk: Sdk): String {
return PyCondaPackageService.getCondaExecutable(sdk.homePath) ?: throw ExecutionException("Cannot find conda executable")
private fun findCondaExecutable(sdk: Sdk?): String {
return PyCondaPackageService.getCondaExecutable(sdk?.homePath) ?: throw ExecutionException("Cannot find conda executable")
}
@Throws(PyExecutionException::class)
@@ -54,4 +67,14 @@ private fun ProcessOutput.checkExitCode(executable: String, arguments: List<Stri
else "Non-zero exit code"
throw PyExecutionException(message, executable, arguments, this)
}
}
}
@Throws(ExecutionException::class, JsonSyntaxException::class)
fun listCondaEnvironments(sdk: Sdk?): List<String> {
val output = runConda(sdk, listOf("env", "list", "--json"))
val text = output.stdout
val envList = Gson().fromJson(text, CondaEnvironmentsList::class.java)
return envList.envs
}
private data class CondaEnvironmentsList(@SerializedName("envs") var envs: List<String>)
@@ -15,6 +15,7 @@
*/
package com.jetbrains.python.sdk.flavors;
import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.io.FileUtil;
@@ -46,25 +47,27 @@ public class VirtualEnvSdkFlavor extends CPythonSdkFlavor {
@Override
public Collection<String> suggestHomePaths(@Nullable Module module) {
final List<String> candidates = new ArrayList<>();
if (module != null) {
final VirtualFile baseDir = PySdkExtKt.getBaseDir(module);
if (baseDir != null) {
candidates.addAll(findInDirectory(baseDir));
return ReadAction.compute(() -> {
final List<String> candidates = new ArrayList<>();
if (module != null) {
final VirtualFile baseDir = PySdkExtKt.getBaseDir(module);
if (baseDir != null) {
candidates.addAll(findInBaseDirectory(baseDir));
}
}
}
final VirtualFile path = getDefaultLocation();
if (path != null) {
candidates.addAll(findInDirectory(path));
}
final VirtualFile path = getDefaultLocation();
if (path != null) {
candidates.addAll(findInBaseDirectory(path));
}
final VirtualFile pyEnvLocation = getPyEnvDefaultLocations();
if (pyEnvLocation != null) {
candidates.addAll(findInDirectory(pyEnvLocation));
}
final VirtualFile pyEnvLocation = getPyEnvDefaultLocations();
if (pyEnvLocation != null) {
candidates.addAll(findInBaseDirectory(pyEnvLocation));
}
return candidates;
return candidates;
});
}
@Nullable
@@ -99,24 +102,31 @@ public class VirtualEnvSdkFlavor extends CPythonSdkFlavor {
return null;
}
public static Collection<String> findInDirectory(VirtualFile rootDir) {
public static Collection<String> findInBaseDirectory(@Nullable VirtualFile baseDir) {
List<String> candidates = new ArrayList<>();
if (rootDir != null) {
rootDir.refresh(true, false);
VirtualFile[] suspects = rootDir.getChildren();
if (baseDir != null) {
baseDir.refresh(true, false);
VirtualFile[] suspects = baseDir.getChildren();
for (VirtualFile child : suspects) {
if (child.isDirectory()) {
final VirtualFile bin = child.findChild("bin");
final VirtualFile scripts = child.findChild("Scripts");
if (bin != null) {
final String interpreter = findInterpreter(bin);
if (interpreter != null) candidates.add(interpreter);
}
if (scripts != null) {
final String interpreter = findInterpreter(scripts);
if (interpreter != null) candidates.add(interpreter);
}
}
candidates.addAll(findInRootDirectory(child));
}
}
return candidates;
}
@NotNull
public static Collection<String> findInRootDirectory(@Nullable VirtualFile rootDir) {
final List<String> candidates = new ArrayList<>();
if (rootDir != null && rootDir.isDirectory()) {
final VirtualFile bin = rootDir.findChild("bin");
final VirtualFile scripts = rootDir.findChild("Scripts");
if (bin != null) {
final String interpreter = findInterpreter(bin);
if (interpreter != null) candidates.add(interpreter);
}
if (scripts != null) {
final String interpreter = findInterpreter(scripts);
if (interpreter != null) candidates.add(interpreter);
}
}
return candidates;