[python] Rewrite PythonHelpersLocator

1. `PythonHelpersLocator` is an API to get helpers. It is aware of PyCharm Community helpers but also aware of some EP that provides additional helper paths.
2. EP implementations for PyCharm Prof and Jupyter that provide additional (prof) helpers.
It will help avoid problems with which Locator to use from Professional, Community or Jupiter plugins.


Merge-request: IJ-MR-140027
Merged-by: Egor Eliseev <Egor.Eliseev@jetbrains.com>

GitOrigin-RevId: c7c34f323247002699866f12f6ff5a08cf6a18ff
This commit is contained in:
Egor.Eliseev
2024-07-23 12:04:22 +00:00
committed by intellij-monorepo-bot
parent 71e4ebad19
commit 0be08ded36
22 changed files with 108 additions and 92 deletions

View File

@@ -21,11 +21,17 @@ import com.intellij.openapi.application.PathManager
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.extensions.ExtensionPointName
import com.intellij.util.PathUtil
import com.intellij.util.concurrency.annotations.RequiresBackgroundThread
import org.jetbrains.annotations.ApiStatus.Internal
import org.jetbrains.annotations.NonNls
import java.io.File
import org.jetbrains.annotations.TestOnly
import java.nio.file.Path
import kotlin.io.path.Path
import kotlin.io.path.absolutePathString
import kotlin.io.path.exists
import kotlin.io.path.pathString
abstract class PythonHelpersLocator {
interface PythonHelpersLocator {
companion object {
val LOG = Logger.getInstance(PythonHelpersLocator::class.java)
private const val PROPERTY_HELPERS_LOCATION = "idea.python.helpers.path"
@@ -36,108 +42,117 @@ abstract class PythonHelpersLocator {
/**
* @return A list of Path objects representing the roots of the Python Helpers.
*/
@Internal
@JvmStatic
fun getHelpersRoots(): List<Path> = EP_NAME.extensionList.first().getRoots()
fun getHelpersRoots(): List<Path> = EP_NAME.extensionList.mapNotNull { it.getRoot() }
/**
* Retrieves the root file of the Community Helpers extension.
* Community Helpers root always should be first in an extension list
* Retrieves a path to the root file of the Community Helpers extension.
*
* @return The root File of the Community Helpers extension.
* @return The root Path of the Community Helpers extension.
*/
@Internal
@JvmStatic
fun getCommunityHelpersRoot(): File = EP_NAME.extensionList.first().getRoots().first().toFile()
fun getCommunityHelpersRoot(): Path = EP_NAME.extensionList.first().getCommunityHelpersRootPath()
/**
* Retrieves File of a helper file given its resource name.
* Retrieves Path of a helper file given its resource name.
*
* @param resourceName The name of the helper resource file.
* @return File of the helper file, or null if the file does not exist.
* @return Path of the helper file, or null if the file does not exist.
*/
@JvmStatic
fun findFileInHelpers(resourceName: String): File? {
@RequiresBackgroundThread
fun findPathInHelpers(resourceName: String): Path? {
for (helperRoot in getHelpersRoots()) {
val file = File(helperRoot.toFile(), resourceName)
if (file.exists())
return file
val path = Path.of(helperRoot.pathString, resourceName)
if (path.exists())
return path
}
LOG.warn("File $resourceName does not exist in helpers root")
return null
}
@Internal
@TestOnly
@JvmStatic
@RequiresBackgroundThread
fun getPythonCommunityPath(): Path {
val pathFromUltimate = Path.of(PathManager.getHomePath(), "community/python")
if (pathFromUltimate.exists()) {
return pathFromUltimate
}
return Path.of(PathManager.getHomePath(), "python")
}
/**
* Retrieves the absolute path of a helper file given its resource name.
*
* @param resourceName The name of the helper resource file.
* @param resourceName The name of the helper resource file (for example, `pydev` or `jupyter_debug`).
* @return The absolute path of the helper file, or null if the file does not exist.
*/
@Internal
@JvmStatic
fun findPathInHelpers(@NonNls resourceName: String): String = findFileInHelpers(resourceName)?.absolutePath ?: ""
@JvmStatic
fun getPythonCommunityPath(): String {
val pathFromUltimate = File(PathManager.getHomePath(), "community/python")
if (pathFromUltimate.exists()) {
return pathFromUltimate.path
}
return File(PathManager.getHomePath(), "python").path
}
@RequiresBackgroundThread
fun findPathStringInHelpers(@NonNls resourceName: String): String = findPathInHelpers(resourceName)?.absolutePathString() ?: ""
@Deprecated("Use {@link PythonHelpersLocator#findPathInHelpers}.", ReplaceWith("findPathInHelpers(resourceName)"))
@JvmStatic
fun getHelperPath(@NonNls resourceName: String): String = findPathInHelpers(resourceName)
fun getHelperPath(@NonNls resourceName: String): String = findPathStringInHelpers(resourceName)
}
// Always add Community Helpers root on the first place of root's list
protected open fun getRoots(): List<Path> = listOf(getCommunityHelpersRootFile().toPath().normalize())
@Internal
fun getRoot(): Path? = getCommunityHelpersRootPath().normalize()
private fun getCommunityHelpersRootFile(): File {
private fun getCommunityHelpersRootPath(): Path {
val property = System.getProperty(PROPERTY_HELPERS_LOCATION)
return if (property != null) {
File(property)
Path.of(property)
}
else getHelpersRoot(COMMUNITY_HELPERS_MODULE_NAME, "/python/helpers").also {
assertHelpersLayout(it)
}
}
protected open fun getHelpersRoot(moduleName: String, relativePath: String): File =
@Internal
@RequiresBackgroundThread
fun getHelpersRoot(moduleName: String, relativePath: String): Path =
findRootByJarPath(PathUtil.getJarPathForClass(PythonHelpersLocator::class.java), moduleName, relativePath)
protected fun findRootByJarPath(jarPath: String, moduleName: String, relativePath: String): File {
@Internal
fun findRootByJarPath(jarPath: String, moduleName: String, relativePath: String): Path {
return if (PluginManagerCore.isRunningFromSources()) {
File(PathManager.getCommunityHomePath() + relativePath)
Path.of(PathManager.getCommunityHomePath(), relativePath)
}
else {
getPluginBaseDir(jarPath)?.let {
File(it, PathUtil.getFileName(relativePath))
} ?: File(File(jarPath).parentFile, moduleName)
Path.of(it.absolutePathString(), PathUtil.getFileName(relativePath))
} ?: Path.of(Path(jarPath).parent.absolutePathString(), moduleName)
}
}
private fun getPluginBaseDir(jarPath: String): File? {
private fun getPluginBaseDir(jarPath: String): Path? {
if (jarPath.endsWith(".jar")) {
val jarFile = File(jarPath)
val path = Path.of(jarPath)
LOG.assertTrue(jarFile.exists(), "jar file cannot be null")
return jarFile.parentFile.parentFile
LOG.assertTrue(path.exists(), "$path to plugin base bir does not exists")
return path.parent.parent
}
return null
}
private fun assertHelpersLayout(root: File): File {
val path = root.absolutePath
LOG.assertTrue(root.exists(), "Helpers root does not exist $path")
@RequiresBackgroundThread
private fun assertHelpersLayout(root: Path): Path {
LOG.assertTrue(root.exists(), "Helpers root does not exist $root")
listOf("generator3", "pycharm", "pycodestyle.py", "pydev", "syspath.py", "typeshed").forEach { child ->
LOG.assertTrue(File(root, child).exists(), "No '$child' inside $path")
LOG.assertTrue(root.resolve(child).exists(), "No '$child' inside $root")
}
return root
}
}
internal class PythonHelpersLocatorDefault : PythonHelpersLocator()
internal class PythonHelpersLocatorDefault : PythonHelpersLocator

View File

@@ -30,7 +30,7 @@ public final class PyStdlibUtil {
@Nullable
private static Set<String> loadStdlibPackagesList() {
final Logger log = Logger.getInstance(PyStdlibUtil.class.getName());
final String helperPath = PythonHelpersLocator.findPathInHelpers("/tools/stdlib_packages.txt");
final String helperPath = PythonHelpersLocator.findPathStringInHelpers("/tools/stdlib_packages.txt");
try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(helperPath), StandardCharsets.UTF_8))) {
return reader.lines().collect(Collectors.toSet());
}

View File

@@ -158,7 +158,7 @@ object PyTypeShed {
get() {
val paths = listOf("${PathManager.getConfigPath()}/typeshed",
"${PathManager.getConfigPath()}/../typeshed",
PythonHelpersLocator.findPathInHelpers("typeshed"))
PythonHelpersLocator.findPathStringInHelpers("typeshed"))
return paths.asSequence()
.filter { File(it).exists() }
.firstOrNull()

View File

@@ -81,7 +81,7 @@ public final class PyUserSkeletonsUtil {
private static List<String> getPossibleUserSkeletonsPaths() {
final List<String> result = new ArrayList<>();
result.add(PathManager.getConfigPath() + File.separator + USER_SKELETONS_DIR);
result.add(PythonHelpersLocator.findPathInHelpers(USER_SKELETONS_DIR));
result.add(PythonHelpersLocator.findPathStringInHelpers(USER_SKELETONS_DIR));
return result;
}

View File

@@ -203,7 +203,7 @@ public class PyDocumentationBuilder {
private void buildForKeyword(@NotNull String name) {
try {
try (FileReader reader = new FileReader(PythonHelpersLocator.findPathInHelpers("/tools/python_keywords/" + name),
try (FileReader reader = new FileReader(PythonHelpersLocator.findPathStringInHelpers("/tools/python_keywords/" + name),
StandardCharsets.UTF_8)) {
final String text = FileUtil.loadTextAndClose(reader);
final String converted = StringUtil.convertLineSeparators(text, "\n");

View File

@@ -42,7 +42,7 @@ public final class UnsupportedFeaturesUtil {
private static void fillTestCaseMethods() throws IOException {
final Logger log = Logger.getInstance(UnsupportedFeaturesUtil.class.getName());
try (FileReader reader = new FileReader(PythonHelpersLocator.findPathInHelpers("/tools/class_method_versions.xml"))) {
try (FileReader reader = new FileReader(PythonHelpersLocator.findPathStringInHelpers("/tools/class_method_versions.xml"))) {
final XMLReader xr = XMLReaderFactory.createXMLReader();
final ClassMethodsParser parser = new ClassMethodsParser();
xr.setContentHandler(parser);
@@ -55,7 +55,7 @@ public final class UnsupportedFeaturesUtil {
private static void fillMaps() throws IOException {
Logger log = Logger.getInstance(UnsupportedFeaturesUtil.class.getName());
try (FileReader reader = new FileReader(PythonHelpersLocator.findPathInHelpers("/tools/versions.xml"))) {
try (FileReader reader = new FileReader(PythonHelpersLocator.findPathStringInHelpers("/tools/versions.xml"))) {
XMLReader xr = XMLReaderFactory.createXMLReader();
VersionsParser parser = new VersionsParser();
xr.setContentHandler(parser);