IJPL-248066 Workspace Model: Codegen-api versioning

IJPL-248066 Workspace Model: improve notifications on invalid codegen api version

(cherry picked from commit 0ac7a8eba01c711afa5c7a7c54f879cdb05ff490)

IJPL-248066 Workspace Model: log error on invalid codegen api version

(cherry picked from commit 089be2790b26e6ec8b9b317a938ce820a6cb5716)

IJPL-248066 Workspace Model: message to upgrade IDE in case codegen versions are different

(cherry picked from commit 7ace5b424ae5153686093b951ff34efb293822ab)

IJPL-248066 Workspace Model: fix codegen-api version check

Message in case versions were same was falsely saying they were incompatible

(cherry picked from commit 1344526a96b109bb1d6ee0ee6778619fa4bceb54)

IJPL-248066 Workspace Model: change codegen-api version check

Merge-request: IJ-MR-212689
Merged-by: Kirill Bochkarev <kirill.bochkarev@jetbrains.com>
(cherry picked from commit 559c716fa09eb8621935c404790c3cc5d26a7f01)


Merge-request: IJ-MR-217063
Merged-by: Kirill Bochkarev <kirill.bochkarev@jetbrains.com>

GitOrigin-RevId: b45269b9bd1dfcd07ce4c17ff6b999a9e2ab6de6
This commit is contained in:
Kirill Bochkarev
2026-08-05 02:56:26 +00:00
committed by intellij-monorepo-bot
parent 295f51aaab
commit 0cc8ebae2c
2 changed files with 59 additions and 15 deletions
@@ -28,6 +28,9 @@ command.name.generate.code.for.workspace.entities.in=Generate Code for Workspace
notification.workspace.incompatible.codegen.api.versions=Incompatible codegen api versions
notification.workspace.incompatible.codegen.api.versions.content.older=Codegen-api version in devkit plugin ({0}) is older than in codegen-impl ({1}). Please, update your IDE
notification.workspace.incompatible.codegen.api.versions.content.newer=Codegen-api version in devkit plugin ({0}) is newer than in codegen-impl ({1}). Please, downgrade your IDE
notification.workspace.incompatible.codegen.api.versions.content.none=Could not read codegen api version from {0}. Check logs for a warning with details
notification.workspace.compatible.but.different.codegen.api.versions=Different codegen api versions
notification.workspace.compatible.but.different.codegen.api.versions.content=Codegen-api version in devkit plugin ({0}) and in codegen-impl ({1}) differ. Generated code might be missing some latest features. Please, update your IDE
notification.workspace.code.generation.not.available=Workspace code generation not available
notification.workspace.code.generation.not.available.message=Entity code generation is not supported for plugins yet
action.WorkspaceModelGenerateAllModulesAction.text=Generate Workspace Model Implementation for All Modules
@@ -16,6 +16,7 @@ import com.intellij.openapi.application.readAction
import com.intellij.openapi.application.writeIntentReadAction
import com.intellij.openapi.command.WriteCommandAction
import com.intellij.openapi.components.service
import com.intellij.openapi.diagnostic.fatalErrorWithWarnDetails
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.module.Module
import com.intellij.openapi.project.DumbService
@@ -52,7 +53,6 @@ import org.jetbrains.kotlin.psi.KtPsiFactory
import org.jetbrains.kotlin.psi.psiUtil.isPublic
import org.jetbrains.kotlin.psi.psiUtil.visibilityModifierType
import org.jetbrains.kotlin.resolve.ImportPath
import java.io.IOException
import java.net.URI
import java.util.ServiceLoader
import java.util.jar.Manifest
@@ -216,16 +216,48 @@ object CodeWriter {
copyHeaderComment(sourceFile, file)
}
private data class ApiVersion(val major: Int, val minor: Int, val patch: Int) : Comparable<ApiVersion> {
fun compatible(other: ApiVersion): Boolean {
return major == other.major && minor == other.minor
}
override fun toString(): String = "$major.$minor.$patch"
override fun compareTo(other: ApiVersion): Int {
return when {
major != other.major -> major - other.major
minor != other.minor -> minor - other.minor
else -> patch - other.patch
}
}
}
private fun codegenApiVersionsAreCompatible(project: Project, codeGeneratorFromDownloadedJar: CodeGenerator): Boolean {
val apiVersionInDevkit = getApiVersionFromJSON(CodeGenerator::class.java)
val apiVersionFromDownloadedJar = getApiVersionFromManifest(codeGeneratorFromDownloadedJar::class.java)
if (apiVersionInDevkit == null || apiVersionFromDownloadedJar == null) {
val where = if (apiVersionInDevkit == null) "devkit plugin" else "codegen-impl"
DevKitWorkspaceModelBundle.message("notification.workspace.incompatible.codegen.api.versions.content.none", where)
return false
}
if (apiVersionInDevkit == apiVersionFromDownloadedJar) {
if (apiVersionInDevkit.compatible(apiVersionFromDownloadedJar)) {
if (apiVersionInDevkit.patch == apiVersionFromDownloadedJar.patch)
return true
val groupId = DevKitWorkspaceModelBundle.message("notification.workspace.compatible.but.different.codegen.api.versions")
val message = DevKitWorkspaceModelBundle.message("notification.workspace.compatible.but.different.codegen.api.versions.content",
apiVersionInDevkit,
apiVersionFromDownloadedJar)
NotificationGroupManager.getInstance()
.getNotificationGroup(groupId)
.createNotification(title = groupId, message, NotificationType.WARNING)
.notify(project)
return true
}
val message =
if (apiVersionFromDownloadedJar == CodegenApiVersion.UNKNOWN_VERSION || apiVersionInDevkit > apiVersionFromDownloadedJar) {
if (apiVersionInDevkit > apiVersionFromDownloadedJar) {
DevKitWorkspaceModelBundle.message("notification.workspace.incompatible.codegen.api.versions.content.newer",
apiVersionInDevkit,
apiVersionFromDownloadedJar)
@@ -245,7 +277,7 @@ object CodeWriter {
return false
}
private fun getApiVersionFromJSON(clazz: Class<*>): String {
private fun getApiVersionFromJSON(clazz: Class<*>): ApiVersion? {
return getApiVersionFromJarFile(clazz, CodegenApiVersion.JSON_RELATIVE_PATH) { jsonPath ->
URI(jsonPath).toURL().openStream().reader().use { reader ->
val jsonReader = JsonReaderEx(reader.readText())
@@ -255,7 +287,7 @@ object CodeWriter {
}
}
private fun getApiVersionFromManifest(clazz: Class<*>): String {
private fun getApiVersionFromManifest(clazz: Class<*>): ApiVersion? {
return getApiVersionFromJarFile(clazz, CodegenApiVersion.MANIFEST_RELATIVE_PATH) { manifestPath ->
URI(manifestPath).toURL().openStream().use {
val manifest = Manifest(it)
@@ -265,23 +297,34 @@ object CodeWriter {
}
}
private fun getApiVersionFromJarFile(clazz: Class<*>, relativePathToFile: String, readApiVersionFromFile: (String) -> String?): String {
private fun getApiVersionFromJarFile(
clazz: Class<*>,
relativePathToFile: String,
readApiVersionFromFile: (String) -> String?,
): ApiVersion? {
val classPath = "${clazz.name.replace(".", "/")}.class"
val classAbsolutePath = clazz.getResource("${clazz.simpleName}.class")?.toString() // Absolute path is jar path + class path
?: error("Absolute path for the class $clazz was not found")
if (classAbsolutePath == null) {
LOG.warn("Failed to read codegen-api version: Absolute path for the class $clazz was not found")
return null
}
val fileAbsolutePath = classAbsolutePath.replace(classPath, relativePathToFile)
val apiVersion: String?
var apiVersion: ApiVersion? = null
try {
apiVersion = readApiVersionFromFile(fileAbsolutePath)
val stringVersion = readApiVersionFromFile(fileAbsolutePath)
if (stringVersion != null) {
val (major, minor, patch) = stringVersion.split(".", "-").take(3).map { it.toInt() }
apiVersion = ApiVersion(major, minor, patch)
}
}
catch (e: IOException) {
LOG.info("Failed to read codegen-api version from file \"$fileAbsolutePath\": " + e.message)
return CodegenApiVersion.UNKNOWN_VERSION
catch (e: Exception) {
LOG.warn("Failed to read codegen-api version from file \"$fileAbsolutePath\": " + e.message)
return null
}
return apiVersion ?: CodegenApiVersion.UNKNOWN_VERSION
return apiVersion
}
private fun generate(
@@ -480,8 +523,6 @@ object CodeWriter {
const val MANIFEST_RELATIVE_PATH = "META-INF/MANIFEST.MF"
const val ATTRIBUTE_NAME = "Codegen-Api-Version"
const val UNKNOWN_VERSION = "unknown version"
}
}