Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,8 @@ jobs:
strategy:
matrix:
environmentName:
- 252
- 253
- 261
- 262
fail-fast: false
steps:
- name: Checkout code
Expand Down
16 changes: 11 additions & 5 deletions buildSrc/src/main/kotlin/intellijUtils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -123,11 +123,17 @@ val Project.csharpPlugins: List<String>
// Plugins which we add to tests for all modules.
// It's the most common plugins which affect the behavior of the plugin code
val Project.commonTestPlugins: List<String>
get() = listOf(
imagesPlugin, // adds `svg` file type and makes IDE consider .svg files as text ones
yamlPlugin, // makes IDE consider .yaml files as text ones and affects formatting of yaml files
jsonPlugin, // dependency of a lot of other bundled plugin
)
get() = buildList {
add(imagesPlugin) // adds `svg` file type and makes IDE consider .svg files as text ones
add(yamlPlugin) // makes IDE consider .yaml files as text ones and affects formatting of yaml files
add(jsonPlugin) // dependency of a lot of other bundled plugin
if (environmentName.toInt() >= 262) {
// Since 2026.2 the SM/test runner modules are provided by this separate bundled plugin.
add("intellij.testRunner.plugin")
// Loads the extension point used by JCEF-aware UI code in the test application.
add("com.intellij.modules.jcef")
}
}

data class TypeWithVersion(val type: IntelliJPlatformType, val version: String)

Expand Down
4 changes: 2 additions & 2 deletions gradle-262.properties
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ customUntilBuild=262.*
# Existent IDE versions can be found in the following repos:
# https://www.jetbrains.com/intellij-repository/releases/
# https://www.jetbrains.com/intellij-repository/snapshots/
ideaVersion=IU-262.8665-EAP-CANDIDATE-SNAPSHOT
clionVersion=CL-262-EAP-SNAPSHOT
ideaVersion=IU-2026.2
clionVersion=CL-2026.2
pycharmVersion=PC-262-EAP-SNAPSHOT
riderVersion=RD-2026.2-EAP9-SNAPSHOT
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# supported values: 252, 253, 261, 262
environmentName=262

pluginVersion=2026.15
pluginVersion=2026.16

