Python: Make version-specific methods static is PythonSdkFlavor: they aren't flavor-specific.

It is always `--version` and `Python [version]`, no need to have virtual overridable methods.
One doesn't need to know the flavor to get a python version.


(cherry picked from commit 63b16768f1dd299cb4cefb5fd935c44614d6ffb6)

IJ-CR-151990

GitOrigin-RevId: ebc2eb5428c10048cc6f17a6e5c99632b3f7d2cc
This commit is contained in:
Ilya.Kazakevich
2024-12-19 17:26:06 +01:00
committed by intellij-monorepo-bot
parent 6a0c236e0b
commit f54ea8affd
12 changed files with 90 additions and 24 deletions

View File

@@ -333,7 +333,7 @@ private suspend fun filterLatestUsablePython(flavorsToPythons: List<Pair<PythonS
for ((flavor, paths) in flavorsToPythons) {
for (pythonPath in paths) {
val versionString = validatePythonAndGetVersion(pythonPath, flavor) ?: continue
val languageLevel = flavor.getLanguageLevelFromVersionString(versionString)
val languageLevel =PythonSdkFlavor.getLanguageLevelFromVersionStringStatic(versionString)
// Highest possible, no need to search further
if (languageLevel == LanguageLevel.getLatest()) {
@@ -375,7 +375,7 @@ private suspend fun validatePythonAndGetVersion(pythonBinary: PythonBinary, flav
fileLogger.warn("$pythonBinary didn't return in $timeout, skipping")
}
0 -> {
return@withContext flavor.getVersionString(pythonBinary.pathString)
return@withContext PythonSdkFlavor.getVersionStringStatic(pythonBinary.pathString)
}
else -> {
fileLogger.warn("$pythonBinary exited with code ${exitCode}, skipping")

View File

@@ -32,5 +32,6 @@
<orderEntry type="library" name="jackson-databind" level="project" />
<orderEntry type="library" name="jackson-module-kotlin" level="project" />
<orderEntry type="module" module-name="intellij.platform.ide.progress" />
<orderEntry type="library" scope="TEST" name="JUnit5" level="project" />
</component>
</module>

View File

@@ -47,6 +47,12 @@ import static com.jetbrains.python.sdk.flavors.PySdkFlavorUtilKt.getFileExecutio
*/
public abstract class PythonSdkFlavor<D extends PyFlavorData> {
public static final ExtensionPointName<PythonSdkFlavor<?>> EP_NAME = ExtensionPointName.create("Pythonid.pythonSdkFlavor");
/**
* <code>
* Python 3.11
* </code>
*/
private static final String PYTHON_VERSION_STRING_PREFIX = "Python ";
/**
* To prevent log pollution and slowness, we cache every {@link #isFileExecutable(String, TargetEnvironmentConfiguration)} call
* and only log it once
@@ -58,6 +64,12 @@ public abstract class PythonSdkFlavor<D extends PyFlavorData> {
private static final Pattern VERSION_RE = Pattern.compile("(Python \\S+).*");
private static final Logger LOG = Logger.getInstance(PythonSdkFlavor.class);
/**
* <code>
* python --version
* </code>
*/
public static final String PYTHON_VERSION_ARG = "--version";
/**
@@ -324,19 +336,31 @@ public abstract class PythonSdkFlavor<D extends PyFlavorData> {
return Files.exists(path) && Files.isExecutable(path);
}
/**
* @deprecated use {@link #getVersionStringStatic(String)}
* @param sdkHome
* @return
*/
@Deprecated(forRemoval = true)
@Nullable
@RequiresBackgroundThread(generateAssertion = false) //because of process output
public String getVersionString(@Nullable String sdkHome) {
return getVersionStringStatic(sdkHome);
}
@Nullable
@RequiresBackgroundThread(generateAssertion = false) //because of process output
public static String getVersionStringStatic(@Nullable String sdkHome) {
if (sdkHome == null) {
return null;
}
final String runDirectory = new File(sdkHome).getParent();
final ProcessOutput processOutput = PySdkUtil.getProcessOutput(runDirectory, new String[]{sdkHome, getVersionOption()}, 10000);
final ProcessOutput processOutput = PySdkUtil.getProcessOutput(runDirectory, new String[]{sdkHome, PYTHON_VERSION_ARG}, 10000);
return getVersionStringFromOutput(processOutput);
}
@Nullable
public String getVersionStringFromOutput(@NotNull ProcessOutput processOutput) {
public static String getVersionStringFromOutput(@NotNull ProcessOutput processOutput) {
if (processOutput.getExitCode() != 0) {
String errors = processOutput.getStderr();
if (StringUtil.isEmpty(errors)) {
@@ -353,12 +377,16 @@ public abstract class PythonSdkFlavor<D extends PyFlavorData> {
}
@Nullable
public String getVersionStringFromOutput(@NotNull String output) {
public static String getVersionStringFromOutput(@NotNull String output) {
return PatternUtil.getFirstMatch(Arrays.asList(StringUtil.splitByLines(output)), VERSION_RE);
}
/**
* @deprecated use {@link #PYTHON_VERSION_ARG}
*/
@Deprecated(forRemoval = true)
public @NotNull String getVersionOption() {
return "-V";
return PYTHON_VERSION_ARG;
}
public @NotNull Collection<String> getExtraDebugOptions() {
@@ -382,13 +410,26 @@ public abstract class PythonSdkFlavor<D extends PyFlavorData> {
@NotNull
public LanguageLevel getLanguageLevel(@NotNull Sdk sdk) {
return getLanguageLevelFromVersionString(sdk.getVersionString());
return getLanguageLevelFromVersionStringStatic(sdk.getVersionString());
}
@NotNull
@RequiresBackgroundThread(generateAssertion = false) //because of process output
public LanguageLevel getLanguageLevel(@NotNull String sdkHome) {
return getLanguageLevelFromVersionString(getVersionString(sdkHome));
return getLanguageLevelFromVersionStringStatic(getVersionString(sdkHome));
}
/**
* Returns wrong language level when argument is null which isn't probably what you except.
* Be sure to check argument for null
*
* @deprecated use {@link #getLanguageLevelFromVersionStringStatic(String)}
*/
@Deprecated(forRemoval = true)
@NotNull
public LanguageLevel getLanguageLevelFromVersionString(@Nullable String version) {
return getLanguageLevelFromVersionStringStatic(version);
}
/**
@@ -396,10 +437,9 @@ public abstract class PythonSdkFlavor<D extends PyFlavorData> {
* Be sure to check argument for null
*/
@NotNull
public LanguageLevel getLanguageLevelFromVersionString(@Nullable String version) {
final String prefix = getName() + " ";
if (version != null && version.startsWith(prefix)) {
return LanguageLevel.fromPythonVersion(version.substring(prefix.length()));
public static LanguageLevel getLanguageLevelFromVersionStringStatic(@Nullable String version) {
if (version != null && version.startsWith(PYTHON_VERSION_STRING_PREFIX)) {
return LanguageLevel.fromPythonVersion(version.substring(PYTHON_VERSION_STRING_PREFIX.length()));
}
return LanguageLevel.getDefault();
}

View File

@@ -0,0 +1,23 @@
package com.intellij.python.junit5Tests.unit
import com.jetbrains.python.psi.LanguageLevel
import com.jetbrains.python.sdk.flavors.PythonSdkFlavor
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Test
class PyVersionTest {
@Test
fun testVersion() {
val pythons = mapOf(
"Python 3.10" to LanguageLevel.PYTHON310,
"Python 3.11.2" to LanguageLevel.PYTHON311,
"Python 3.8.0" to LanguageLevel.PYTHON38
)
for ((versionString, level) in pythons) {
val calculatedLevel = PythonSdkFlavor.getLanguageLevelFromVersionStringStatic(versionString)
Assertions.assertEquals(level, calculatedLevel, "wrong level for $versionString")
}
}
}

View File

@@ -45,11 +45,11 @@ public final class PyRemoteInterpreterUtil {
ProcessOutput processOutput;
try {
try {
String[] command = {data.getInterpreterPath(), flavor.getVersionOption()};
String[] command = {data.getInterpreterPath(), PythonSdkFlavor.PYTHON_VERSION_ARG};
processOutput = PyRemoteProcessStarterManagerUtil.getManager(data).executeRemoteProcess(myProject, command, null,
data, new PyRemotePathMapper());
if (processOutput.getExitCode() == 0) {
final String version = flavor.getVersionStringFromOutput(processOutput);
final String version = PythonSdkFlavor.getVersionStringFromOutput(processOutput);
if (version != null || nullForUnparsableVersion) {
result.set(version);
return;

View File

@@ -73,7 +73,7 @@ object PySdkToInstallManager {
}
.filter {
val detectedLevel = PythonSdkFlavor.getFlavor(it)?.let { flavor ->
flavor.getLanguageLevelFromVersionString(flavor.getVersionString(it.homePath!!))
PythonSdkFlavor.getLanguageLevelFromVersionStringStatic(PythonSdkFlavor.getVersionStringStatic(it.homePath!!))
}
languageLevel?.equals(detectedLevel) ?: true
}

View File

@@ -14,6 +14,7 @@ import com.intellij.openapi.util.Disposer
import com.jetbrains.python.PythonHelper
import com.jetbrains.python.run.*
import com.jetbrains.python.run.target.HelpersAwareTargetEnvironmentRequest
import com.jetbrains.python.sdk.flavors.PythonSdkFlavor
class PyTargetsIntrospectionFacade(val sdk: Sdk, val project: Project) {
private val pyRequest: HelpersAwareTargetEnvironmentRequest =
@@ -34,11 +35,11 @@ class PyTargetsIntrospectionFacade(val sdk: Sdk, val project: Project) {
val cmdBuilder = TargetedCommandLineBuilder(targetEnvRequest)
sdk.configureBuilderToRunPythonOnTarget(cmdBuilder)
val sdkFlavor = sdk.sdkFlavor
cmdBuilder.addParameter(sdkFlavor.versionOption)
cmdBuilder.addParameter(PythonSdkFlavor.PYTHON_VERSION_ARG)
val cmd = cmdBuilder.build()
val environment = targetEnvRequest.prepareEnvironment(TargetProgressIndicatorAdapter(indicator))
return sdkFlavor.getVersionStringFromOutput(cmd.execute(environment, indicator))
return PythonSdkFlavor.getVersionStringFromOutput(cmd.execute(environment, indicator))
}
@Throws(ExecutionException::class)

View File

@@ -476,7 +476,7 @@ public final class PythonSdkType extends SdkType {
return null;
}
final PythonSdkFlavor flavor = PythonSdkFlavor.getFlavor(sdkHome);
return flavor != null ? flavor.getVersionString(sdkHome) : null;
return flavor != null ? PythonSdkFlavor.getVersionStringStatic(sdkHome) : null;
}
@Override

View File

@@ -16,6 +16,7 @@ import com.intellij.openapi.util.Ref
import com.intellij.remote.RemoteSdkException
import com.intellij.util.ui.UIUtil
import com.jetbrains.python.PyBundle
import com.jetbrains.python.sdk.flavors.PythonSdkFlavor
@Throws(RemoteSdkException::class)
fun PyTargetAwareAdditionalData.getInterpreterVersion(project: Project?, nullForUnparsableVersion: Boolean = true): String? {
@@ -38,7 +39,7 @@ fun PyTargetAwareAdditionalData.getInterpreterVersion(project: Project?,
try {
val targetedCommandLineBuilder = TargetedCommandLineBuilder(targetEnvironmentRequest)
targetedCommandLineBuilder.setExePath(interpreterPath)
targetedCommandLineBuilder.addParameter(flavor.versionOption)
targetedCommandLineBuilder.addParameter(PythonSdkFlavor.PYTHON_VERSION_ARG)
val targetEnvironment = targetEnvironmentRequest.prepareEnvironment(TargetProgressIndicatorAdapter(indicator))
val targetedCommandLine = targetedCommandLineBuilder.build()
val process = targetEnvironment.createProcess(targetedCommandLine, indicator)
@@ -46,7 +47,7 @@ fun PyTargetAwareAdditionalData.getInterpreterVersion(project: Project?,
val capturingProcessHandler = CapturingProcessHandler(process, Charsets.UTF_8, commandLineString)
val processOutput = capturingProcessHandler.runProcess()
if (processOutput.exitCode == 0) {
val version = flavor.getVersionStringFromOutput(processOutput)
val version = PythonSdkFlavor.getVersionStringFromOutput(processOutput)
if (version != null || nullForUnparsableVersion) {
result.set(version)
return

View File

@@ -47,7 +47,7 @@ sealed class PythonType<T : Any>(private val tag: @NonNls String? = null) {
?: error("No python in $path")
val flavor = PythonSdkFlavor.tryDetectFlavorByLocalPath(binary.toString())
?: error("Unknown flavor: $binary")
flavor.getVersionString(binary.toString())?.let { path to flavor.getLanguageLevelFromVersionString(it) }
flavor.getVersionString(binary.toString())?.let { path to PythonSdkFlavor.getLanguageLevelFromVersionStringStatic(it) }
?: error("Can't get language level for $flavor , $binary")
}
.sortedByDescending { (_, languageLevel) -> languageLevel }

View File

@@ -29,7 +29,7 @@ internal suspend fun getPythonVersion(sdk: Sdk, request: TargetEnvironmentReques
internal suspend fun getPythonVersion(commandLineBuilder: TargetedCommandLineBuilder,
flavor: PythonSdkFlavor<*>,
request: TargetEnvironmentRequest): String? {
commandLineBuilder.addParameter(flavor.versionOption)
commandLineBuilder.addParameter(PythonSdkFlavor.PYTHON_VERSION_ARG)
val commandLine = commandLineBuilder.build()
val result = request
.prepareEnvironment(TargetProgressIndicator.EMPTY)
@@ -41,7 +41,7 @@ internal suspend fun getPythonVersion(commandLineBuilder: TargetedCommandLineBui
val out = result.stdOut.decodeToString()
Assert.assertEquals(err, 0, result.exitCode)
val versionString = out.ifBlank { err }.trim()
return flavor.getVersionStringFromOutput(versionString)
return PythonSdkFlavor.getVersionStringFromOutput(versionString)
}
/**

View File

@@ -53,7 +53,7 @@ public class PySdkFlavorTest extends PyTestCase {
@NotNull
private Sdk createMockSdk(@NotNull PythonSdkFlavor flavor, @NotNull String versionOutput) {
final String versionString = flavor.getVersionStringFromOutput(versionOutput);
final String versionString = PythonSdkFlavor.getVersionStringFromOutput(versionOutput);
final Sdk sdk = ProjectJdkTable.getInstance().createSdk("Test", PythonSdkType.getInstance());
SdkModificator sdkModificator = sdk.getSdkModificator();
sdkModificator.setHomePath("/path/to/sdk");