diff --git a/python/python-sdk/intellij.python.sdk.iml b/python/python-sdk/intellij.python.sdk.iml index a05a8bcc2f67..df5ebed6a41f 100644 --- a/python/python-sdk/intellij.python.sdk.iml +++ b/python/python-sdk/intellij.python.sdk.iml @@ -5,6 +5,7 @@ + @@ -25,5 +26,7 @@ + + \ No newline at end of file diff --git a/python/src/com/jetbrains/python/sdk/flavors/WinAppxTools.kt b/python/python-sdk/src/com/jetbrains/python/sdk/WinAppxTools.kt similarity index 85% rename from python/src/com/jetbrains/python/sdk/flavors/WinAppxTools.kt rename to python/python-sdk/src/com/jetbrains/python/sdk/WinAppxTools.kt index 2d216a28babc..19664617dcdf 100644 --- a/python/src/com/jetbrains/python/sdk/flavors/WinAppxTools.kt +++ b/python/python-sdk/src/com/jetbrains/python/sdk/WinAppxTools.kt @@ -1,16 +1,17 @@ // 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.jetbrains.python.sdk.flavors +package com.jetbrains.python.sdk import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.util.SystemInfo +import com.intellij.util.io.isAncestor import com.sun.jna.platform.win32.Kernel32 import com.sun.jna.platform.win32.Kernel32.* import com.sun.jna.platform.win32.Ntifs import com.sun.jna.platform.win32.WinioctlUtil import com.sun.jna.ptr.IntByReference -import java.io.File -import java.io.FilenameFilter import java.nio.ByteBuffer +import java.nio.file.Path +import kotlin.io.path.* /** * AppX packages installed to AppX volume (see ``Get-AppxDefaultVolume``, ``Get-AppxPackage``). @@ -26,6 +27,8 @@ import java.nio.ByteBuffer * There is no Java API to see reparse point destination, so we use JNA. * This tool returns AppX name (either ``PythonSoftwareFoundation...`` or ``DesktopAppInstaller..``). * We use it to check if ``python.exe`` is real python or WindowsStore mock. + * + * See [WinAppxTest] */ /** @@ -39,22 +42,24 @@ private const val storeMarker = "DesktopAppInstaller" * There may be several files linked to this product, we need only first. * And for 3.7 there could be ``PythonSoftwareFoundation.Python.3.7_(SOME_OTHER_UID)``. */ -fun getAppxFiles(expectedProduct: String, filePattern: Regex): Collection = - userAppxFolder?.listFiles(FilenameFilter { _, name -> filePattern.matches(name) }) +fun getAppxFiles(expectedProduct: String?, filePattern: Regex): Collection = + userAppxFolder?.listDirectoryEntries() + ?.filter { filePattern.matches(it.name) } ?.sortedBy { it.nameWithoutExtension } ?.mapNotNull { file -> file.appxProduct?.let { product -> Pair(product, file) } } ?.toMap() - ?.filterKeys { expectedProduct in it } + ?.filterKeys { expectedProduct == null || expectedProduct in it } ?.values ?: emptyList() /** * If file is AppX reparse point link -- return its product name */ -val File.appxProduct: String? +val Path.appxProduct: String? get() { - if (parentFile?.equals(userAppxFolder) != true) return null - return getAppxTag(absolutePath)?.let { + val userAppxFolder = userAppxFolder ?: return null + if (!userAppxFolder.isAncestor(this)) return null + return getAppxTag(this)?.let { if (storeMarker !in it) it else null } } @@ -63,13 +68,13 @@ val File.appxProduct: String? /** * Path to ``%LOCALAPPDATA%\Microsoft\WindowsApps`` */ -private val userAppxFolder = +private val userAppxFolder: Path? = if (!SystemInfo.isWin10OrNewer) { null } else { System.getenv("LOCALAPPDATA")?.let { localappdata -> - val appsPath = File(localappdata, "Microsoft//WindowsApps") + val appsPath = Path.of(localappdata, "Microsoft//WindowsApps") if (appsPath.exists()) appsPath else null } } @@ -99,11 +104,11 @@ See https://youtrack.jetbrains.com/issue/PY-43082 Output is unicode 16-LE */ -private fun getAppxTag(path: String): String? { +private fun getAppxTag(path: Path): String? { if (!SystemInfo.isWin10OrNewer) return null val kernel = INSTANCE val logger = Logger.getInstance(Kernel32::class.java) - val file = kernel.CreateFile(path, GENERIC_READ, FILE_SHARE_READ, null, OPEN_EXISTING, FILE_FLAG_OPEN_REPARSE_POINT, null) + val file = kernel.CreateFile(path.pathString, GENERIC_READ, FILE_SHARE_READ, null, OPEN_EXISTING, FILE_FLAG_OPEN_REPARSE_POINT, null) if (file == INVALID_HANDLE_VALUE) { logger.warn("Invalid handle for $path") return null diff --git a/python/python-sdk/tests/com/jetbrains/python/tests/WinAppxTest.kt b/python/python-sdk/tests/com/jetbrains/python/tests/WinAppxTest.kt new file mode 100644 index 000000000000..c1a3cc05e33a --- /dev/null +++ b/python/python-sdk/tests/com/jetbrains/python/tests/WinAppxTest.kt @@ -0,0 +1,20 @@ +package com.jetbrains.python.tests + +import com.intellij.openapi.util.SystemInfoRt +import com.jetbrains.python.sdk.appxProduct +import com.jetbrains.python.sdk.getAppxFiles +import org.junit.Assert +import org.junit.Assume +import org.junit.Test + +class WinAppxTest { + @Test + fun testAppx() { + Assume.assumeTrue("Win only", SystemInfoRt.isWindows) + val appxFiles = getAppxFiles(null, Regex(".+")) + Assume.assumeTrue("No appx apps found", appxFiles.isNotEmpty()) + for (appxFile in appxFiles) { + Assert.assertNotNull("No appx product product for $appxFile", appxFile.appxProduct) + } + } +} \ No newline at end of file diff --git a/python/src/com/jetbrains/python/sdk/flavors/WinPythonSdkFlavor.java b/python/src/com/jetbrains/python/sdk/flavors/WinPythonSdkFlavor.java index cc94a0e9f00a..4f2f1be61eb3 100644 --- a/python/src/com/jetbrains/python/sdk/flavors/WinPythonSdkFlavor.java +++ b/python/src/com/jetbrains/python/sdk/flavors/WinPythonSdkFlavor.java @@ -26,8 +26,8 @@ import java.io.File; import java.nio.file.Path; import java.util.*; -import static com.jetbrains.python.sdk.flavors.WinAppxToolsKt.getAppxFiles; -import static com.jetbrains.python.sdk.flavors.WinAppxToolsKt.getAppxProduct; +import static com.jetbrains.python.sdk.WinAppxToolsKt.getAppxFiles; +import static com.jetbrains.python.sdk.WinAppxToolsKt.getAppxProduct; /** * This class knows how to find python in Windows Registry according to @@ -100,7 +100,6 @@ public class WinPythonSdkFlavor extends CPythonSdkFlavor { } var path = sdk.getHomePath(); return path != null && isLocalPathValidPython(Path.of(path)); - } @Override @@ -117,8 +116,7 @@ public class WinPythonSdkFlavor extends CPythonSdkFlavor { return true; } - final File file = path.toFile(); - return StringUtils.contains(getAppxProduct(file), APPX_PRODUCT) && isValidSdkPath(file); + return StringUtils.contains(getAppxProduct(path), APPX_PRODUCT) && isValidSdkPath(path.toFile()); } @Override @@ -160,7 +158,7 @@ public class WinPythonSdkFlavor extends CPythonSdkFlavor { @NotNull private static Set getPythonsFromStore() { - return ContainerUtil.map2Set(getAppxFiles(APPX_PRODUCT, PYTHON_EXE), file -> file.getAbsolutePath()); + return ContainerUtil.map2Set(getAppxFiles(APPX_PRODUCT, PYTHON_EXE), file -> file.toAbsolutePath().toString()); } @NotNull