diff --git a/java/idea-ui/intellij.java.ui.iml b/java/idea-ui/intellij.java.ui.iml index 005ecd451f91..abb03b9688a8 100644 --- a/java/idea-ui/intellij.java.ui.iml +++ b/java/idea-ui/intellij.java.ui.iml @@ -34,5 +34,7 @@ + + \ No newline at end of file diff --git a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/EelSdkFilter.kt b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/EelSdkFilter.kt new file mode 100644 index 000000000000..5eaa6e7f4e6b --- /dev/null +++ b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/EelSdkFilter.kt @@ -0,0 +1,24 @@ +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +@file:JvmName("EelSdkFilter") + +package com.intellij.openapi.roots.ui.configuration + +import com.intellij.openapi.project.Project +import com.intellij.openapi.projectRoots.Sdk +import com.intellij.openapi.roots.ui.configuration.projectRoot.ProjectSdksModel +import com.intellij.platform.eel.provider.getEelApiKey +import java.util.function.Predicate + +internal fun filterSdkByEel(project: Project): Predicate { + val eelApiKey = project.getEelApiKey() + return Predicate { sdk -> + ProjectSdksModel.sdkMatchesEel(eelApiKey, sdk) + } +} + +internal fun filterSdkSuggestionByEel(project: Project): Predicate { + val eelApiKey = project.getEelApiKey() + return Predicate { item -> + ProjectSdksModel.sdkMatchesEel(eelApiKey, item.homePath) + } +} \ No newline at end of file diff --git a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/ModuleJdkConfigurable.java b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/ModuleJdkConfigurable.java index bbf34073fd4c..511a190f71a3 100644 --- a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/ModuleJdkConfigurable.java +++ b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/ModuleJdkConfigurable.java @@ -76,6 +76,7 @@ public abstract class ModuleJdkConfigurable implements Disposable { final Project project = getRootModel().getModule().getProject(); Predicate predicate = SimpleJavaSdkType.notSimpleJavaSdkType(); + // TODO Use EelApi here. myCbModuleJdk = new JdkComboBox(project, myJdksModel, predicate::test, WslSdkFilter.filterSdkByWsl(project), WslSdkFilter.filterSdkSuggestionByWsl(project), null, jdk -> { diff --git a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/ProjectJdkConfigurableUi.kt b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/ProjectJdkConfigurableUi.kt index 87a7dd89b7f5..cd4507891a3e 100644 --- a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/ProjectJdkConfigurableUi.kt +++ b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/ProjectJdkConfigurableUi.kt @@ -5,6 +5,7 @@ import com.intellij.openapi.application.ApplicationBundle import com.intellij.openapi.project.Project import com.intellij.openapi.projectRoots.SimpleJavaSdkType import com.intellij.openapi.roots.ui.configuration.projectRoot.ProjectSdksModel +import com.intellij.openapi.util.registry.Registry import com.intellij.ui.dsl.builder.AlignX import com.intellij.ui.dsl.builder.panel import javax.swing.JButton @@ -16,13 +17,21 @@ internal class ProjectJdkConfigurableUi { fun panel(project: Project, jdkModel: ProjectSdksModel): JPanel = panel { row { - jdkComboBox = cell( + val component = if (Registry.`is`("java.home.finder.use.eel")) + JdkComboBox.createCombobox(project, + jdkModel, + SimpleJavaSdkType.notSimpleJavaSdkType(), + filterSdkByEel(project), + filterSdkSuggestionByEel(project), + null) + else JdkComboBox.createCombobox(project, jdkModel, SimpleJavaSdkType.notSimpleJavaSdkType(), WslSdkFilter.filterSdkByWsl(project), WslSdkFilter.filterSdkSuggestionByWsl(project), - null)) + null) + jdkComboBox = cell(component) .resizableColumn() .align(AlignX.FILL) .component diff --git a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/ModuleStructureConfigurable.java b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/ModuleStructureConfigurable.java index cad02aec9ee2..856e4016b482 100644 --- a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/ModuleStructureConfigurable.java +++ b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/ModuleStructureConfigurable.java @@ -44,10 +44,12 @@ import com.intellij.openapi.util.NullableComputable; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.io.NioFiles; +import com.intellij.openapi.util.registry.Registry; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.pointers.VirtualFilePointerManager; +import com.intellij.platform.eel.provider.EelApiKey; import com.intellij.ui.navigation.Place; import com.intellij.util.PlatformIcons; import com.intellij.util.concurrency.ThreadingAssertions; @@ -67,6 +69,8 @@ import java.nio.file.Paths; import java.util.*; import java.util.function.Predicate; +import static com.intellij.platform.eel.provider.EelProviderUtil.getEelApiKey; + public class ModuleStructureConfigurable extends BaseStructureConfigurable implements Place.Navigator, Configurable.WithEpDependencies { private static final Comparator NODE_COMPARATOR = (o1, o2) -> { final NamedConfigurable configurable1 = o1.getConfigurable(); @@ -572,8 +576,9 @@ public class ModuleStructureConfigurable extends BaseStructureConfigurable imple modules = myContext.myModulesConfigurator.addNewModule(basePath); } if (modules != null && !modules.isEmpty()) { + EelApiKey eelKey = Registry.is("java.home.finder.use.eel") ? getEelApiKey(myProject) : null; //new module wizard may add yet another SDK to the project - myProjectStructureConfigurable.getProjectJdksModel().syncSdks(); + myProjectStructureConfigurable.getProjectJdksModel().syncSdks(eelKey); for (Module module : modules) { addModuleNode(module); } diff --git a/java/java-features-trainer/src/com/intellij/java/ift/lesson/essential/CommonLogicForOnboardingTours.kt b/java/java-features-trainer/src/com/intellij/java/ift/lesson/essential/CommonLogicForOnboardingTours.kt index 0b72de7836c7..25b633215285 100644 --- a/java/java-features-trainer/src/com/intellij/java/ift/lesson/essential/CommonLogicForOnboardingTours.kt +++ b/java/java-features-trainer/src/com/intellij/java/ift/lesson/essential/CommonLogicForOnboardingTours.kt @@ -49,7 +49,6 @@ import training.dsl.LessonUtil.restoreIfModifiedOrMoved import training.dsl.LessonUtil.restorePopupPosition import training.learn.LessonsBundle import training.learn.course.KLesson -import training.learn.course.LessonProperties import training.learn.lesson.general.run.clearBreakpoints import training.learn.lesson.general.run.toggleBreakpointTask import training.ui.LearningUiHighlightingManager @@ -357,7 +356,7 @@ abstract class CommonLogicForOnboardingTours(id: String, @Nls lessonName: String val jdkVersionsFuture = CompletableFuture>() runBackgroundableTask(ProjectBundle.message("progress.title.detecting.sdks"), project, false) { indicator -> val jdkVersions = mutableListOf() - SdkDetector.getInstance().detectSdks(JavaSdk.getInstance(), indicator, object : SdkDetector.DetectedSdkListener { + SdkDetector.getInstance().detectSdks(project, JavaSdk.getInstance(), indicator, object : SdkDetector.DetectedSdkListener { override fun onSdkDetected(type: SdkType, version: String, home: String) { jdkVersions.add(version) } diff --git a/java/java-impl/intellij.java.impl.iml b/java/java-impl/intellij.java.impl.iml index 736ed95cbd5f..f9818c146754 100644 --- a/java/java-impl/intellij.java.impl.iml +++ b/java/java-impl/intellij.java.impl.iml @@ -80,6 +80,8 @@ + + diff --git a/java/java-impl/src/com/intellij/openapi/projectRoots/impl/ExistingJdkConfigurationActivity.kt b/java/java-impl/src/com/intellij/openapi/projectRoots/impl/ExistingJdkConfigurationActivity.kt index 6b82b408dd1d..3a26677237a3 100644 --- a/java/java-impl/src/com/intellij/openapi/projectRoots/impl/ExistingJdkConfigurationActivity.kt +++ b/java/java-impl/src/com/intellij/openapi/projectRoots/impl/ExistingJdkConfigurationActivity.kt @@ -42,7 +42,7 @@ private class ExistingJdkConfigurationActivity : ProjectActivity { val rootManager = project.serviceAsync() val addedJdks = registeredJdks.toMutableList() - val priorityPaths = JavaHomeFinder.getFinder().findInJavaHome() + val priorityPaths = JavaHomeFinder.getFinder(project).findInJavaHome() writeAction { // Register collected JDKs diff --git a/java/java-impl/src/com/intellij/openapi/projectRoots/impl/JavaSdkImpl.java b/java/java-impl/src/com/intellij/openapi/projectRoots/impl/JavaSdkImpl.java index 2e92f97f0190..13d3878bcc7e 100644 --- a/java/java-impl/src/com/intellij/openapi/projectRoots/impl/JavaSdkImpl.java +++ b/java/java-impl/src/com/intellij/openapi/projectRoots/impl/JavaSdkImpl.java @@ -13,6 +13,7 @@ import com.intellij.openapi.extensions.ExtensionPointUtil; import com.intellij.openapi.fileEditor.impl.LoadTextUtil; import com.intellij.openapi.fileTypes.FileTypeManager; import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.project.Project; import com.intellij.openapi.project.ProjectBundle; import com.intellij.openapi.projectRoots.*; import com.intellij.openapi.roots.AnnotationOrderRootType; @@ -33,6 +34,7 @@ import com.intellij.openapi.vfs.newvfs.events.VFileContentChangeEvent; import com.intellij.openapi.vfs.newvfs.events.VFileCreateEvent; import com.intellij.openapi.vfs.newvfs.events.VFileDeleteEvent; import com.intellij.openapi.vfs.newvfs.events.VFileEvent; +import com.intellij.platform.eel.provider.EelProviderUtil; import com.intellij.pom.java.LanguageLevel; import com.intellij.util.PathUtil; import com.intellij.util.concurrency.AppExecutorUtil; @@ -189,7 +191,12 @@ public final class JavaSdkImpl extends JavaSdk { @Override public @NotNull Collection suggestHomePaths() { - return JavaHomeFinder.suggestHomePaths(); + return suggestHomePaths(null); + } + + @Override + public @NotNull Collection suggestHomePaths(@Nullable Project project) { + return JavaHomeFinder.suggestHomePaths(EelProviderUtil.getEelApiBlocking(project), false); } @Override diff --git a/platform/lang-core/api-dump.txt b/platform/lang-core/api-dump.txt index f4b05f5400c1..a1e71af67a22 100644 --- a/platform/lang-core/api-dump.txt +++ b/platform/lang-core/api-dump.txt @@ -475,7 +475,8 @@ f:com.intellij.openapi.projectRoots.JdkCommandLineSetup$Companion com.intellij.openapi.projectRoots.JdkFinder - a:defaultJavaLocation():java.lang.String - s:getInstance():com.intellij.openapi.projectRoots.JdkFinder -- a:suggestHomePaths():java.util.List +- suggestHomePaths():java.util.List +- a:suggestHomePaths(com.intellij.openapi.project.Project):java.util.List f:com.intellij.openapi.projectRoots.JdkUtil - sf:AGENT_RUNTIME_CLASSPATH:com.intellij.openapi.util.Key - sf:COMMAND_LINE_CONTENT:com.intellij.openapi.util.Key @@ -548,6 +549,7 @@ a:com.intellij.openapi.projectRoots.SdkType - showCustomCreateUI(com.intellij.openapi.projectRoots.SdkModel,javax.swing.JComponent,com.intellij.openapi.projectRoots.Sdk,com.intellij.util.Consumer):V - a:suggestHomePath():java.lang.String - suggestHomePaths():java.util.Collection +- suggestHomePaths(com.intellij.openapi.project.Project):java.util.Collection - a:suggestSdkName(java.lang.String,java.lang.String):java.lang.String - supportsCustomCreateUI():Z c:com.intellij.openapi.projectRoots.SimpleJavaSdkType @@ -570,6 +572,7 @@ c:com.intellij.openapi.projectRoots.SimpleJavaSdkType - saveAdditionalData(com.intellij.openapi.projectRoots.SdkAdditionalData,org.jdom.Element):V - suggestHomePath():java.lang.String - suggestHomePaths():java.util.Collection +- suggestHomePaths(com.intellij.openapi.project.Project):java.util.Collection - suggestSdkName(java.lang.String,java.lang.String):java.lang.String - versionStringComparator():java.util.Comparator f:com.intellij.openapi.projectRoots.impl.SdkVersionUtil diff --git a/platform/lang-core/src/com/intellij/openapi/projectRoots/JdkFinder.java b/platform/lang-core/src/com/intellij/openapi/projectRoots/JdkFinder.java index 800b00864328..10b0dc9650cd 100644 --- a/platform/lang-core/src/com/intellij/openapi/projectRoots/JdkFinder.java +++ b/platform/lang-core/src/com/intellij/openapi/projectRoots/JdkFinder.java @@ -2,6 +2,7 @@ package com.intellij.openapi.projectRoots; import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.project.Project; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -24,7 +25,25 @@ public interface JdkFinder { * If no JDK found, returns possible folders to start file chooser. * The method is heavy, it is not recommended to run it from EDT thread. * @return suggested sdk home paths (sorted) + * + * @deprecated Consider using {@link JdkFinder#suggestHomePaths(Project)}. + * The JDK should be searched on the machine where the project is located, + * not where the IDE is running. */ @NotNull - List suggestHomePaths(); + @Deprecated + default List<@NotNull String> suggestHomePaths() { + return suggestHomePaths(null); + } + + /** + * Tries to find existing Java SDKs on the computer that contains {@code project}. + * If no JDK found, returns possible folders to start file chooser. + * The method is heavy, it is not recommended to run it from EDT thread. + * + * @param project the project for which JDK should be suggested, or {@code null} if JDK should be searched locally + * @return suggested sdk home paths (sorted) + */ + @NotNull + List<@NotNull String> suggestHomePaths(@Nullable Project project); } diff --git a/platform/lang-core/src/com/intellij/openapi/projectRoots/SdkType.java b/platform/lang-core/src/com/intellij/openapi/projectRoots/SdkType.java index 0f1538f8dae3..334c289f5cff 100644 --- a/platform/lang-core/src/com/intellij/openapi/projectRoots/SdkType.java +++ b/platform/lang-core/src/com/intellij/openapi/projectRoots/SdkType.java @@ -24,6 +24,7 @@ import java.util.Collection; import java.util.Comparator; import java.util.List; + /** * Inherit from this class and register implementation as {@code sdkType} extension in plugin.xml to provide a custom type of * SDK. Users can create and assign SDKs to modules in Project Structure dialog. @@ -63,12 +64,28 @@ public abstract class SdkType implements SdkTypeId { * for possible interruption request. It is not recommended to call this method from a ETD thread. See * an alternative {@link #suggestHomePath()} method for EDT-friendly calls. * @see #suggestHomePath() + * + * @deprecated Use {@link #suggestHomePaths(Project)} */ + @Deprecated public @NotNull Collection suggestHomePaths() { String home = suggestHomePath(); return ContainerUtil.createMaybeSingletonList(home); } + /** + * Returns a list of all valid SDKs found on the host where {@code project} is located. + *

+ * E.g. for Python SDK on Unix the method may return {@code ["/usr/bin/python2", "/usr/bin/python3"]}. + *

+ * This method may take significant time to execute. The implementation may check {@link ProgressManager#checkCanceled()} + * for possible interruption request. It is not recommended to call this method from a ETD thread. See + * an alternative {@link #suggestHomePath()} method for EDT-friendly calls. + */ + public @NotNull Collection suggestHomePaths(@Nullable Project project) { + return suggestHomePaths(); + } + /** * This method is used to decide if a given {@link VirtualFile} has something in common * with this {@link SdkType}. diff --git a/platform/lang-core/src/com/intellij/openapi/projectRoots/SimpleJavaSdkType.java b/platform/lang-core/src/com/intellij/openapi/projectRoots/SimpleJavaSdkType.java index 14043e83cd46..4276765268ca 100644 --- a/platform/lang-core/src/com/intellij/openapi/projectRoots/SimpleJavaSdkType.java +++ b/platform/lang-core/src/com/intellij/openapi/projectRoots/SimpleJavaSdkType.java @@ -1,8 +1,7 @@ // Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.intellij.openapi.projectRoots; -import com.intellij.openapi.application.Application; -import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.project.Project; import com.intellij.openapi.project.ProjectBundle; import com.intellij.openapi.projectRoots.impl.SdkVersionUtil; import com.intellij.openapi.util.Comparing; @@ -80,12 +79,17 @@ public class SimpleJavaSdkType extends SdkType implements JavaSdkType { @Override public @NotNull Collection suggestHomePaths() { + return suggestHomePaths(null); + } + + @Override + public @NotNull Collection suggestHomePaths(@Nullable Project project) { //there is no need to search for JDKs if there is JavaSdkImpl registered if (!notSimpleJavaSdkTypeIfAlternativeExists().test(this)) { return Collections.emptyList(); } - return JdkFinder.getInstance().suggestHomePaths(); + return JdkFinder.getInstance().suggestHomePaths(project); } @Override diff --git a/platform/lang-impl/api-dump-unreviewed.txt b/platform/lang-impl/api-dump-unreviewed.txt index 7bacd02ea4e8..6f9a31574f27 100644 --- a/platform/lang-impl/api-dump-unreviewed.txt +++ b/platform/lang-impl/api-dump-unreviewed.txt @@ -14525,6 +14525,7 @@ f:com.intellij.openapi.roots.ui.configuration.SdkComboBoxModel$Companion - bs:createSdkComboBoxModel$default(com.intellij.openapi.roots.ui.configuration.SdkComboBoxModel$Companion,com.intellij.openapi.project.Project,com.intellij.openapi.roots.ui.configuration.projectRoot.ProjectSdksModel,java.util.function.Predicate,java.util.function.Predicate,java.util.function.Predicate,I,java.lang.Object):com.intellij.openapi.roots.ui.configuration.SdkComboBoxModel c:com.intellij.openapi.roots.ui.configuration.SdkDetector - ():V +- detectSdks(com.intellij.openapi.project.Project,com.intellij.openapi.projectRoots.SdkType,com.intellij.openapi.progress.ProgressIndicator,com.intellij.openapi.roots.ui.configuration.SdkDetector$DetectedSdkListener):V - detectSdks(com.intellij.openapi.projectRoots.SdkType,com.intellij.openapi.progress.ProgressIndicator,com.intellij.openapi.roots.ui.configuration.SdkDetector$DetectedSdkListener):V - getDetectedSdksWithUpdate(com.intellij.openapi.project.Project,com.intellij.openapi.Disposable,com.intellij.openapi.application.ModalityState,com.intellij.openapi.roots.ui.configuration.SdkDetector$DetectedSdkListener):V - s:getInstance():com.intellij.openapi.roots.ui.configuration.SdkDetector @@ -14821,7 +14822,6 @@ c:com.intellij.openapi.roots.ui.configuration.projectRoot.ProjectSdksModel - doAdd(com.intellij.openapi.projectRoots.Sdk,java.util.function.Consumer):V - doAdd(javax.swing.JComponent,com.intellij.openapi.projectRoots.Sdk,com.intellij.openapi.projectRoots.SdkType,com.intellij.util.Consumer):V - doAdd(javax.swing.JComponent,com.intellij.openapi.projectRoots.SdkType,com.intellij.util.Consumer):V -- doDownload(com.intellij.openapi.roots.ui.configuration.projectRoot.SdkDownload,javax.swing.JComponent,com.intellij.openapi.projectRoots.Sdk,com.intellij.openapi.projectRoots.SdkType,java.util.function.Consumer):V - downloadSdk(com.intellij.openapi.projectRoots.Sdk):V - findSdk(com.intellij.openapi.projectRoots.Sdk):com.intellij.openapi.projectRoots.Sdk - findSdk(java.lang.String):com.intellij.openapi.projectRoots.Sdk diff --git a/platform/lang-impl/intellij.platform.lang.impl.iml b/platform/lang-impl/intellij.platform.lang.impl.iml index d43ceeb7d2be..3f54f6299caf 100644 --- a/platform/lang-impl/intellij.platform.lang.impl.iml +++ b/platform/lang-impl/intellij.platform.lang.impl.iml @@ -107,5 +107,7 @@ + + \ No newline at end of file diff --git a/platform/lang-impl/src/com/intellij/openapi/projectRoots/impl/JavaHomeFinder.java b/platform/lang-impl/src/com/intellij/openapi/projectRoots/impl/JavaHomeFinder.java index e947f594648e..fbd57725ecd7 100644 --- a/platform/lang-impl/src/com/intellij/openapi/projectRoots/impl/JavaHomeFinder.java +++ b/platform/lang-impl/src/com/intellij/openapi/projectRoots/impl/JavaHomeFinder.java @@ -1,10 +1,12 @@ // Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.intellij.openapi.projectRoots.impl; +import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.SystemInfo; import com.intellij.openapi.util.SystemInfoRt; import com.intellij.openapi.util.registry.Registry; +import com.intellij.platform.eel.EelApi; import com.intellij.util.EnvironmentUtil; import com.intellij.util.SystemProperties; import com.intellij.util.containers.ContainerUtil; @@ -21,6 +23,10 @@ import java.util.Collection; import java.util.Collections; import java.util.List; +import static com.intellij.openapi.projectRoots.impl.JavaHomeFinderEel.javaHomeFinderEel; +import static com.intellij.platform.eel.provider.EelProviderUtil.getEelApiBlocking; +import static com.intellij.platform.eel.provider.EelProviderUtil.getLocalEel; + @ApiStatus.Internal public abstract class JavaHomeFinder { public static class SystemInfoProvider { @@ -54,7 +60,11 @@ public abstract class JavaHomeFinder { * Tries to find existing Java SDKs on this computer. * If no JDK found, returns possible directories to start file chooser. * @return suggested sdk home paths (sorted) + * + * @deprecated Please use {@link JavaHomeFinder#suggestHomePaths(Project)}. The project can be located on a remote machine, + * and the SDK should be local to the project, not to the IDE. */ + @Deprecated public static @NotNull List suggestHomePaths() { return suggestHomePaths(false); } @@ -65,7 +75,22 @@ public abstract class JavaHomeFinder { * or that need the embedded JetBrains Runtime. */ public static @NotNull List suggestHomePaths(boolean forceEmbeddedJava) { - JavaHomeFinderBasic javaFinder = getFinder(forceEmbeddedJava); + return suggestHomePaths(getLocalEel(), forceEmbeddedJava); + } + + /** + * Tries to find Java SDKs on the machine where {@code project} is located. + * If no JDK found, returns possible directories to start file chooser. + * + * @return suggested sdk home paths (sorted) + */ + public static @NotNull List<@NotNull String> suggestHomePaths(@Nullable Project project) { + return suggestHomePaths(getEelApiBlocking(project), false); + } + + @ApiStatus.Internal + public static @NotNull List suggestHomePaths(@NotNull EelApi eel, boolean forceEmbeddedJava) { + JavaHomeFinderBasic javaFinder = getFinder(eel, forceEmbeddedJava); if (javaFinder == null) return Collections.emptyList(); ArrayList paths = new ArrayList<>(javaFinder.findExistingJdks()); @@ -77,13 +102,21 @@ public abstract class JavaHomeFinder { return forceEmbeddedJava || Registry.is("java.detector.enabled", true); } - private static JavaHomeFinderBasic getFinder(boolean forceEmbeddedJava) { + private static JavaHomeFinderBasic getFinder(@NotNull EelApi eel, boolean forceEmbeddedJava) { if (!isDetectorEnabled(forceEmbeddedJava)) return null; - return getFinder().checkEmbeddedJava(forceEmbeddedJava); + return getFinder(eel).checkEmbeddedJava(forceEmbeddedJava); } - public static @NotNull JavaHomeFinderBasic getFinder() { + public static @NotNull JavaHomeFinderBasic getFinder(@Nullable Project project) { + return getFinder(getEelApiBlocking(project)); + } + + private static @NotNull JavaHomeFinderBasic getFinder(@NotNull EelApi eel) { + if (Registry.is("java.home.finder.use.eel")) { + return javaHomeFinderEel(eel); + } + SystemInfoProvider systemInfoProvider = new SystemInfoProvider(); if (SystemInfo.isWindows) { diff --git a/platform/lang-impl/src/com/intellij/openapi/projectRoots/impl/JavaHomeFinderBasic.java b/platform/lang-impl/src/com/intellij/openapi/projectRoots/impl/JavaHomeFinderBasic.java index 37e113fbcde2..a72b2b7e909d 100644 --- a/platform/lang-impl/src/com/intellij/openapi/projectRoots/impl/JavaHomeFinderBasic.java +++ b/platform/lang-impl/src/com/intellij/openapi/projectRoots/impl/JavaHomeFinderBasic.java @@ -10,6 +10,7 @@ import com.intellij.openapi.projectRoots.ProjectJdkTable; import com.intellij.openapi.projectRoots.Sdk; import com.intellij.openapi.projectRoots.impl.jdkDownloader.JdkInstaller; import com.intellij.openapi.projectRoots.impl.jdkDownloader.JdkInstallerStore; +import com.intellij.openapi.projectRoots.impl.jdkDownloader.OsAbstractionForJdkInstaller; import com.intellij.openapi.util.text.StringUtilRt; import com.intellij.util.ArrayUtil; import com.intellij.util.SystemProperties; @@ -156,7 +157,7 @@ public class JavaHomeFinderBasic { Set paths = new HashSet<>(); if (myCheckDefaultInstallDir) { - paths.add(JdkInstaller.getInstance().defaultInstallDir()); + paths.add(JdkInstaller.getInstance().defaultInstallDir((OsAbstractionForJdkInstaller)null)); } if (myCheckUsedInstallDirs) { diff --git a/platform/lang-impl/src/com/intellij/openapi/projectRoots/impl/JavaHomeFinderEel.kt b/platform/lang-impl/src/com/intellij/openapi/projectRoots/impl/JavaHomeFinderEel.kt new file mode 100644 index 000000000000..95eaadac2ded --- /dev/null +++ b/platform/lang-impl/src/com/intellij/openapi/projectRoots/impl/JavaHomeFinderEel.kt @@ -0,0 +1,116 @@ +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +@file:JvmName("JavaHomeFinderEel") + +package com.intellij.openapi.projectRoots.impl + +import com.intellij.openapi.components.Service +import com.intellij.openapi.components.service +import com.intellij.openapi.progress.runBlockingMaybeCancellable +import com.intellij.platform.eel.* +import com.intellij.platform.eel.fs.* +import com.intellij.platform.eel.path.EelPath +import com.intellij.platform.eel.provider.utils.awaitProcessResult +import com.intellij.util.suspendingLazy +import kotlinx.coroutines.CoroutineScope +import java.io.IOException +import java.nio.file.Path + +private class EelSystemInfoProvider(private val eel: EelApi) : JavaHomeFinder.SystemInfoProvider() { + @Service + private class ScopeService(coroutineScope: CoroutineScope) : CoroutineScope by coroutineScope + + private val environmentVariables = service().suspendingLazy { + eel.exec.fetchLoginShellEnvVariables() + } + + override fun getEnvironmentVariable(name: String): String? = runBlockingMaybeCancellable { + environmentVariables.getValue()[name] + } + + override fun getPath(path: String, vararg more: String): Path = + eel.mapper.toNioPath(EelPath.Absolute.parse(eel.fs.pathOs, path, *more)) + + override fun getUserHome(): Path? = with(eel) { + mapper.toNioPath(fs.user.home) + } + + override fun getFsRoots(): Collection = runBlockingMaybeCancellable { + val paths = when (val fs = eel.fs) { + is EelFileSystemPosixApi -> listOf(EelPath.Absolute.build("/")) + is EelFileSystemWindowsApi -> fs.getRootDirectories() + else -> error(fs) + } + paths.map(eel.mapper::toNioPath) + } + + override fun getPathSeparator(): String? = + when (val fs = eel.fs) { + is EelFileSystemPosixApi -> ":" + is EelFileSystemWindowsApi -> ";" + else -> error(fs) + } + + private val isCaseSensitive = service().suspendingLazy { + val testDir = eel.fs.user.home + val type = when (val stat = eel.fs.stat(testDir, EelFileSystemApi.SymlinkPolicy.RESOLVE_AND_FOLLOW)) { + is EelResult.Ok -> stat.value.type + is EelResult.Error -> error(stat) + } + + val sensitivity = when (val type = type) { + is EelFileInfo.Type.Directory -> type.sensitivity + is EelFileInfo.Type.Other, is EelFileInfo.Type.Regular, is EelPosixFileInfo.Type.Symlink -> error("Not a directory") + } + + when (sensitivity) { + EelFileInfo.CaseSensitivity.SENSITIVE -> true + EelFileInfo.CaseSensitivity.INSENSITIVE -> false + EelFileInfo.CaseSensitivity.UNKNOWN -> true + } + } + + override fun isFileSystemCaseSensitive(): Boolean = runBlockingMaybeCancellable { + isCaseSensitive.getValue() + } +} + +internal fun javaHomeFinderEel(eel: EelApi): JavaHomeFinderBasic { + val systemInfoProvider = EelSystemInfoProvider(eel) + + val parentFinder = when (eel.platform) { + is EelPlatform.Windows -> + JavaHomeFinderWindows( + registeredJdks = true, + wslJdks = false, + systemInfoProvider = systemInfoProvider, + processRunner = { cmd -> + runBlockingMaybeCancellable { + // TODO Introduce Windows Registry access in EelApi + val process = eel.exec.execute(EelExecApi.ExecuteProcessOptions.Builder(cmd.first()).args(cmd.drop(1)).build()).getOrThrow { + throw IOException("Failed to read Windows Registry: $it") + } + val result = process.awaitProcessResult() + if (result.exitCode != 0) throw IOException("Failed to read Windows Registry: $result") + result.stdout + } + } + ) + + is EelPlatform.Darwin -> JavaHomeFinderMac(systemInfoProvider) + + is EelPlatform.Linux -> { + val checkPaths = JavaHomeFinder.DEFAULT_JAVA_LINUX_PATHS.toMutableSet() + val userHome = eel.fs.user.home + checkPaths.add(eel.mapper.toNioPath(userHome.resolve(EelPath.Relative.build(".jdks"))).toString()) + JavaHomeFinderBasic(systemInfoProvider).checkSpecifiedPaths(*checkPaths.toTypedArray()) + } + + is EelPlatform.Posix -> JavaHomeFinderBasic(systemInfoProvider) + } + + val isLocal = eel is LocalEelApi + return parentFinder + .checkDefaultInstallDir(isLocal) + .checkUsedInstallDirs(isLocal) + .checkConfiguredJdks(isLocal) +} \ No newline at end of file diff --git a/platform/lang-impl/src/com/intellij/openapi/projectRoots/impl/JavaHomeFinderWindows.kt b/platform/lang-impl/src/com/intellij/openapi/projectRoots/impl/JavaHomeFinderWindows.kt index 57c53ed2a66e..e622dee123fe 100644 --- a/platform/lang-impl/src/com/intellij/openapi/projectRoots/impl/JavaHomeFinderWindows.kt +++ b/platform/lang-impl/src/com/intellij/openapi/projectRoots/impl/JavaHomeFinderWindows.kt @@ -23,7 +23,7 @@ class JavaHomeFinderWindows : JavaHomeFinderBasic { const val defaultJavaLocation: String = "C:\\Program Files" @Suppress("SpellCheckingInspection") - private const val regCommand = """reg query HKLM\SOFTWARE\JavaSoft\JDK /s /v JavaHome""" + private val regCommand = listOf("reg", "query", """HKLM\SOFTWARE\JavaSoft\JDK""", "/s", "/v", "JavaHome") private val javaHomePattern = Regex("""^\s+JavaHome\s+REG_SZ\s+(\S.+\S)\s*$""", setOf(MULTILINE, IGNORE_CASE)) @@ -40,9 +40,17 @@ class JavaHomeFinderWindows : JavaHomeFinderBasic { } } - constructor(registeredJdks: Boolean, - wslJdks: Boolean, - systemInfoProvider: JavaHomeFinder.SystemInfoProvider) : super(systemInfoProvider) { + private val processRunner: (cmd: List) -> CharSequence + + @JvmOverloads + constructor( + registeredJdks: Boolean, + wslJdks: Boolean, + systemInfoProvider: JavaHomeFinder.SystemInfoProvider, + processRunner: (cmd: List) -> CharSequence = { cmd -> WindowsRegistryUtil.readRegistry(cmd.joinToString(" ")) }, + ) : super(systemInfoProvider) { + this.processRunner = processRunner + if (registeredJdks) { /** Whether the OS is 64-bit (**important**: it's not the same as [com.intellij.util.system.CpuArch]). */ val os64bit = !systemInfoProvider.getEnvironmentVariable("ProgramFiles(x86)").isNullOrBlank() @@ -90,11 +98,11 @@ class JavaHomeFinderWindows : JavaHomeFinderBasic { val cmd = when (b) { null -> regCommand - Bitness.x32 -> "$regCommand /reg:32" - Bitness.x64 -> "$regCommand /reg:64" + Bitness.x32 -> regCommand + "/reg:32" + Bitness.x64 -> regCommand + "/reg:64" } try { - val registryLines: CharSequence = WindowsRegistryUtil.readRegistry(cmd) + val registryLines: CharSequence = processRunner(cmd) val registeredPaths = gatherHomePaths(registryLines) val folders: MutableSet = TreeSet() for (rp in registeredPaths) { diff --git a/platform/lang-impl/src/com/intellij/openapi/projectRoots/impl/JdkFinderImpl.java b/platform/lang-impl/src/com/intellij/openapi/projectRoots/impl/JdkFinderImpl.java index c72c4bab714c..2207267c4455 100644 --- a/platform/lang-impl/src/com/intellij/openapi/projectRoots/impl/JdkFinderImpl.java +++ b/platform/lang-impl/src/com/intellij/openapi/projectRoots/impl/JdkFinderImpl.java @@ -1,7 +1,9 @@ // Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.intellij.openapi.projectRoots.impl; +import com.intellij.openapi.project.Project; import com.intellij.openapi.projectRoots.JdkFinder; +import com.intellij.platform.eel.provider.EelProviderUtil; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -19,4 +21,9 @@ public final class JdkFinderImpl implements JdkFinder { public @Nullable String defaultJavaLocation() { return JavaHomeFinder.defaultJavaLocation(); } + + @Override + public @NotNull List<@NotNull String> suggestHomePaths(@Nullable Project project) { + return JavaHomeFinder.suggestHomePaths(EelProviderUtil.getEelApiBlocking(project), false); + } } diff --git a/platform/lang-impl/src/com/intellij/openapi/projectRoots/impl/jdkDownloader/JdkAuto.kt b/platform/lang-impl/src/com/intellij/openapi/projectRoots/impl/jdkDownloader/JdkAuto.kt index 0000fe386752..6b2aa9122253 100644 --- a/platform/lang-impl/src/com/intellij/openapi/projectRoots/impl/jdkDownloader/JdkAuto.kt +++ b/platform/lang-impl/src/com/intellij/openapi/projectRoots/impl/jdkDownloader/JdkAuto.kt @@ -6,11 +6,13 @@ import com.intellij.openapi.application.ApplicationInfo import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.runWriteAction import com.intellij.openapi.components.BaseState +import com.intellij.openapi.components.Service import com.intellij.openapi.components.SimplePersistentStateComponent import com.intellij.openapi.components.service import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.progress.ProcessCanceledException import com.intellij.openapi.progress.ProgressIndicator +import com.intellij.openapi.progress.runBlockingCancellable import com.intellij.openapi.project.Project import com.intellij.openapi.project.ProjectBundle import com.intellij.openapi.projectRoots.* @@ -26,10 +28,15 @@ import com.intellij.openapi.util.NlsSafe import com.intellij.openapi.util.registry.Registry import com.intellij.openapi.vfs.JarFileSystem import com.intellij.openapi.vfs.VirtualFile +import com.intellij.platform.eel.provider.getEelApi +import com.intellij.platform.eel.provider.localEel +import com.intellij.util.SuspendingLazy import com.intellij.util.lang.JavaVersion +import com.intellij.util.suspendingLazy import com.intellij.util.system.CpuArch import com.intellij.util.text.nullize import com.intellij.util.xmlb.annotations.XCollection +import kotlinx.coroutines.CoroutineScope import org.jetbrains.annotations.Nls import org.jetbrains.annotations.NotNull import org.jetbrains.jps.model.java.JdkVersionDetector @@ -99,6 +106,9 @@ class JdkAuto : UnknownSdkResolver, JdkDownloaderBase { return createResolverImpl(project, indicator) } + @Service + private class ServiceScope(val coroutineScope: CoroutineScope) + fun createResolverImpl(project: Project?, indicator: ProgressIndicator): UnknownSdkLookup? { val sdkType = SdkType.getAllTypeList() .asSequence() @@ -111,19 +121,27 @@ class JdkAuto : UnknownSdkResolver, JdkDownloaderBase { }.firstOrNull() ?: return null return object : UnknownSdkLookup { + private val coroutineScope = service().coroutineScope + val projectWslDistribution by lazy { project?.basePath?.let { WslPath.getDistributionByWindowsUncPath(it) } } + val eel = coroutineScope.suspendingLazy { + project?.getEelApi() ?: localEel + } + + @Deprecated("Remove when EelApi is stabilized") val projectInWsl by lazy { project?.basePath?.let { WslPath.isWslUncPath(it) } == true } - val lazyDownloadModel: List by lazy { + val lazyDownloadModel: SuspendingLazy> = coroutineScope.suspendingLazy { indicator.pushState() indicator.text = ProjectBundle.message("progress.text.downloading.jdk.list") try { val jdkPredicate = when { + Registry.`is`("java.home.finder.use.eel") -> JdkPredicate.forEel(eel.getValue()) projectInWsl -> JdkPredicate.forWSL() else -> JdkPredicate.default() } @@ -196,12 +214,16 @@ class JdkAuto : UnknownSdkResolver, JdkDownloaderBase { override fun proposeDownload(sdk: UnknownSdk, indicator: ProgressIndicator): UnknownSdkDownloadableSdkFix? = proposeDownload(sdk, indicator, null) override fun proposeDownload(sdk: UnknownSdk, indicator: ProgressIndicator, lookupReason: @Nls String?): UnknownSdkDownloadableSdkFix? { + return runBlockingCancellable { proposeDownload0(sdk, lookupReason) } + } + + private suspend fun proposeDownload0(sdk: UnknownSdk, lookupReason: @Nls String?): UnknownSdkDownloadableSdkFix? { if (sdk.sdkType != sdkType) return null val req = parseSdkRequirement(sdk) ?: return null LOG.info("Looking for a possible download for ${sdk.sdkType.presentableName} with name ${sdk.sdkName} ; $req") - val jdks = lazyDownloadModel + val jdks = lazyDownloadModel.getValue() .asSequence() .filter { CpuArch.fromString(it.arch) == CpuArch.CURRENT } .mapNotNull { @@ -221,10 +243,10 @@ class JdkAuto : UnknownSdkResolver, JdkDownloaderBase { val jarConfigurator = JarSdkConfigurator(resolveHint(sdk)?.includeJars ?: listOf()) - if (jdkToDownload != null) { - return singleJdkDownloadFix(jarConfigurator, jdkToDownload.first, lookupReason) + return if (jdkToDownload != null) { + singleJdkDownloadFix(jarConfigurator, jdkToDownload.first, lookupReason) } else { - return multipleJdksDownloadFix(jarConfigurator, matchingJdks, lookupReason) + multipleJdksDownloadFix(jarConfigurator, matchingJdks, lookupReason) } } @@ -239,11 +261,11 @@ class JdkAuto : UnknownSdkResolver, JdkDownloaderBase { override fun getSdkLookupReason(): String? = lookupReason override fun getDownloadDescription() = jdkToDownload.fullPresentationText + " (${(jdkToDownload.archiveSize / 1024 / 1024).toInt()} MB)" - override fun createTask(indicator: ProgressIndicator): SdkDownloadTask { + override fun createTask(indicator: ProgressIndicator): SdkDownloadTask = runBlockingCancellable { val jdkInstaller = JdkInstaller.getInstance() - val homeDir = jdkInstaller.defaultInstallDir(jdkToDownload, projectWslDistribution) + val homeDir = jdkInstaller.defaultInstallDir(jdkToDownload, eel.getValue(), projectWslDistribution) val request = jdkInstaller.prepareJdkInstallation(jdkToDownload, homeDir) - return JdkDownloaderBase.newDownloadTask(jdkToDownload, request, project) + JdkDownloaderBase.newDownloadTask(jdkToDownload, request, project) } override fun toString() = "UnknownSdkDownloadableFix{${jdkToDownload.fullPresentationText}, wsl=${projectWslDistribution}}" @@ -264,11 +286,11 @@ class JdkAuto : UnknownSdkResolver, JdkDownloaderBase { override fun getSdkLookupReason(): String? = lookupReason ?: ProjectBundle.message("sdk.download.picker.text", ApplicationInfo.getInstance().fullApplicationName) override fun getDownloadDescription() = item.fullPresentationText + " (${item.archiveSizeInMB} MB)" - override fun createTask(indicator: ProgressIndicator): SdkDownloadTask { + override fun createTask(indicator: ProgressIndicator): SdkDownloadTask = runBlockingCancellable { val jdkInstaller = JdkInstaller.getInstance() - val path = homeDir ?: jdkInstaller.defaultInstallDir(item, projectWslDistribution) + val path = homeDir ?: jdkInstaller.defaultInstallDir(item, eel.getValue(), projectWslDistribution) val request = jdkInstaller.prepareJdkInstallation(item, path) - return JdkDownloaderBase.newDownloadTask(item, request, project) + JdkDownloaderBase.newDownloadTask(item, request, project) } override fun toString() = "UnknownSdkMultipleDownloadsFix{${items.joinToString(" / ") { it.fullPresentationText }}, wsl=${projectWslDistribution}}" @@ -286,7 +308,7 @@ class JdkAuto : UnknownSdkResolver, JdkDownloaderBase { indicator.text = ProjectBundle.message("progress.text.detecting.local.jdks") val result = mutableListOf() - SdkDetector.getInstance().detectSdks(sdkType, indicator, object : DetectedSdkListener { + SdkDetector.getInstance().detectSdks(project, sdkType, indicator, object : DetectedSdkListener { override fun onSdkDetected(type: SdkType, version: String, home: String) { val javaVersion = JavaVersion.tryParse(version) ?: return val suggestedName = JdkUtil.suggestJdkName(version) ?: return diff --git a/platform/lang-impl/src/com/intellij/openapi/projectRoots/impl/jdkDownloader/JdkDownloadDialog.kt b/platform/lang-impl/src/com/intellij/openapi/projectRoots/impl/jdkDownloader/JdkDownloadDialog.kt index d6305d4263f6..0cd815a6acd0 100644 --- a/platform/lang-impl/src/com/intellij/openapi/projectRoots/impl/jdkDownloader/JdkDownloadDialog.kt +++ b/platform/lang-impl/src/com/intellij/openapi/projectRoots/impl/jdkDownloader/JdkDownloadDialog.kt @@ -12,6 +12,7 @@ import com.intellij.openapi.ui.popup.ListSeparator import com.intellij.openapi.util.NlsContexts import com.intellij.openapi.util.NlsSafe import com.intellij.openapi.util.io.FileUtil +import com.intellij.platform.eel.EelApi import com.intellij.ui.* import com.intellij.ui.components.textFieldWithBrowseButton import com.intellij.ui.dsl.builder.AlignX @@ -204,14 +205,26 @@ fun buildJdkDownloaderModel(allItems: List, itemFilter: (JdkItem) -> Bo } internal class JdkDownloaderMergedModel( + @Deprecated("Remove when eelModel is stabilized") private val mainModel: JdkDownloaderModel, + + @Deprecated("Remove when eelModel is stabilized") private val wslModel: JdkDownloaderModel?, + + private val eelModel: JdkDownloaderModel?, + + val eel: EelApi?, val wslDistributions: List, val projectWSLDistribution: WSLDistribution? ) { + val hasEel: Boolean get() = eelModel != null + + @Deprecated("Remove when eelModel is stabilized") val hasWsl: Boolean get() = wslModel != null + @Deprecated("Remove when eelModel is stabilized") fun selectModel(wsl: Boolean): JdkDownloaderModel = when { + eelModel != null -> eelModel wsl && wslModel != null -> wslModel else -> mainModel } @@ -290,7 +303,7 @@ internal class JdkDownloadDialog( } private fun setupContainer(): JComponent { - if (mergedModel.hasWsl) { + if (mergedModel.hasWsl && !mergedModel.hasEel) { // TODO File chooser for Eel installDirCombo = ComboBox().apply { isEditable = true initBrowsableEditor( @@ -363,7 +376,7 @@ internal class JdkDownloadDialog( vendorComboBox.selectedItem = it.selectItem val newVersion = it.item - val path = JdkInstaller.getInstance().defaultInstallDir(newVersion, mergedModel.projectWSLDistribution).toString() + val path = JdkInstaller.getInstance().defaultInstallDir(newVersion, mergedModel.eel, mergedModel.projectWSLDistribution).toString() val relativePath = FileUtil.getLocationRelativeToUserHome(path) if (installDirTextField != null) { installDirTextField!!.text = relativePath @@ -376,8 +389,11 @@ internal class JdkDownloadDialog( } private fun getSuggestedInstallDirs(newVersion: JdkItem): List { + if (mergedModel.hasEel) { + return listOf(JdkInstaller.getInstance().defaultInstallDir(newVersion, mergedModel.eel, null).toString()) + } return (listOf(null) + mergedModel.wslDistributions).mapTo(LinkedHashSet()) { - JdkInstaller.getInstance().defaultInstallDir(newVersion, it).toString() + JdkInstaller.getInstance().defaultInstallDir(newVersion, null, it).toString() }.map { FileUtil.getLocationRelativeToUserHome(it) } diff --git a/platform/lang-impl/src/com/intellij/openapi/projectRoots/impl/jdkDownloader/JdkDownloader.kt b/platform/lang-impl/src/com/intellij/openapi/projectRoots/impl/jdkDownloader/JdkDownloader.kt index 6bb1f5072688..e3a38b119346 100644 --- a/platform/lang-impl/src/com/intellij/openapi/projectRoots/impl/jdkDownloader/JdkDownloader.kt +++ b/platform/lang-impl/src/com/intellij/openapi/projectRoots/impl/jdkDownloader/JdkDownloader.kt @@ -24,6 +24,8 @@ import com.intellij.openapi.roots.ui.configuration.projectRoot.SdkDownloadTask import com.intellij.openapi.ui.Messages import com.intellij.openapi.util.NlsContexts import com.intellij.openapi.util.registry.Registry +import com.intellij.platform.eel.EelApi +import com.intellij.platform.eel.provider.getEelApiBlocking import org.jetbrains.annotations.ApiStatus.Internal import org.jetbrains.annotations.Nls import java.nio.file.Path @@ -38,10 +40,14 @@ internal val JDK_DOWNLOADER_EXT: DataKey = Dat internal interface JdkDownloaderDialogHostExtension { fun allowWsl() : Boolean = true + fun allowEel() : Boolean = Registry.`is`("java.home.finder.use.eel") + fun createMainPredicate() : JdkPredicate? = null fun createWslPredicate() : JdkPredicate? = null + fun createEelPredicate(eel: EelApi) : JdkPredicate? = null + fun shouldIncludeItem(sdkType: SdkTypeId, item: JdkItem) : Boolean = true } @@ -137,7 +143,22 @@ class JdkDownloader : SdkDownload, JdkDownloaderBase { val mainModel = buildModel(extension.createMainPredicate() ?: JdkPredicate.default()) ?: return@computeInBackground null val wslModel = if (allowWsl && wslDistributions.isNotEmpty()) buildModel(extension.createWslPredicate() ?: JdkPredicate.forWSL()) else null - JdkDownloaderMergedModel(mainModel, wslModel, wslDistributions, projectWslDistribution) + + val eelPair: Pair? = + if (extension.allowEel()) { + val eel = project.getEelApiBlocking() + buildModel(extension.createEelPredicate(eel) ?: JdkPredicate.forEel(eel))?.let { eel to it} + } + else null + + JdkDownloaderMergedModel( + mainModel = mainModel, + wslModel = wslModel, + eelModel = eelPair?.second, + wslDistributions = wslDistributions, + eel = eelPair?.first, + projectWSLDistribution = projectWslDistribution, + ) } } catch (e: Throwable) { @@ -207,7 +228,17 @@ internal fun selectJdkAndPath( val projectWslDistribution = if (allowWsl) project?.basePath?.let { WslPath.getDistributionByWindowsUncPath(it) } else null val mainModel = buildJdkDownloaderModel(items) { extension.shouldIncludeItem(sdkTypeId, it) } - val mergedModel = JdkDownloaderMergedModel(mainModel, null, wslDistributions, projectWslDistribution) + + val eelModel = null // TODO What should be here? + + val mergedModel = JdkDownloaderMergedModel( + mainModel = mainModel, + wslModel = null, + eelModel = eelModel, + eel = null, + wslDistributions = wslDistributions, + projectWSLDistribution = projectWslDistribution, + ) if (project?.isDisposed == true) return null diff --git a/platform/lang-impl/src/com/intellij/openapi/projectRoots/impl/jdkDownloader/JdkInstaller.kt b/platform/lang-impl/src/com/intellij/openapi/projectRoots/impl/jdkDownloader/JdkInstaller.kt index d09153093da6..40bf6b8444e5 100644 --- a/platform/lang-impl/src/com/intellij/openapi/projectRoots/impl/jdkDownloader/JdkInstaller.kt +++ b/platform/lang-impl/src/com/intellij/openapi/projectRoots/impl/jdkDownloader/JdkInstaller.kt @@ -13,20 +13,32 @@ import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.extensions.ExtensionPointName import com.intellij.openapi.progress.ProcessCanceledException import com.intellij.openapi.progress.ProgressIndicator +import com.intellij.openapi.progress.runBlockingCancellable +import com.intellij.openapi.progress.runBlockingMaybeCancellable import com.intellij.openapi.progress.util.ProgressIndicatorBase import com.intellij.openapi.progress.util.RelayUiToDelegateIndicator import com.intellij.openapi.project.Project import com.intellij.openapi.project.ProjectBundle import com.intellij.openapi.projectRoots.JdkUtil +import com.intellij.openapi.projectRoots.impl.jdkDownloader.JdkInstallerEel.unpackJdkOnEel import com.intellij.openapi.projectRoots.impl.jdkDownloader.JdkInstallerWSL.unpackJdkOnWsl import com.intellij.openapi.util.SystemInfo import com.intellij.openapi.util.io.FileUtil import com.intellij.openapi.util.registry.Registry +import com.intellij.platform.eel.EelApi +import com.intellij.platform.eel.EelExecApi +import com.intellij.platform.eel.EelPlatform +import com.intellij.platform.eel.getOrThrow +import com.intellij.platform.eel.path.EelPath +import com.intellij.platform.eel.provider.getEelApi +import com.intellij.platform.eel.provider.utils.awaitProcessResult import com.intellij.util.Urls import com.intellij.util.io.HttpRequests import com.intellij.util.io.delete import com.intellij.util.xmlb.annotations.Tag import com.intellij.util.xmlb.annotations.XCollection +import kotlinx.coroutines.TimeoutCancellationException +import kotlinx.coroutines.withTimeout import org.jetbrains.annotations.ApiStatus.Internal import org.jetbrains.annotations.Nls import java.nio.file.Files @@ -41,6 +53,7 @@ import kotlin.io.path.exists import kotlin.io.path.isDirectory import kotlin.io.path.isRegularFile import kotlin.math.absoluteValue +import kotlin.time.Duration.Companion.milliseconds @Internal interface JdkInstallRequest { @@ -90,21 +103,52 @@ class JdkInstaller : JdkInstallerBase() { override fun findHistoryRoots(feedItem: JdkItem): List = service().findInstallations(feedItem) - public override fun wslDistributionFromPath(targetDir: Path): WSLDistributionForJdkInstaller? { + public override fun wslDistributionFromPath(targetDir: Path): OsAbstractionForJdkInstaller.Wsl? { val d = WslPath.getDistributionByWindowsUncPath(targetDir.toString()) ?: return null return wrap(d) } + public override fun eelFromPath(targetDir: Path): OsAbstractionForJdkInstaller.Eel? = + if (Registry.`is`("java.home.finder.use.eel")) + EelForJdkInstallerImpl(runBlockingMaybeCancellable { + targetDir.getEelApi() + }) + else + null + private fun wrap(d: WSLDistribution) = WSLDistributionForJdkInstallerImpl(d) - private class WSLDistributionForJdkInstallerImpl(val d: WSLDistribution) : WSLDistributionForJdkInstaller { - override fun getWslPath(path: Path): String = d.getWslPath(path) ?: error("Failed to map $path to WSL") + private class WSLDistributionForJdkInstallerImpl(override val d: WSLDistribution) : OsAbstractionForJdkInstaller.Wsl { + override fun getPath(path: Path): String = d.getWslPath(path) ?: error("Failed to map $path to WSL") - override fun executeOnWsl(command: List, dir: String, timeout: Int): ProcessOutput { + override fun execute(command: List, dir: String, timeout: Int): ProcessOutput { return d.executeOnWsl(command, WSLCommandLineOptions().setRemoteWorkingDirectory(dir), timeout, null) } } + private class EelForJdkInstallerImpl(override val eel: EelApi) : OsAbstractionForJdkInstaller.Eel { + override fun getPath(path: Path): String = + eel.mapper.getOriginalPath(path)?.toString() ?: error("Failed to map $path to WSL") + + override fun execute(command: List, dir: String, timeout: Int): ProcessOutput = runBlockingCancellable { + val builder = EelExecApi + .ExecuteProcessOptions.Builder(command.first()) + .args(command.drop(1)) + .workingDirectory(dir) + .build() + val process = eel.exec.execute(builder).getOrThrow() + try { + withTimeout(timeout.milliseconds) { + process.awaitProcessResult() + } + } + catch (_: TimeoutCancellationException) { + process.terminate() // TODO Originally there was a much more difficult logic of termination. + ProcessOutput("", "", -1, true, false) + } + } + } + override fun installJdkImpl(request: JdkInstallRequest, indicator: ProgressIndicator?, project: Project?) { JDK_INSTALL_LISTENER_EP_NAME.forEachExtensionSafe { it.onJdkDownloadStarted(request, project) } try { @@ -115,10 +159,24 @@ class JdkInstaller : JdkInstallerBase() { } } - override fun defaultInstallDir(wslDistribution: WSLDistributionForJdkInstaller?): Path { - if (wslDistribution is WSLDistributionForJdkInstallerImpl) return defaultInstallDir(wslDistribution.d) + override fun defaultInstallDir(osAbstraction: OsAbstractionForJdkInstaller?): Path = + when (osAbstraction) { + is OsAbstractionForJdkInstaller.Eel -> defaultInstallDir(osAbstraction.eel) + is OsAbstractionForJdkInstaller.Wsl -> defaultInstallDir(osAbstraction.d) + null -> defaultInstallDirForLocalOs() + } - return defaultInstallDir() + private fun defaultInstallDir(eel: EelApi): Path { + val userHome = eel.fs.user.home + + val relativePath = EelPath.Relative.parse( + when (eel.platform) { + is EelPlatform.Windows, is EelPlatform.Linux -> ".jdks" + is EelPlatform.Darwin -> "Library/Java/JavaVirtualMachines" + }) + + val jdks = userHome.resolve(relativePath) + return eel.mapper.toNioPath(jdks) } private fun defaultInstallDir(wslDistribution: WSLDistribution?) : Path { @@ -128,10 +186,10 @@ class JdkInstaller : JdkInstallerBase() { } } - return defaultInstallDir() + return defaultInstallDirForLocalOs() } - override fun defaultInstallDir(): Path { + private fun defaultInstallDirForLocalOs(): Path { val explicitHome = System.getProperty("jdk.downloader.home") if (explicitHome != null) { return Paths.get(explicitHome) @@ -147,15 +205,26 @@ class JdkInstaller : JdkInstallerBase() { } } - fun defaultInstallDir(newVersion: JdkItem, wslDistribution: WSLDistribution? = null) : Path { - return defaultInstallDir(defaultInstallDir(wslDistribution), newVersion) + fun defaultInstallDir(newVersion: JdkItem, eel: EelApi?, wslDistribution: WSLDistribution? = null): Path { + val installDir = + if (eel != null) defaultInstallDir(eel) + else defaultInstallDir(wslDistribution) + return defaultInstallDir(installDir, newVersion) } } @Internal -interface WSLDistributionForJdkInstaller { - fun getWslPath(path: Path): String - fun executeOnWsl(command: List, dir: String, timeout: Int): ProcessOutput +sealed interface OsAbstractionForJdkInstaller { + fun getPath(path: Path): String + fun execute(command: List, dir: String, timeout: Int): ProcessOutput + + interface Eel : OsAbstractionForJdkInstaller { + val eel: EelApi + } + + interface Wsl : OsAbstractionForJdkInstaller { + val d: WSLDistribution + } } @Internal @@ -163,12 +232,11 @@ abstract class JdkInstallerBase { @Suppress("PropertyName", "SSBasedInspection") protected val LOG: Logger = Logger.getInstance(javaClass) - abstract fun defaultInstallDir() : Path - open fun defaultInstallDir(wslDistribution: WSLDistributionForJdkInstaller?) : Path = defaultInstallDir() + abstract fun defaultInstallDir(osAbstractionForJdkInstaller: OsAbstractionForJdkInstaller?): Path - fun defaultInstallDir(newVersion: JdkItem) : Path = defaultInstallDir(defaultInstallDir(), newVersion) + fun defaultInstallDir(newVersion: JdkItem): Path = defaultInstallDir(defaultInstallDir(null), newVersion) - protected fun defaultInstallDir(installDir: Path, newVersion: JdkItem) : Path { + protected fun defaultInstallDir(installDir: Path, newVersion: JdkItem): Path { val targetDir = installDir.resolve(newVersion.installFolderName) var count = 1 var uniqueDir = targetDir @@ -240,15 +308,23 @@ abstract class JdkInstallerBase { error("URL must use https:// protocol, but was: $url") } - val wslDistribution = wslDistributionFromPath(targetDir) - if (wslDistribution != null && item.os != "linux") { - JdkDownloaderLogger.logFailed(JdkDownloaderLogger.DownloadFailure.WSLIssue) - logFailed = true - error("Cannot install non-linux JDK into WSL environment to $targetDir from $item") + val eel = eelFromPath(targetDir)?.eel + val wslDistribution: OsAbstractionForJdkInstaller.Wsl? + if (eel != null) { + wslDistribution = null + } + else { + wslDistribution = wslDistributionFromPath(targetDir) + if (wslDistribution != null && item.os != "linux") { + JdkDownloaderLogger.logFailed(JdkDownloaderLogger.DownloadFailure.WSLIssue) + logFailed = true + error("Cannot install non-linux JDK into WSL environment to $targetDir from $item") + } } indicator?.text2 = ProjectBundle.message("progress.text2.downloading.jdk") - val downloadFile = Paths.get(PathManager.getTempPath(), FileUtil.sanitizeFileName("jdk-${System.nanoTime()}-${item.archiveFileName}")) + // TODO Sanitize `archiveFileName` in a way that it doesn't replace `.` with `_`. `FileUtil.sanitizeFileName` can't be applied here. + val downloadFile = Paths.get(PathManager.getTempPath(), "jdk-${System.nanoTime()}-${item.archiveFileName}") try { try { HttpRequests.request(item.url) @@ -290,7 +366,11 @@ abstract class JdkInstallerBase { indicator?.text2 = ProjectBundle.message("progress.text2.unpacking.jdk") try { - if (wslDistribution != null) { + if (eel != null) { + val targetDirEel = eel.mapper.getOriginalPath(targetDir) ?: TODO("Failed to map $targetDir to $eel") + unpackJdkOnEel(eel, downloadFile, targetDirEel, item.packageRootPrefix) + } + else if (wslDistribution != null) { unpackJdkOnWsl(wslDistribution, item.packageType, downloadFile, targetDir, item.packageRootPrefix) } else { @@ -428,7 +508,7 @@ abstract class JdkInstallerBase { } } - private fun findAlreadyInstalledJdk(feedItem: JdkItem, distribution: WSLDistributionForJdkInstaller?) : JdkInstallRequest? { + private fun findAlreadyInstalledJdk(feedItem: JdkItem, distribution: OsAbstractionForJdkInstaller?) : JdkInstallRequest? { try { val localRoots = run { val defaultInstallDir = defaultInstallDir(distribution) @@ -461,7 +541,8 @@ abstract class JdkInstallerBase { } protected open fun findHistoryRoots(feedItem: JdkItem): List = listOf() - protected open fun wslDistributionFromPath(targetDir: Path) : WSLDistributionForJdkInstaller? = null + protected open fun wslDistributionFromPath(targetDir: Path) : OsAbstractionForJdkInstaller.Wsl? = null + protected open fun eelFromPath(targetDir: Path): OsAbstractionForJdkInstaller.Eel? = null } private data class PendingJdkRequest( diff --git a/platform/lang-impl/src/com/intellij/openapi/projectRoots/impl/jdkDownloader/JdkInstallerEel.kt b/platform/lang-impl/src/com/intellij/openapi/projectRoots/impl/jdkDownloader/JdkInstallerEel.kt new file mode 100644 index 000000000000..7027f0fff9a2 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/openapi/projectRoots/impl/jdkDownloader/JdkInstallerEel.kt @@ -0,0 +1,94 @@ +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.intellij.openapi.projectRoots.impl.jdkDownloader + +import com.intellij.openapi.diagnostic.thisLogger +import com.intellij.openapi.progress.runBlockingMaybeCancellable +import com.intellij.openapi.util.io.NioFiles +import com.intellij.platform.eel.EelApi +import com.intellij.platform.eel.fs.EelFileSystemApi +import com.intellij.platform.eel.getOrThrow +import com.intellij.platform.eel.path.EelPath +import com.intellij.platform.eel.toNioPath +import org.jetbrains.annotations.ApiStatus.Internal +import java.nio.file.Files +import java.nio.file.Path +import java.nio.file.StandardCopyOption +import kotlin.io.path.name + +@Internal +object JdkInstallerEel { + fun unpackJdkOnEel( + eel: EelApi, + downloadFile: Path, + targetDirEel: EelPath.Absolute, + packageRootPrefixRaw: String, + ): Unit = runBlockingMaybeCancellable { + var downloadFileEelCopy: EelPath.Absolute? = eel.mapper.getOriginalPath(downloadFile) + + val tempDirectory: EelPath.Absolute? = + if (downloadFileEelCopy == null) { + // TODO Eel downloading API + val archiveName = EelPath.Relative.parse(downloadFile.name) + + downloadFileEelCopy = eel.fs + .createTemporaryDirectory(EelFileSystemApi.CreateTemporaryDirectoryOptions.Builder().prefix("download-jdk-").build()).getOrThrow() + .resolve(archiveName) + + Files.copy(downloadFile, downloadFileEelCopy.toNioPath(eel), StandardCopyOption.REPLACE_EXISTING) + + downloadFileEelCopy.parent + } + else null + try { + val unpackDir = targetDirEel.parent!! + .resolve(EelPath.Relative.parse(".${targetDirEel.fileName}-downloading-${System.currentTimeMillis()}")) + try { + eel.archive.extract(downloadFileEelCopy, unpackDir) + moveUnpackedJdkPrefixOnEel( + eel = eel, + unpackDir = unpackDir, + targetDir = targetDirEel, + packageRootPrefixRaw = packageRootPrefixRaw, + ) + } + finally { + try { + NioFiles.deleteRecursively(unpackDir.toNioPath(eel)) + } + catch (_: FileSystemException) { + // Ignored. + } + } + } + finally { + tempDirectory?.let(eel.mapper::toNioPath)?.let { absolute -> + NioFiles.deleteRecursively(absolute) + } + } + } + + private suspend fun moveUnpackedJdkPrefixOnEel( + eel: EelApi, + unpackDir: EelPath.Absolute, + targetDir: EelPath.Absolute, + packageRootPrefixRaw: String, + ) { + val packageRootPrefix = packageRootPrefixRaw.removePrefix("./").trim('/') + val packageRootResolved = + if (packageRootPrefix.isBlank()) + unpackDir + else + unpackDir.resolve(EelPath.Relative.parse(packageRootPrefixRaw)).normalize() + + + if (!packageRootResolved.startsWith(unpackDir)) { + error("Failed to move JDK contents from $unpackDir to $packageRootResolved. Invalid metadata is detected") + } + + if (!Files.isDirectory(packageRootResolved.toNioPath(eel))) { + thisLogger().info("Could not unpack JDK in $packageRootResolved. File system entry is not a directory. ") + return + } + eel.fs.move(packageRootResolved, targetDir, EelFileSystemApi.ReplaceExistingDuringMove.REPLACE_EVERYTHING, true) + } +} \ No newline at end of file diff --git a/platform/lang-impl/src/com/intellij/openapi/projectRoots/impl/jdkDownloader/JdkInstallerWSL.kt b/platform/lang-impl/src/com/intellij/openapi/projectRoots/impl/jdkDownloader/JdkInstallerWSL.kt index 2c2cc89c92c3..6e8228482325 100644 --- a/platform/lang-impl/src/com/intellij/openapi/projectRoots/impl/jdkDownloader/JdkInstallerWSL.kt +++ b/platform/lang-impl/src/com/intellij/openapi/projectRoots/impl/jdkDownloader/JdkInstallerWSL.kt @@ -11,7 +11,7 @@ private val LOG = logger() @Internal object JdkInstallerWSL { - fun unpackJdkOnWsl(wslDistribution: WSLDistributionForJdkInstaller, + fun unpackJdkOnWsl(wslDistribution: OsAbstractionForJdkInstaller.Wsl, packageType: JdkPackageType, downloadFile: Path, targetDir: Path, @@ -31,19 +31,19 @@ object JdkInstallerWSL { } } - private fun unpackJdkArchiveOnWsl(wslDistribution: WSLDistributionForJdkInstaller, + private fun unpackJdkArchiveOnWsl(osAbstraction: OsAbstractionForJdkInstaller, packageType: JdkPackageType, downloadFile: Path, targetDir: Path) { - val downloadFileWslPath = wslDistribution.getWslPath(downloadFile) - val targetWslPath = wslDistribution.getWslPath(targetDir) + val downloadFileWslPath = osAbstraction.getPath(downloadFile) + val targetWslPath = osAbstraction.getPath(targetDir) FileUtil.createDirectory(targetDir.toFile()) val command = when (packageType) { JdkPackageType.ZIP -> listOf("unzip", downloadFileWslPath) JdkPackageType.TAR_GZ -> listOf("tar", "xzf", downloadFileWslPath) } - val processOutput = wslDistribution.executeOnWsl(command, targetWslPath, 300_000) + val processOutput = osAbstraction.execute(command, targetWslPath, 300_000) if (processOutput.exitCode != 0) { val message = "Failed to unpack $downloadFile to $targetDir" LOG.warn(message + ": " + processOutput.stderrLines.takeLast(10).joinToString("") { "\n $it" }) @@ -52,7 +52,7 @@ object JdkInstallerWSL { } private fun moveUnpackedJdkPrefixOnWsl( - wslDistribution: WSLDistributionForJdkInstaller, + osAbstraction: OsAbstractionForJdkInstaller, unpackDir: Path, targetDir: Path, packageRootPrefixRaw: String, @@ -68,13 +68,13 @@ object JdkInstallerWSL { error("Invalid package. Directory is expected under '$packageRootPrefixRaw' path on the JDK package") } - val wslTarget = wslDistribution.getWslPath(targetDir) - val wslUnpack = wslDistribution.getWslPath(unpackDir) - val wslSource = wslDistribution.getWslPath(packageRootDir) + val wslTarget = osAbstraction.getPath(targetDir) + val wslUnpack = osAbstraction.getPath(unpackDir) + val wslSource = osAbstraction.getPath(packageRootDir) FileUtil.delete(targetDir) val command = listOf("mv", wslSource, wslTarget) - val processOutput = wslDistribution.executeOnWsl(command, wslUnpack, 300_000) + val processOutput = osAbstraction.execute(command, wslUnpack, 300_000) if (processOutput.exitCode != 0) { val message = "Failed to strip package root prefix ${packageRootPrefix}" diff --git a/platform/lang-impl/src/com/intellij/openapi/projectRoots/impl/jdkDownloader/JdkList.kt b/platform/lang-impl/src/com/intellij/openapi/projectRoots/impl/jdkDownloader/JdkList.kt index febe34937ab2..816a80cf1610 100644 --- a/platform/lang-impl/src/com/intellij/openapi/projectRoots/impl/jdkDownloader/JdkList.kt +++ b/platform/lang-impl/src/com/intellij/openapi/projectRoots/impl/jdkDownloader/JdkList.kt @@ -12,11 +12,14 @@ import com.intellij.openapi.diagnostic.thisLogger import com.intellij.openapi.progress.ProcessCanceledException import com.intellij.openapi.progress.ProgressIndicator import com.intellij.openapi.progress.ProgressManager +import com.intellij.openapi.projectRoots.impl.jdkDownloader.JdkPackageType.entries import com.intellij.openapi.util.BuildNumber import com.intellij.openapi.util.NlsSafe import com.intellij.openapi.util.SystemInfo import com.intellij.openapi.util.registry.Registry import com.intellij.openapi.util.text.StringUtil +import com.intellij.platform.eel.EelApi +import com.intellij.platform.eel.EelPlatform import com.intellij.util.io.Decompressor import com.intellij.util.io.HttpRequests import com.intellij.util.io.write @@ -243,6 +246,12 @@ data class JdkPredicate( fun default(): JdkPredicate = createInstance(forWsl = false) fun forWSL(buildNumber: BuildNumber? = ApplicationInfoImpl.getShadowInstance().build): JdkPredicate = createInstance(forWsl = true, buildNumber) + fun forEel( + eel: EelApi, + buildNumber: BuildNumber? = ApplicationInfoImpl.getShadowInstance().build, + ): JdkPredicate = + createInstance(eel, buildNumber) + /** * Selects only JDKs that are for the same OS and CPU arch as the current Java process. */ @@ -263,6 +272,18 @@ data class JdkPredicate( return JdkPredicate(buildNumber, platforms.toSet()) } + private fun createInstance(eel: EelApi, buildNumber: BuildNumber?): JdkPredicate { + val platform = when (eel.platform) { + EelPlatform.Arm64Darwin -> setOf(JdkPlatform("macOS", "x86_64"), JdkPlatform("macOS", "aarch64")) + EelPlatform.X8664Darwin -> setOf(JdkPlatform("macOS", "x86_64")) + EelPlatform.Aarch64Linux -> setOf(JdkPlatform("linux", "aarch64")) + EelPlatform.X8664Linux -> setOf(JdkPlatform("linux", "x86_64")) + EelPlatform.X64Windows -> setOf(JdkPlatform("windows", "x86_64")) + // TODO Windows aarch64 + } + return JdkPredicate(buildNumber, platform) + } + val currentOS: String = when { SystemInfo.isWindows -> "windows" SystemInfo.isMac -> "macOS" diff --git a/platform/lang-impl/src/com/intellij/openapi/projectRoots/impl/jdkDownloader/RuntimeChooserCustom.kt b/platform/lang-impl/src/com/intellij/openapi/projectRoots/impl/jdkDownloader/RuntimeChooserCustom.kt index f38ee2c0aa59..2a90cc50b055 100644 --- a/platform/lang-impl/src/com/intellij/openapi/projectRoots/impl/jdkDownloader/RuntimeChooserCustom.kt +++ b/platform/lang-impl/src/com/intellij/openapi/projectRoots/impl/jdkDownloader/RuntimeChooserCustom.kt @@ -48,6 +48,7 @@ internal object RuntimeChooserCustom { private val jdkDownloaderExtension = object : JdkDownloaderDialogHostExtension { override fun allowWsl(): Boolean = false + override fun allowEel(): Boolean = false override fun shouldIncludeItem(sdkType: SdkTypeId, item: JdkItem): Boolean { return sdkType == this@RuntimeChooserCustom.sdkType && isSupportedSdkItem(item) diff --git a/platform/lang-impl/src/com/intellij/openapi/projectRoots/impl/jdkDownloader/RuntimeChooserDownloader.kt b/platform/lang-impl/src/com/intellij/openapi/projectRoots/impl/jdkDownloader/RuntimeChooserDownloader.kt index 5ca9bb3737dd..8d6b8d079f94 100644 --- a/platform/lang-impl/src/com/intellij/openapi/projectRoots/impl/jdkDownloader/RuntimeChooserDownloader.kt +++ b/platform/lang-impl/src/com/intellij/openapi/projectRoots/impl/jdkDownloader/RuntimeChooserDownloader.kt @@ -3,7 +3,6 @@ package com.intellij.openapi.projectRoots.impl.jdkDownloader import com.intellij.lang.LangBundle import com.intellij.openapi.components.Service -import com.intellij.openapi.components.service import com.intellij.openapi.diagnostic.ControlFlowException import com.intellij.openapi.diagnostic.thisLogger import com.intellij.openapi.progress.ProgressIndicator @@ -16,7 +15,7 @@ import java.nio.file.Paths internal class RuntimeChooserDownloader { fun downloadAndUse(indicator: ProgressIndicator, jdk: JdkItem, targetDir: Path): Path? { try { - val installer = service() + val installer = RuntimeChooserJbrInstaller val request = installer.prepareJdkInstallation(jdk, targetDir) installer.installJdk(request, indicator, null) return request.javaHome @@ -32,9 +31,9 @@ internal class RuntimeChooserDownloader { } } -@Service(Service.Level.APP) -internal class RuntimeChooserJbrInstaller : JdkInstallerBase() { - override fun defaultInstallDir(): Path { +internal object RuntimeChooserJbrInstaller : JdkInstallerBase() { + override fun defaultInstallDir(osAbstractionForJdkInstaller: OsAbstractionForJdkInstaller?): Path { + // TODO Use osAbstractionForJdkInstaller val explicitHome = System.getProperty("jbr.downloader.home") if (explicitHome != null) { return Paths.get(explicitHome) diff --git a/platform/lang-impl/src/com/intellij/openapi/projectRoots/impl/jdkDownloader/RuntimeChooserJre.kt b/platform/lang-impl/src/com/intellij/openapi/projectRoots/impl/jdkDownloader/RuntimeChooserJre.kt index bc069cc9763d..c5edf5468c10 100644 --- a/platform/lang-impl/src/com/intellij/openapi/projectRoots/impl/jdkDownloader/RuntimeChooserJre.kt +++ b/platform/lang-impl/src/com/intellij/openapi/projectRoots/impl/jdkDownloader/RuntimeChooserJre.kt @@ -35,6 +35,7 @@ internal object RuntimeChooserJreValidator { get() = 11 fun isSupportedSdkItem(item: JdkItem): Boolean { + // TODO Introduce EelApi here. //we do only support mac bundle layout if (SystemInfo.isMac && !item.packageToBinJavaPrefix.endsWith("Contents/Home")) { return false diff --git a/platform/lang-impl/src/com/intellij/openapi/projectRoots/impl/jdkDownloader/RuntimeChooserModel.kt b/platform/lang-impl/src/com/intellij/openapi/projectRoots/impl/jdkDownloader/RuntimeChooserModel.kt index e16452202f9f..76e46eb5b142 100644 --- a/platform/lang-impl/src/com/intellij/openapi/projectRoots/impl/jdkDownloader/RuntimeChooserModel.kt +++ b/platform/lang-impl/src/com/intellij/openapi/projectRoots/impl/jdkDownloader/RuntimeChooserModel.kt @@ -2,7 +2,6 @@ package com.intellij.openapi.projectRoots.impl.jdkDownloader import com.intellij.openapi.Disposable -import com.intellij.openapi.components.service import com.intellij.openapi.observable.properties.GraphProperty import com.intellij.openapi.observable.properties.PropertyGraph import com.intellij.openapi.util.io.FileUtil @@ -49,7 +48,7 @@ internal class RuntimeChooserModel { file = Paths.get(path + "-" + (it + 1)) } } - return service().defaultInstallDir(item) + return RuntimeChooserJbrInstaller.defaultInstallDir(item) } private fun updateMainCombobox(newSelection: RuntimeChooserItem? = null) { diff --git a/platform/lang-impl/src/com/intellij/openapi/roots/ui/configuration/SdkDetector.java b/platform/lang-impl/src/com/intellij/openapi/roots/ui/configuration/SdkDetector.java index 8ee75567570d..57b858485ed0 100644 --- a/platform/lang-impl/src/com/intellij/openapi/roots/ui/configuration/SdkDetector.java +++ b/platform/lang-impl/src/com/intellij/openapi/roots/ui/configuration/SdkDetector.java @@ -15,6 +15,7 @@ import com.intellij.openapi.projectRoots.SdkType; import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.registry.Registry; import com.intellij.util.concurrency.ThreadingAssertions; +import com.intellij.util.concurrency.annotations.RequiresBackgroundThread; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -91,7 +92,7 @@ public class SdkDetector { */ BackgroundTaskUtil.executeOnPooledThread(lifetime, () -> { var progressIndicator = ProgressManager.getInstance().getProgressIndicator(); - detectAllSdks(progressIndicator, myMulticaster); + detectAllSdks(project, progressIndicator, myMulticaster); }); } @@ -140,16 +141,33 @@ public class SdkDetector { /** * Run Sdk detection assuming called in a background thread + * + * @deprecated Please use {@link SdkDetector#detectSdks(Project, SdkType, ProgressIndicator, DetectedSdkListener)} */ + @Deprecated public void detectSdks( @NotNull SdkType type, @NotNull ProgressIndicator indicator, @NotNull DetectedSdkListener callback + ) { + detectSdks(null, type, indicator, callback); + } + + /** + * Run Sdk detection on the machine where {@code project} is located + * This function assumes that it is called in background thread + */ + @RequiresBackgroundThread + public void detectSdks( + @Nullable Project project, + @NotNull SdkType type, + @NotNull ProgressIndicator indicator, + @NotNull DetectedSdkListener callback ) { try { callback.onSearchStarted(); if (isDetectorEnabled()) { - detect(type, indicator, callback); + detect(project, type, indicator, callback); } } finally { @@ -157,7 +175,7 @@ public class SdkDetector { } } - private static void detectAllSdks(@NotNull ProgressIndicator indicator, @NotNull DetectedSdkListener callback) { + private static void detectAllSdks(@NotNull Project project, @NotNull ProgressIndicator indicator, @NotNull DetectedSdkListener callback) { try { callback.onSearchStarted(); indicator.setIndeterminate(false); @@ -166,7 +184,7 @@ public class SdkDetector { indicator.setFraction((float)i / types.size()); indicator.checkCanceled(); if (isDetectorEnabled()) { - detect(types.get(i), indicator, callback); + detect(project, types.get(i), indicator, callback); } } } @@ -175,11 +193,12 @@ public class SdkDetector { } } - private static void detect(@NotNull SdkType type, + private static void detect(@Nullable Project project, + @NotNull SdkType type, @NotNull ProgressIndicator indicator, @NotNull DetectedSdkListener callback) { try { - Collection suggestedPaths = type.suggestHomePaths(); + Collection suggestedPaths = type.suggestHomePaths(project); for (String path : suggestedPaths) { indicator.checkCanceled(); diff --git a/platform/lang-impl/src/com/intellij/openapi/roots/ui/configuration/projectRoot/ProjectSdksModel.java b/platform/lang-impl/src/com/intellij/openapi/roots/ui/configuration/projectRoot/ProjectSdksModel.java index b3da869be64b..fb4186af717a 100644 --- a/platform/lang-impl/src/com/intellij/openapi/roots/ui/configuration/projectRoot/ProjectSdksModel.java +++ b/platform/lang-impl/src/com/intellij/openapi/roots/ui/configuration/projectRoot/ProjectSdksModel.java @@ -2,8 +2,10 @@ package com.intellij.openapi.roots.ui.configuration.projectRoot; import com.intellij.execution.wsl.WslPath; +import com.intellij.ide.DataManager; import com.intellij.openapi.Disposable; import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.CommonDataKeys; import com.intellij.openapi.actionSystem.DefaultActionGroup; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; @@ -21,21 +23,28 @@ import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.NlsContexts.ListItem; import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.util.registry.Registry; +import com.intellij.platform.eel.provider.EelApiKey; +import com.intellij.platform.eel.provider.LocalEelKey; import com.intellij.util.ArrayUtilRt; import com.intellij.util.Consumer; import com.intellij.util.EventDispatcher; import com.intellij.util.concurrency.annotations.RequiresEdt; import one.util.streamex.StreamEx; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; +import java.nio.file.InvalidPathException; +import java.nio.file.Path; import java.util.*; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Predicate; import static com.intellij.openapi.util.NlsActions.ActionText; +import static com.intellij.platform.eel.provider.EelProviderUtil.getEelApiKey; /** * @author anna @@ -80,10 +89,20 @@ public class ProjectSdksModel implements SdkModel { } public void syncSdks() { + syncSdks(LocalEelKey.INSTANCE); + } + + /** + * @param eel can be null only if the corresponding feature flag is disabled. + */ + @ApiStatus.Internal + public void syncSdks(@Nullable EelApiKey eelApiKey) { final Sdk[] projectSdks = ProjectJdkTable.getInstance().getAllJdks(); for (Sdk sdk : projectSdks) { if (myProjectSdks.containsKey(sdk) || myProjectSdks.containsValue(sdk)) continue; + if (eelApiKey != null && !sdkMatchesEel(eelApiKey, sdk)) continue; + Sdk editableCopy; try { editableCopy = sdk.clone(); @@ -99,12 +118,47 @@ public class ProjectSdksModel implements SdkModel { } } + @ApiStatus.Internal + public static boolean sdkMatchesEel(@NotNull EelApiKey eelApiKey, Sdk sdk) { + String sdkHomePath = sdk.getHomePath(); + return sdkMatchesEel(eelApiKey, sdkHomePath); + } + + @ApiStatus.Internal + public static boolean sdkMatchesEel(@NotNull EelApiKey eelApiKey, String sdkHomePath) { + if (sdkHomePath != null) { + try { + Path path = Path.of(sdkHomePath); + if (getEelApiKey(path).equals(eelApiKey)) { + return true; + } + } + catch (InvalidPathException ignored) { + // Ignored. + } + } + return false; + } + public void reset(@Nullable Project project) { + EelApiKey eelApiKey; + if (!Registry.is("java.home.finder.use.eel")) { + eelApiKey = null; + } + else if (project != null) { + eelApiKey = getEelApiKey(project); + } + else { + eelApiKey = LocalEelKey.INSTANCE; + } + myProjectSdks.clear(); ProjectJdkTable jdkTable = ProjectJdkTable.getInstance(); jdkTable.preconfigure(); final Sdk[] projectSdks = jdkTable.getAllJdks(); for (Sdk sdk : projectSdks) { + if (eelApiKey != null && !sdkMatchesEel(eelApiKey, sdk)) continue; + try { Sdk editable = sdk.clone(); myProjectSdks.put(sdk, editable); @@ -370,7 +424,8 @@ public class ProjectSdksModel implements SdkModel { public void actionPerformed(@Nullable Sdk selectedSdk, @NotNull JComponent parent, @NotNull java.util.function.Consumer callback) { - doDownload(downloadExtension, parent, selectedSdk, type, callback); + Project project = CommonDataKeys.PROJECT.getData(DataManager.getInstance().getDataContext(parent)); + doDownload(project, downloadExtension, parent, selectedSdk, type, callback); } }; @@ -421,7 +476,8 @@ public class ProjectSdksModel implements SdkModel { doAdd(parent, null, type, callback); } - public void doDownload(@NotNull SdkDownload downloadExtension, + private void doDownload(@Nullable Project project, + @NotNull SdkDownload downloadExtension, @NotNull JComponent parent, @Nullable Sdk selectedSdk, @NotNull SdkType type, @@ -429,7 +485,7 @@ public class ProjectSdksModel implements SdkModel { LOG.assertTrue(downloadExtension.supportsDownload(type)); myModified = true; - downloadExtension.showDownloadUI(type, this, parent, null, selectedSdk, null, sdk -> setupInstallableSdk(type, sdk, callback)); + downloadExtension.showDownloadUI(type, this, parent, project, selectedSdk, null, sdk -> setupInstallableSdk(type, sdk, callback)); } public void doAdd(@NotNull JComponent parent, final @Nullable Sdk selectedSdk, final @NotNull SdkType type, final @NotNull Consumer callback) { diff --git a/platform/lang-impl/testSources/com/intellij/openapi/projectRoots/impl/jdkDownloader/JdkDownloaderTest.kt b/platform/lang-impl/testSources/com/intellij/openapi/projectRoots/impl/jdkDownloader/JdkDownloaderTest.kt index 066375bf44dd..3a0828eb78c6 100644 --- a/platform/lang-impl/testSources/com/intellij/openapi/projectRoots/impl/jdkDownloader/JdkDownloaderTest.kt +++ b/platform/lang-impl/testSources/com/intellij/openapi/projectRoots/impl/jdkDownloader/JdkDownloaderTest.kt @@ -5,6 +5,7 @@ package com.intellij.openapi.projectRoots.impl.jdkDownloader import com.intellij.execution.configurations.GeneralCommandLine import com.intellij.execution.process.CapturingProcessHandler import com.intellij.execution.process.ProcessOutput +import com.intellij.execution.wsl.WSLDistribution import com.intellij.openapi.components.service import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.extensions.ExtensionPointName @@ -20,7 +21,6 @@ import org.junit.Assert.* import org.junit.Before import org.junit.Rule import org.junit.Test -import java.io.File import java.nio.file.Path import kotlin.concurrent.thread @@ -79,19 +79,21 @@ class JdkDownloaderTest : BareTestFixtureTestCase() { size = 604, sha256 = "1cf15536c1525f413190fd53243f343511a17e6ce7439ccee4dc86f0d34f9e81") - private val mockWSL = object: WSLDistributionForJdkInstaller { - override fun getWslPath(path: Path): String = path.toString() + private val mockWSL = object: OsAbstractionForJdkInstaller.Wsl { + override val d: WSLDistribution + get() = TODO("Not yet implemented") - override fun executeOnWsl(command: List, dir: String, timeout: Int): ProcessOutput { - val cmd = GeneralCommandLine(command) - cmd.workDirectory = File(dir) - return CapturingProcessHandler(cmd).runProcess(timeout) + override fun getPath(path: Path): String = path.toString() + + override fun execute(command: List, dir: String, timeout: Int): ProcessOutput { + val processHandler = CapturingProcessHandler(GeneralCommandLine(command).withWorkingDirectory(Path.of(dir))); + return processHandler.runProcess(timeout) } } private val mockWSLInstaller = object: JdkInstallerBase() { override fun wslDistributionFromPath(targetDir: Path) = mockWSL - override fun defaultInstallDir(): Path = error("Must not call") + override fun defaultInstallDir(osAbstractionForJdkInstaller: OsAbstractionForJdkInstaller?): Path = error("Must not call") } @Test fun `test reuse pending JDKs`() { diff --git a/platform/util/resources/misc/registry.properties b/platform/util/resources/misc/registry.properties index c745a7122165..468d331d86bd 100644 --- a/platform/util/resources/misc/registry.properties +++ b/platform/util/resources/misc/registry.properties @@ -2555,5 +2555,8 @@ i18n.locale.description=changes locale of the IDE properties.file.encoding.legacy.support=false properties.file.encoding.legacy.support.description=Switch the encoding for properties files to legacy ISO 8859-1 (used up to Java 8). Starting from Java 9, the default encoding is UTF-8. +java.home.finder.use.eel=true +java.home.finder.use.eel.description=Use new API for OS abstraction in Project SDKs + # please leave this note as last line # TODO please use EP com.intellij.registryKey for plugin/product specific keys diff --git a/plugins/maven/intellij.maven.iml b/plugins/maven/intellij.maven.iml index ebe5fda33536..585620f4c2dd 100644 --- a/plugins/maven/intellij.maven.iml +++ b/plugins/maven/intellij.maven.iml @@ -109,6 +109,8 @@ + + diff --git a/plugins/maven/src/main/java/org/jetbrains/idea/maven/utils/MavenWslUtil.kt b/plugins/maven/src/main/java/org/jetbrains/idea/maven/utils/MavenWslUtil.kt index 6af145a565e2..8a2ec2fab55c 100644 --- a/plugins/maven/src/main/java/org/jetbrains/idea/maven/utils/MavenWslUtil.kt +++ b/plugins/maven/src/main/java/org/jetbrains/idea/maven/utils/MavenWslUtil.kt @@ -10,6 +10,7 @@ import com.intellij.notification.NotificationListener import com.intellij.notification.NotificationType import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.WriteAction +import com.intellij.openapi.application.writeAction import com.intellij.openapi.components.Service import com.intellij.openapi.components.service import com.intellij.openapi.externalSystem.service.execution.InvalidSdkException @@ -17,6 +18,7 @@ import com.intellij.openapi.options.ShowSettingsUtil import com.intellij.openapi.progress.ProgressIndicator import com.intellij.openapi.progress.ProgressManager import com.intellij.openapi.progress.Task +import com.intellij.openapi.progress.coroutineToIndicator import com.intellij.openapi.project.Project import com.intellij.openapi.projectRoots.* import com.intellij.openapi.projectRoots.impl.SdkConfigurationUtil @@ -27,9 +29,15 @@ import com.intellij.openapi.roots.ProjectRootManager import com.intellij.openapi.roots.ex.ProjectRootManagerEx import com.intellij.openapi.roots.ui.configuration.ProjectStructureConfigurable import com.intellij.openapi.util.io.FileUtil +import com.intellij.openapi.util.registry.Registry import com.intellij.openapi.util.text.StringUtil +import com.intellij.platform.eel.provider.getEelApi +import com.intellij.platform.ide.progress.withBackgroundProgress +import com.intellij.platform.util.progress.withProgressText import com.intellij.ui.navigation.Place import com.intellij.util.text.VersionComparatorUtil +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch import org.jetbrains.idea.maven.config.MavenConfig import org.jetbrains.idea.maven.config.MavenConfigSettings import org.jetbrains.idea.maven.execution.MavenRunnerSettings @@ -358,6 +366,10 @@ object MavenWslUtil : MavenUtil() { sdk: Sdk, notification: Notification, listener: NotificationListener) { + if (Registry.`is`("java.home.finder.use.eel")) { + return findOrDownloadNewJdkOverEel(project, notification, listener) + } + val jdkTask = object : Task.Backgroundable(null, MavenProjectBundle.message("wsl.jdk.searching"), false) { override fun run(indicator: ProgressIndicator) { val sdkPath = service().suggestHomePaths().filter { @@ -389,7 +401,7 @@ object MavenWslUtil : MavenUtil() { } else { this.title = MavenProjectBundle.message("wsl.jdk.downloading") - val homeDir = installer.defaultInstallDir(model[0], projectWslDistr) + val homeDir = installer.defaultInstallDir(model[0], null, projectWslDistr) val request = installer.prepareJdkInstallation(model[0], homeDir) installer.installJdk(request, indicator, project) notification.hideBalloon() @@ -398,6 +410,54 @@ object MavenWslUtil : MavenUtil() { } ProgressManager.getInstance().run(jdkTask) } + + private fun findOrDownloadNewJdkOverEel( + project: Project, + notification: Notification, + listener: NotificationListener, + ) { + MavenCoroutineScopeProvider.getCoroutineScope(project).launch(Dispatchers.IO) { + withBackgroundProgress(project, MavenProjectBundle.message("wsl.jdk.searching"), cancellable = false) { + val eel = project.getEelApi() + val sdkPath = service().suggestHomePaths(project).firstOrNull() + if (sdkPath != null) { + writeAction { + val jdkName = SdkConfigurationUtil.createUniqueSdkName(JavaSdk.getInstance(), sdkPath, + ProjectJdkTable.getInstance().allJdks.toList()) + val newJdk = JavaSdk.getInstance().createJdk(jdkName, sdkPath) + ProjectJdkTable.getInstance().addJdk(newJdk) + ProjectRootManagerEx.getInstance(project).projectSdk = newJdk + notification.hideBalloon() + } + return@withBackgroundProgress + } + val installer = JdkInstaller.getInstance() + val jdkPredicate = JdkPredicate.forEel(eel) + val model = coroutineToIndicator { + JdkListDownloader.getInstance().downloadModelForJdkInstaller(ProgressManager.getGlobalProgressIndicator(), jdkPredicate) + } + if (model.isEmpty()) { + Notification( + MAVEN_NOTIFICATION_GROUP, + MavenProjectBundle.message("maven.wsl.jdk.fix.failed"), + MavenProjectBundle.message("maven.wsl.jdk.fix.failed.descr"), + NotificationType.ERROR + ).setListener(listener).notify(project) + + } + else { + withProgressText(MavenProjectBundle.message("wsl.jdk.downloading")) { + val homeDir = installer.defaultInstallDir(model[0], eel, null) + val request = installer.prepareJdkInstallation(model[0], homeDir) + coroutineToIndicator { + installer.installJdk(request, ProgressManager.getGlobalProgressIndicator(), project) + } + notification.hideBalloon() + } + } + } + } + } }