mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
wsl: properly cache case-insensitive msId, add test
Distributions produced by `wsl.exe -l` should be preferred, thus msIds of different cases should be evicted. GitOrigin-RevId: 705162e41e54c3c1911f394b2577b4e144c4ca14
This commit is contained in:
committed by
intellij-monorepo-bot
parent
c42632cc78
commit
cb7908f3a2
@@ -17,6 +17,7 @@ import com.intellij.openapi.util.NlsSafe;
|
||||
import com.intellij.openapi.util.NullableLazyValue;
|
||||
import com.intellij.openapi.util.io.FileUtil;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.openapi.util.text.Strings;
|
||||
import com.intellij.openapi.vfs.VfsUtil;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.openapi.vfs.impl.local.LocalFileSystemBase;
|
||||
@@ -488,12 +489,12 @@ public class WSLDistribution {
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return this == o || o != null && getClass() == o.getClass() && myDescriptor.equals(((WSLDistribution)o).myDescriptor);
|
||||
return this == o || o != null && getClass() == o.getClass() && getMsId().equals(((WSLDistribution)o).getMsId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return myDescriptor.hashCode();
|
||||
return Strings.stringHashCodeInsensitive(getMsId());
|
||||
}
|
||||
|
||||
/** @deprecated use {@link WSLDistribution#getUNCRootPath()} instead */
|
||||
|
||||
@@ -1,39 +1,27 @@
|
||||
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.execution.wsl;
|
||||
|
||||
import com.intellij.execution.ExecutionException;
|
||||
import com.intellij.execution.configurations.GeneralCommandLine;
|
||||
import com.intellij.execution.process.ProcessOutput;
|
||||
import com.intellij.execution.util.ExecUtil;
|
||||
import com.intellij.ide.SaveAndSyncHandler;
|
||||
import com.intellij.openapi.Disposable;
|
||||
import com.intellij.openapi.application.Application;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.components.Service;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.util.NlsSafe;
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import com.intellij.openapi.util.io.FileUtil;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.util.TimeoutUtil;
|
||||
import com.intellij.util.concurrency.AppExecutorUtil;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.intellij.util.text.CaseInsensitiveStringHashingStrategy;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
@Service(Service.Level.APP)
|
||||
public final class WslDistributionManager implements Disposable {
|
||||
public abstract class WslDistributionManager implements Disposable {
|
||||
|
||||
private static final Logger LOG = Logger.getInstance(WslDistributionManager.class);
|
||||
// Distributions created by tools, e.g. Docker. Not suitable for running users apps.
|
||||
private static final Set<String> INTERNAL_DISTRIBUTIONS = Set.of("docker-desktop-data");
|
||||
static final Logger LOG = Logger.getInstance(WslDistributionManager.class);
|
||||
private static final Object LOCK = new Object();
|
||||
|
||||
public static @NotNull WslDistributionManager getInstance() {
|
||||
@@ -41,7 +29,8 @@ public final class WslDistributionManager implements Disposable {
|
||||
}
|
||||
|
||||
private volatile CachedDistributions myInstalledDistributions;
|
||||
private final Map<String, WSLDistribution> myMsIdToDistributionCache = ContainerUtil.createConcurrentWeakMap();
|
||||
private final Map<String, WSLDistribution> myMsIdToDistributionCache = ContainerUtil.createConcurrentWeakMap(
|
||||
CaseInsensitiveStringHashingStrategy.INSTANCE);
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
@@ -80,24 +69,27 @@ public final class WslDistributionManager implements Disposable {
|
||||
|
||||
/**
|
||||
* @return {@link WSLDistribution} instance by WSL distribution name. Please note that
|
||||
* the returned distribution is not guaranteed to be installed actually (for that check if the distribution is contained in
|
||||
* the returned distribution is not guaranteed to be installed (for that, check if the distribution is contained in
|
||||
* {@link #getInstalledDistributions}).
|
||||
* @param msId WSL distribution name, same as produced by `wsl.exe -l`
|
||||
*/
|
||||
public @NotNull WSLDistribution getOrCreateDistributionByMsId(@NonNls @NotNull String msId) {
|
||||
return getOrCreateDistributionByMsId(msId, false);
|
||||
}
|
||||
|
||||
private @NotNull WSLDistribution getOrCreateDistributionByMsId(@NonNls @NotNull String msId, boolean overrideCaseInsensitively) {
|
||||
if (msId.isEmpty()) {
|
||||
throw new IllegalStateException("WSL msId is empty");
|
||||
}
|
||||
// reuse previously created WSLDistribution instances to avoid re-calculating Host IP / WSL IP
|
||||
WSLDistribution d = myMsIdToDistributionCache.get(msId);
|
||||
if (d != null) {
|
||||
return d;
|
||||
}
|
||||
synchronized (myMsIdToDistributionCache) {
|
||||
d = myMsIdToDistributionCache.get(msId);
|
||||
if (d == null) {
|
||||
d = new WSLDistribution(msId);
|
||||
myMsIdToDistributionCache.put(msId, d);
|
||||
if (d == null || (overrideCaseInsensitively && !d.getMsId().equals(msId))) {
|
||||
synchronized (myMsIdToDistributionCache) {
|
||||
d = myMsIdToDistributionCache.get(msId);
|
||||
if (d == null || (overrideCaseInsensitively && !d.getMsId().equals(msId))) {
|
||||
d = new WSLDistribution(msId);
|
||||
myMsIdToDistributionCache.put(msId, d);
|
||||
}
|
||||
}
|
||||
}
|
||||
return d;
|
||||
@@ -116,7 +108,7 @@ public final class WslDistributionManager implements Disposable {
|
||||
String distName = path.substring(0, index);
|
||||
String wslPath = FileUtil.toSystemIndependentName(path.substring(index));
|
||||
|
||||
return Pair.create(wslPath, getOrCreateDistributionByMsId(distName));
|
||||
return Pair.create(wslPath, getOrCreateDistributionByMsId(distName, false));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -133,65 +125,12 @@ public final class WslDistributionManager implements Disposable {
|
||||
}
|
||||
|
||||
private @NotNull List<WSLDistribution> loadInstalledDistributions() {
|
||||
checkEdtAndReadAction();
|
||||
if (!new WSLCommandLineOptions().isLaunchWithWslExe()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
try {
|
||||
long startNano = System.nanoTime();
|
||||
Pair<GeneralCommandLine, List<WSLDistribution>> result = doFetchDistributionsFromWslCli();
|
||||
LOG.info("Fetched WSL distributions: " + ContainerUtil.map(result.second, WSLDistribution::getMsId) +
|
||||
" (\"" + result.first.getCommandLineString() + "\" done in " + TimeoutUtil.getDurationMillis(startNano) + " ms)");
|
||||
return result.second;
|
||||
}
|
||||
catch (IOException e) {
|
||||
LOG.info("Cannot parse WSL distributions", e);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
private @NotNull Pair<GeneralCommandLine, List<WSLDistribution>> doFetchDistributionsFromWslCli() throws IOException {
|
||||
GeneralCommandLine commandLine = createCommandLine();
|
||||
ProcessOutput output;
|
||||
try {
|
||||
output = ExecUtil.execAndGetOutput(commandLine, 10_000);
|
||||
}
|
||||
catch (ExecutionException e) {
|
||||
throw new IOException("Failed to run " + commandLine.getCommandLineString(), e);
|
||||
}
|
||||
if (output.isTimeout() || output.getExitCode() != 0 || !output.getStderr().isEmpty()) {
|
||||
String details = StringUtil.join(ContainerUtil.newArrayList(
|
||||
"timeout: " + output.isTimeout(),
|
||||
"exitCode: " + output.getExitCode(),
|
||||
"stdout: " + output.getStdout(),
|
||||
"stderr: " + output.getStderr()
|
||||
), ", ");
|
||||
throw new IOException("Failed to run " + commandLine.getCommandLineString() + ": " + details);
|
||||
}
|
||||
List<@NlsSafe String> msIds = ContainerUtil.filter(output.getStdoutLines(), distribution -> {
|
||||
return !INTERNAL_DISTRIBUTIONS.contains(distribution);
|
||||
return ContainerUtil.map(loadInstalledDistributionMsIds(), (msId) -> {
|
||||
return getOrCreateDistributionByMsId(msId, true);
|
||||
});
|
||||
return Pair.create(commandLine, ContainerUtil.map(msIds, this::getOrCreateDistributionByMsId));
|
||||
}
|
||||
|
||||
private static @NotNull GeneralCommandLine createCommandLine() throws IOException {
|
||||
Path wslExe = WSLDistribution.findWslExe();
|
||||
if (wslExe == null) {
|
||||
throw new IOException("No wsl.exe found in %PATH%");
|
||||
}
|
||||
return new GeneralCommandLine(wslExe.toString(), "--list", "--quiet").withCharset(StandardCharsets.UTF_16LE);
|
||||
}
|
||||
|
||||
private static void checkEdtAndReadAction() {
|
||||
Application application = ApplicationManager.getApplication();
|
||||
if (application == null || !application.isInternal() || application.isHeadlessEnvironment()) {
|
||||
return;
|
||||
}
|
||||
if (application.isReadAccessAllowed()) {
|
||||
LOG.error("Please call WslDistributionManager.getInstalledDistributions on a background thread and " +
|
||||
"not under read action as it runs a potentially long operation.");
|
||||
}
|
||||
}
|
||||
protected abstract @NotNull List<String> loadInstalledDistributionMsIds();
|
||||
|
||||
private static class CachedDistributions {
|
||||
private final @NotNull List<WSLDistribution> myInstalledDistributions;
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.execution.wsl;
|
||||
|
||||
import com.intellij.execution.ExecutionException;
|
||||
import com.intellij.execution.configurations.GeneralCommandLine;
|
||||
import com.intellij.execution.process.ProcessOutput;
|
||||
import com.intellij.execution.util.ExecUtil;
|
||||
import com.intellij.openapi.application.Application;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.util.NlsSafe;
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.util.TimeoutUtil;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public final class WslDistributionManagerImpl extends WslDistributionManager {
|
||||
|
||||
// Distributions created by tools, e.g. Docker. Not suitable for running users apps.
|
||||
private static final Set<String> INTERNAL_DISTRIBUTIONS = Set.of("docker-desktop-data");
|
||||
|
||||
@Override
|
||||
protected @NotNull List<String> loadInstalledDistributionMsIds() {
|
||||
checkEdtAndReadAction();
|
||||
if (!new WSLCommandLineOptions().isLaunchWithWslExe()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
try {
|
||||
long startNano = System.nanoTime();
|
||||
Pair<GeneralCommandLine, List<String>> result = doFetchDistributionsFromWslCli();
|
||||
LOG.info("Fetched WSL distributions: " + result.second +
|
||||
" (\"" + result.first.getCommandLineString() + "\" done in " + TimeoutUtil.getDurationMillis(startNano) + " ms)");
|
||||
return result.second;
|
||||
}
|
||||
catch (IOException e) {
|
||||
LOG.info("Cannot parse WSL distributions", e);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
private static @NotNull Pair<GeneralCommandLine, List<String>> doFetchDistributionsFromWslCli() throws IOException {
|
||||
GeneralCommandLine commandLine = createCommandLine();
|
||||
ProcessOutput output;
|
||||
try {
|
||||
output = ExecUtil.execAndGetOutput(commandLine, 10_000);
|
||||
}
|
||||
catch (ExecutionException e) {
|
||||
throw new IOException("Failed to run " + commandLine.getCommandLineString(), e);
|
||||
}
|
||||
if (output.isTimeout() || output.getExitCode() != 0 || !output.getStderr().isEmpty()) {
|
||||
String details = StringUtil.join(ContainerUtil.newArrayList(
|
||||
"timeout: " + output.isTimeout(),
|
||||
"exitCode: " + output.getExitCode(),
|
||||
"stdout: " + output.getStdout(),
|
||||
"stderr: " + output.getStderr()
|
||||
), ", ");
|
||||
throw new IOException("Failed to run " + commandLine.getCommandLineString() + ": " + details);
|
||||
}
|
||||
List<@NlsSafe String> msIds = ContainerUtil.filter(output.getStdoutLines(true), distribution -> {
|
||||
return !INTERNAL_DISTRIBUTIONS.contains(distribution);
|
||||
});
|
||||
return Pair.create(commandLine, msIds);
|
||||
}
|
||||
|
||||
private static @NotNull GeneralCommandLine createCommandLine() throws IOException {
|
||||
Path wslExe = WSLDistribution.findWslExe();
|
||||
if (wslExe == null) {
|
||||
throw new IOException("No wsl.exe found in %PATH%");
|
||||
}
|
||||
return new GeneralCommandLine(wslExe.toString(), "--list", "--quiet").withCharset(StandardCharsets.UTF_16LE);
|
||||
}
|
||||
|
||||
private static void checkEdtAndReadAction() {
|
||||
Application application = ApplicationManager.getApplication();
|
||||
if (application == null || !application.isInternal() || application.isHeadlessEnvironment()) {
|
||||
return;
|
||||
}
|
||||
if (application.isReadAccessAllowed()) {
|
||||
LOG.error("Please call WslDistributionManager.getInstalledDistributions on a background thread and " +
|
||||
"not under read action as it runs a potentially long operation.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -121,6 +121,8 @@
|
||||
<experimentalFeature id="wsl.execute.with.wsl.exe" percentOfUsers="100">
|
||||
<description>Run WSL commands using wsl.exe, e.g. "wsl.exe --distribution Ubuntu-20.04" (available since Windows 10 update 1903).</description>
|
||||
</experimentalFeature>
|
||||
<applicationService serviceInterface="com.intellij.execution.wsl.WslDistributionManager"
|
||||
serviceImplementation="com.intellij.execution.wsl.WslDistributionManagerImpl"/>
|
||||
<executionTargetType implementation="com.intellij.execution.wsl.target.WslTargetType"/>
|
||||
|
||||
<virtualFileSystem implementationClass="com.intellij.openapi.vfs.impl.local.LocalFileSystemImpl" key="file" physical="true"/>
|
||||
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.execution.wsl;
|
||||
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.testFramework.ServiceContainerUtil;
|
||||
import com.intellij.testFramework.fixtures.BareTestFixtureTestCase;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class WslDistributionManagerTest extends BareTestFixtureTestCase {
|
||||
|
||||
@Test
|
||||
public void caseInsensitiveDistributionName() {
|
||||
String ubuntuName = "Ubuntu";
|
||||
String lowerCaseUbuntuName = StringUtil.toLowerCase(ubuntuName);
|
||||
String debianName = "Debian";
|
||||
ServiceContainerUtil.replaceService(
|
||||
ApplicationManager.getApplication(), WslDistributionManager.class,
|
||||
new WslDistributionManager() {
|
||||
@Override
|
||||
protected @NotNull List<String> loadInstalledDistributionMsIds() {
|
||||
return List.of(ubuntuName, debianName);
|
||||
}
|
||||
}, getTestRootDisposable());
|
||||
WslDistributionManager distributionManager = WslDistributionManager.getInstance();
|
||||
WSLDistribution lowerCaseUbuntu = distributionManager.getOrCreateDistributionByMsId(lowerCaseUbuntuName);
|
||||
assertEquals(lowerCaseUbuntuName, lowerCaseUbuntu.getMsId());
|
||||
WSLDistribution debian = distributionManager.getOrCreateDistributionByMsId(debianName);
|
||||
assertEquals(debianName, debian.getMsId());
|
||||
|
||||
// Load the installed distributions to replace previously created distributions with different case.
|
||||
distributionManager.getInstalledDistributions();
|
||||
|
||||
assertEquals(ubuntuName, distributionManager.getOrCreateDistributionByMsId(lowerCaseUbuntuName).getMsId());
|
||||
assertEquals(debianName, distributionManager.getOrCreateDistributionByMsId(debianName).getMsId());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user