[eel] IJPL-163027: Move Project Structure -> SDKs to EelApi

GitOrigin-RevId: 20fe00115b3693c50c1485a33635c58bdb83ad73
This commit is contained in:
Vladimir Lagunov
2024-10-30 17:09:45 +00:00
committed by intellij-monorepo-bot
parent 6b518ff68c
commit 0f8de35eb7
37 changed files with 770 additions and 104 deletions
+2
View File
@@ -34,5 +34,7 @@
<orderEntry type="module" module-name="intellij.xml.dom" />
<orderEntry type="module" module-name="intellij.platform.util.coroutines" />
<orderEntry type="module" module-name="intellij.platform.backend.observation" />
<orderEntry type="module" module-name="intellij.platform.eel" />
<orderEntry type="module" module-name="intellij.platform.eel.provider" />
</component>
</module>
@@ -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<Sdk> {
val eelApiKey = project.getEelApiKey()
return Predicate { sdk ->
ProjectSdksModel.sdkMatchesEel(eelApiKey, sdk)
}
}
internal fun filterSdkSuggestionByEel(project: Project): Predicate<SdkListItem.SuggestedItem> {
val eelApiKey = project.getEelApiKey()
return Predicate { item ->
ProjectSdksModel.sdkMatchesEel(eelApiKey, item.homePath)
}
}
@@ -76,6 +76,7 @@ public abstract class ModuleJdkConfigurable implements Disposable {
final Project project = getRootModel().getModule().getProject();
Predicate<SdkTypeId> predicate = SimpleJavaSdkType.notSimpleJavaSdkType();
// TODO Use EelApi here.
myCbModuleJdk = new JdkComboBox(project, myJdksModel, predicate::test,
WslSdkFilter.filterSdkByWsl(project), WslSdkFilter.filterSdkSuggestionByWsl(project),
null, jdk -> {
@@ -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
@@ -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<MyNode> 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);
}
@@ -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<List<String>>()
runBackgroundableTask(ProjectBundle.message("progress.title.detecting.sdks"), project, false) { indicator ->
val jdkVersions = mutableListOf<String>()
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)
}
+2
View File
@@ -80,6 +80,8 @@
<orderEntry type="module" module-name="intellij.platform.markdown.utils" />
<orderEntry type="library" name="jetbrains.markdown" level="project" />
<orderEntry type="module" module-name="intellij.platform.threadDumpParser" />
<orderEntry type="module" module-name="intellij.platform.eel" />
<orderEntry type="module" module-name="intellij.platform.eel.provider" />
</component>
<component name="copyright">
<Base>
@@ -42,7 +42,7 @@ private class ExistingJdkConfigurationActivity : ProjectActivity {
val rootManager = project.serviceAsync<ProjectRootManager>()
val addedJdks = registeredJdks.toMutableList()
val priorityPaths = JavaHomeFinder.getFinder().findInJavaHome()
val priorityPaths = JavaHomeFinder.getFinder(project).findInJavaHome()
writeAction {
// Register collected JDKs
@@ -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<String> suggestHomePaths() {
return JavaHomeFinder.suggestHomePaths();
return suggestHomePaths(null);
}
@Override
public @NotNull Collection<String> suggestHomePaths(@Nullable Project project) {
return JavaHomeFinder.suggestHomePaths(EelProviderUtil.getEelApiBlocking(project), false);
}
@Override
+4 -1
View File
@@ -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
@@ -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<String> 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);
}
@@ -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
* <a href="https://www.jetbrains.com/help/idea/sdk.html">SDK</a>. 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<String> suggestHomePaths() {
String home = suggestHomePath();
return ContainerUtil.createMaybeSingletonList(home);
}
/**
* Returns a list of all valid SDKs found on the host where {@code project} is located.
* <p/>
* E.g. for Python SDK on Unix the method may return {@code ["/usr/bin/python2", "/usr/bin/python3"]}.
* <p/>
* 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<String> 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}.
@@ -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<String> suggestHomePaths() {
return suggestHomePaths(null);
}
@Override
public @NotNull Collection<String> 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
+1 -1
View File
@@ -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
- <init>():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
@@ -107,5 +107,7 @@
<orderEntry type="module" module-name="intellij.platform.ide.internal" />
<orderEntry type="module" module-name="intellij.platform.concurrency" />
<orderEntry type="module" module-name="intellij.platform.ide.ui" />
<orderEntry type="module" module-name="intellij.platform.eel" />
<orderEntry type="module" module-name="intellij.platform.eel.provider" />
</component>
</module>
@@ -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<String> suggestHomePaths() {
return suggestHomePaths(false);
}
@@ -65,7 +75,22 @@ public abstract class JavaHomeFinder {
* or that need the embedded JetBrains Runtime.
*/
public static @NotNull List<String> 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<String> suggestHomePaths(@NotNull EelApi eel, boolean forceEmbeddedJava) {
JavaHomeFinderBasic javaFinder = getFinder(eel, forceEmbeddedJava);
if (javaFinder == null) return Collections.emptyList();
ArrayList<String> 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) {
@@ -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<Path> paths = new HashSet<>();
if (myCheckDefaultInstallDir) {
paths.add(JdkInstaller.getInstance().defaultInstallDir());
paths.add(JdkInstaller.getInstance().defaultInstallDir((OsAbstractionForJdkInstaller)null));
}
if (myCheckUsedInstallDirs) {
@@ -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<ScopeService>().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<Path> = 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<ScopeService>().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)
}
@@ -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<String>) -> CharSequence
@JvmOverloads
constructor(
registeredJdks: Boolean,
wslJdks: Boolean,
systemInfoProvider: JavaHomeFinder.SystemInfoProvider,
processRunner: (cmd: List<String>) -> 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<Path> = TreeSet()
for (rp in registeredPaths) {
@@ -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);
}
}
@@ -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<ServiceScope>().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<JdkItem> by lazy {
val lazyDownloadModel: SuspendingLazy<List<JdkItem>> = 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<JavaLocalSdkFix>()
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
@@ -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<JdkItem>, 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<WSLDistribution>,
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<String>().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<String> {
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)
}
@@ -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<JdkDownloaderDialogHostExtension> = 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<EelApi, JdkDownloaderModel>? =
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
@@ -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<Path> = service<JdkInstallerStore>().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<String>, dir: String, timeout: Int): ProcessOutput {
override fun execute(command: List<String>, 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<String>, 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<String>, dir: String, timeout: Int): ProcessOutput
sealed interface OsAbstractionForJdkInstaller {
fun getPath(path: Path): String
fun execute(command: List<String>, 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<Path> = 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(
@@ -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)
}
}
@@ -11,7 +11,7 @@ private val LOG = logger<JdkInstallerWSL>()
@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}"
@@ -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"
@@ -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)
@@ -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<RuntimeChooserJbrInstaller>()
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)
@@ -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
@@ -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<RuntimeChooserJbrInstaller>().defaultInstallDir(item)
return RuntimeChooserJbrInstaller.defaultInstallDir(item)
}
private fun updateMainCombobox(newSelection: RuntimeChooserItem? = null) {
@@ -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<String> suggestedPaths = type.suggestHomePaths();
Collection<String> suggestedPaths = type.suggestHomePaths(project);
for (String path : suggestedPaths) {
indicator.checkCanceled();
@@ -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<? super Sdk> 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<? super Sdk> callback) {
@@ -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<String>, 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<String>, 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`() {
@@ -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
+2
View File
@@ -109,6 +109,8 @@
<orderEntry type="module" module-name="intellij.platform.diagnostic.telemetry.impl" />
<orderEntry type="module" module-name="intellij.maven.server.telemetry" />
<orderEntry type="library" name="ASM" level="project" />
<orderEntry type="module" module-name="intellij.platform.eel" />
<orderEntry type="module" module-name="intellij.platform.eel.provider" />
</component>
<component name="copyright">
<Base>
@@ -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<JdkFinder>().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<JdkFinder>().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()
}
}
}
}
}
}