# type of IDE (IDEA, CLion, etc.) used to build/test running
# for more details see `Different IDEs` section in `PlatformVersions.md`
Expand Down
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.0-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip
networkTimeout=10000
retries=0
retryBackOffMs=500
Expand Down
12 changes: 12 additions & 0 deletions intellij-plugin/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,17 @@ dependencies {

intellijPlatform {
intellijIde(baseVersion)
// hs-core tests are compiled in this module. Since 2026.2 these APIs are
// no longer part of the IDE's core classpath, so their modules must be
// repeated here instead of relying on hs-core's non-transitive platform dependencies.
bundledModulesSince(
baseVersion, 262,
"intellij.platform.smRunner",
"intellij.platform.testRunner",
"intellij.platform.ui.jcef",
"intellij.libraries.jcef",
"intellij.platform.sqlite",
)

pluginModule(implementation(project("hs-jvm-core")))
pluginModule(implementation(project("hs-Java")))
Expand Down Expand Up @@ -168,6 +179,7 @@ tasks {
// This is needed because hs-core tests use relative path "testData/" for test resources
test {
workingDir = project("hs-core").projectDir
systemProperty("idea.home.path", intellijPlatform.platformPath.toString())
}

// Copy hs-core.xml and Hyperskill.xml to main plugin JAR for XInclude resolution.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,15 @@ project(':util') {
}
}

def utilProject = rootProject.project(':util')

configure(subprojects.findAll { it.name != 'util' }) {
dependencies {
testImplementation project(':util').sourceSets.main.output
testImplementation project(':util').sourceSets.test.output
testImplementation utilProject.sourceSets.main.output
testImplementation utilProject.sourceSets.test.output
}
}

wrapper {
gradleVersion = hs.gradle.version
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,11 @@ project(':util') {
}
}

def utilProject = rootProject.project(':util')

configure(subprojects.findAll { it.name != 'util' }) {
dependencies {
implementation project(':util').sourceSets.main.output
testImplementation project(':util').sourceSets.test.output
implementation utilProject.sourceSets.main.output
testImplementation utilProject.sourceSets.test.output
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,11 @@ project(':util') {
}
}

def utilProject = rootProject.project(':util')

configure(subprojects.findAll { it.name != 'util' }) {
dependencies {
implementation project(':util').sourceSets.main.output
testImplementation project(':util').sourceSets.test.output
implementation utilProject.sourceSets.main.output
testImplementation utilProject.sourceSets.test.output
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,11 @@ project(':util') {
}
}

def utilProject = rootProject.project(':util')

configure(subprojects.findAll { it.name != 'util' }) {
dependencies {
implementation project(':util').sourceSets.main.output
testImplementation project(':util').sourceSets.test.output
implementation utilProject.sourceSets.main.output
testImplementation utilProject.sourceSets.test.output
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -76,13 +76,15 @@ project(':util') {
}
}

def utilProject = rootProject.project(':util')

configure(subprojects.findAll { it.name != 'util' }) {
dependencies {
testImplementation project(':util').sourceSets.main.output
testImplementation project(':util').sourceSets.test.output
testImplementation utilProject.sourceSets.main.output
testImplementation utilProject.sourceSets.test.output
}
}

wrapper {
gradleVersion = hs.gradle.version
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,11 @@ project(':util') {
}
}

def utilProject = rootProject.project(':util')

configure(subprojects.findAll { it.name != 'util' }) {
dependencies {
implementation project(':util').sourceSets.main.output
testImplementation project(':util').sourceSets.test.output
implementation utilProject.sourceSets.main.output
testImplementation utilProject.sourceSets.test.output
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ import com.jetbrains.python.sdk.detectSystemWideSdks

private val LOG = logger<PyCourseProjectGenerator>()

internal fun prepareSdkForVirtualEnvCreation(sdk: PySdkToCreateVirtualEnv): Sdk {
val homePath = sdk.homePath ?: error("Home path is not passed during fake python sdk creation")
return PyDetectedSdk(homePath)
}

/**
* If [sdk] is an "install Python" suggestion ([PySdkToInstall]), downloads and installs Python
* and returns the freshly detected system-wide SDK, or `null` if the installation failed.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ import com.jetbrains.python.sdk.detectSystemWideSdks

private val LOG = logger<PyCourseProjectGenerator>()

internal fun prepareSdkForVirtualEnvCreation(sdk: PySdkToCreateVirtualEnv): Sdk {
val homePath = sdk.homePath ?: error("Home path is not passed during fake python sdk creation")
return PyDetectedSdk(homePath)
}

/**
* If [sdk] is an "install Python" suggestion ([PySdkToInstall]), downloads and installs Python
* and returns the freshly detected system-wide SDK, or `null` if the installation failed.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ import com.jetbrains.python.sdk.detectSystemWideSdks

private val LOG = logger<PyCourseProjectGenerator>()

internal fun prepareSdkForVirtualEnvCreation(sdk: PySdkToCreateVirtualEnv): Sdk {
val homePath = sdk.homePath ?: error("Home path is not passed during fake python sdk creation")
return PyDetectedSdk(homePath)
}

/**
* If [sdk] is an "install Python" suggestion ([PySdkToInstall]), downloads and installs Python
* and returns the freshly detected system-wide SDK, or `null` if the installation failed.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,16 @@ import com.intellij.openapi.util.UserDataHolderBase
import com.intellij.openapi.vfs.StandardFileSystems
import com.intellij.platform.ide.progress.ModalTaskOwner
import com.intellij.platform.ide.progress.runWithModalProgressBlocking
import com.intellij.python.venv.sdk.flavors.VirtualEnvSdkFlavor
import com.intellij.util.concurrency.annotations.RequiresEdt
import com.jetbrains.python.packaging.PyPackageManagers
import com.jetbrains.python.packaging.PyTargetEnvCreationManager
import com.jetbrains.python.sdk.PythonSdkAdditionalData
import com.jetbrains.python.sdk.PythonSdkType
import com.jetbrains.python.sdk.baseDir
import com.jetbrains.python.sdk.excludeInnerVirtualEnv
import com.jetbrains.python.sdk.flavors.PyFlavorAndData
import com.jetbrains.python.sdk.flavors.PyFlavorData
import com.jetbrains.python.sdk.impl.PySdkBundle
import com.jetbrains.python.sdk.setAssociationToModule
import com.jetbrains.python.sdk.setAssociationToPath
Expand Down Expand Up @@ -108,5 +112,6 @@ private fun createSdkByGenerateTask(
val homePath = ProgressManager.getInstance().run(generateSdkHomePath)
val homeFile = StandardFileSystems.local().refreshAndFindFileByPath(homePath)
?: throw ExecutionException("Python interpreter is not found at $homePath")
return SdkConfigurationUtil.setupSdk(existingSdks.toTypedArray(), homeFile, PythonSdkType.getInstance(), null, null)
val additionalData = PythonSdkAdditionalData(PyFlavorAndData(PyFlavorData.Empty, VirtualEnvSdkFlavor.getInstance()))
return SdkConfigurationUtil.setupSdk(existingSdks.toTypedArray(), homeFile, PythonSdkType.getInstance(), additionalData, null)
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@ import com.jetbrains.python.sdk.detectSystemWideSdks

private val LOG = logger<PyCourseProjectGenerator>()

/**
* Python 262 requires every SDK used to launch Python to have [com.jetbrains.python.sdk.PythonSdkAdditionalData].
* [PySdkToCreateVirtualEnv] already has it; converting it to a [com.jetbrains.python.sdk.PyDetectedSdk]
* would discard that data and make virtual environment creation fail.
*/
internal fun prepareSdkForVirtualEnvCreation(sdk: PySdkToCreateVirtualEnv): Sdk = sdk

@Suppress("DEPRECATION_ERROR")
internal fun installSdkIfSuggested(sdk: Sdk?): Sdk? {
if (sdk !is PySdkToInstallCompat) return null
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package org.hyperskill.academy.python.learning.newproject

import com.intellij.testFramework.LightPlatformTestCase
import com.jetbrains.python.sdk.PythonSdkAdditionalData

class PySdkVirtualEnvCompatibilityTest : LightPlatformTestCase() {
fun testSdkKeepsAdditionalDataForVirtualEnvCreation() {
val sdk = PySdkToCreateVirtualEnv.create("Python 3.14", "/usr/bin/python3.14", "3.14")

val preparedSdk = prepareSdkForVirtualEnvCreation(sdk)

assertSame(sdk, preparedSdk)
assertInstanceOf(preparedSdk.sdkAdditionalData, PythonSdkAdditionalData::class.java)
}
}
30 changes: 30 additions & 0 deletions intellij-plugin/hs-Python/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,33 @@ plugins {
id("intellij-plugin-module-conventions")
}

private val pythonPlatformModuleDependenciesMarker = "<!-- PYTHON_PLATFORM_MODULE_DEPENDENCIES -->"
private val pythonPlatformModuleDependencies = if (environmentName.toInt() >= 262) {
listOf(
"intellij.libraries.lucene.common",
"intellij.python.sdk",
).joinToString("\n") { " <module name=\"$it\"/>" }
}
else {
""
}

dependencies {
intellijPlatform {
// needed to load `org.toml.lang plugin` for Python plugin in tests
val ideVersion = if (isRiderIDE) ideaVersion else baseVersion
intellijIde(ideVersion)
bundledModulesSince(ideVersion, 262, "intellij.platform.smRunner", "intellij.platform.testRunner")
if (platformBranch(ideVersion) >= 262) {
// In 2026.2 these libraries moved under separate bundled plugins. PythonCore
// reaches them through JSON/spellchecker and its JCEF integration, so their
// owning plugins must be loaded in the test sandbox.
testBundledPlugins(
"intellij.libraries.misc.plugin",
"com.intellij.modules.jcef",
"intellij.structureView.plugin",
)
}

intellijPlugins(pythonPlugin)
testIntellijPlugins(tomlPlugin)
Expand All @@ -18,3 +39,12 @@ dependencies {

testImplementation(project(":intellij-plugin:hs-core", "testOutput"))
}

tasks.processResources {
inputs.property("pythonPlatformModuleDependencies", pythonPlatformModuleDependencies)
filesMatching("hs-Python.xml") {
filter { line ->
if (pythonPlatformModuleDependenciesMarker in line) pythonPlatformModuleDependencies else line
}
}
}
4 changes: 3 additions & 1 deletion intellij-plugin/hs-Python/resources/hs-Python.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
<!--suppress PluginXmlValidity -->
<dependencies>
<plugin id="com.intellij.modules.python"/>
<!-- Python 2026.2+ content-module dependency is inserted here by build.gradle.kts. -->
<!-- PYTHON_PLATFORM_MODULE_DEPENDENCIES -->
</dependencies>

<extensions defaultExtensionNs="com.intellij">
Expand Down Expand Up @@ -31,4 +33,4 @@
id="HyperskillEducational.Python.InstallDependenciesWorkaround"
class="org.hyperskill.academy.python.learning.InstallDependenciesWorkaroundAction"/>
</actions>
</idea-plugin>
</idea-plugin>
Original file line number Diff line number Diff line change
Expand Up @@ -34,28 +34,26 @@ open class PyCourseProjectGenerator(
// See `installSdkIfSuggested` implementations in `branches/<version>/src`.
val installedSdk = installSdkIfSuggested(sdk)
if (installedSdk != null) {
createAndAddVirtualEnv(project, projectSettings, installedSdk)
sdk = projectSettings.sdk
sdk = createAndAddVirtualEnv(project, projectSettings, installedSdk)
}
else if (sdk is PySdkToCreateVirtualEnv) {
val homePath = sdk.homePath ?: error("Home path is not passed during fake python sdk creation")
createAndAddVirtualEnv(project, projectSettings, PyDetectedSdk(homePath))
sdk = projectSettings.sdk
sdk = createAndAddVirtualEnv(project, projectSettings, prepareSdkForVirtualEnvCreation(sdk))
}
sdk = updateSdkIfNeeded(project, sdk)
LOG.warn("PyCourseProjectGenerator: After updateSdkIfNeeded, sdk = ${sdk?.name} at ${sdk?.homePath}")
SdkConfigurationUtil.setDirectoryProjectSdk(project, sdk)
LOG.warn("PyCourseProjectGenerator: After setDirectoryProjectSdk")
if (sdk == null) {
LOG.warn("PyCourseProjectGenerator: SDK is null, skipping package installation")
return
}
LOG.warn("PyCourseProjectGenerator: About to install packages")
installRequiredPackages(project, sdk)
else {
LOG.warn("PyCourseProjectGenerator: About to install packages")
installRequiredPackages(project, sdk)
}
super.afterProjectGenerated(project, projectSettings, openCourseParams, onConfigurationFinished)
}

private fun createAndAddVirtualEnv(project: Project, settings: PyProjectSettings, baseSdk: Sdk) {
private fun createAndAddVirtualEnv(project: Project, settings: PyProjectSettings, baseSdk: Sdk): Sdk? {
val virtualEnvPath = project.basePath + "/.idea/VirtualEnvironment"
val existingSdks = PyConfigurableInterpreterList.getInstance(null).allPythonSdks
val module = ModuleManager.getInstance(project).sortedModules.firstOrNull()
Expand All @@ -76,14 +74,15 @@ open class PyCourseProjectGenerator(
}
catch (e: Exception) {
LOG.warn("Failed to create virtual env in $virtualEnvPath", e)
return
return null
}
settings.sdk = sdk
SdkConfigurationUtil.addSdk(sdk)

if (module != null) {
setAssociationToModule(sdk, module)
}
return sdk
}

companion object {
Expand Down
Loading
Loading