Skip to content

Commit

Permalink
feat(mps-model-adapters): support for MPS 2020.3
Browse files Browse the repository at this point in the history
  • Loading branch information
slisson committed Jan 28, 2025
1 parent 50d7b65 commit 9e302a5
Show file tree
Hide file tree
Showing 15 changed files with 54 additions and 61 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/mps-compatibility.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ jobs:
strategy:
matrix:
version:
- "2020.3"
- "2021.1"
- "2021.2"
- "2021.3"
Expand All @@ -42,6 +43,7 @@ jobs:
run: >-
./gradlew --build-cache
:bulk-model-sync-mps:build
:bulk-model-sync-lib:mps-test:build
:metamodel-export:build
:mps-model-adapters:build
:mps-model-adapters-plugin:build
Expand Down
13 changes: 7 additions & 6 deletions build-logic/src/main/kotlin/org/modelix/CopyMps.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import java.io.File
import java.util.zip.ZipInputStream

val Project.mpsMajorVersion: String get() {
if (project != rootProject) return rootProject.mpsVersion
if (project != rootProject) return rootProject.mpsMajorVersion
return project.findProperty("mps.version.major")?.toString()?.takeIf { it.isNotEmpty() }
?: project.findProperty("mps.version")?.toString()?.takeIf { it.isNotEmpty() }?.replace(Regex("""(20\d\d\.\d+).*"""), "$1")
?: "2021.1"
Expand All @@ -25,11 +25,12 @@ val Project.mpsVersion: String get() {
"2021.1" to "2021.1.4",
"2021.2" to "2021.2.6",
"2021.3" to "2021.3.5",
"2022.2" to "2022.2.3",
"2022.3" to "2022.3.1",
"2023.2" to "2023.2",
"2023.3" to "2023.3",
"2024.1" to "2024.1-EAP1",
"2022.2" to "2022.2.4",
"2022.3" to "2022.3.3",
"2023.2" to "2023.2.2",
"2023.3" to "2023.3.2",
"2024.1" to "2024.1.1",
"2024.3" to "2024.3",
)[it],
) { "Unknown MPS version: $it" }
}
Expand Down
6 changes: 6 additions & 0 deletions bulk-model-sync-lib/mps-test/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import org.modelix.copyMps
import org.modelix.mpsMajorVersion

plugins {
`modelix-kotlin-jvm`
Expand All @@ -20,6 +21,7 @@ dependencies {
testImplementation(libs.kotlin.serialization.json)
testImplementation(libs.xmlunit.matchers)
testImplementation(libs.jimfs)
testImplementation(libs.modelix.mpsApi)
}

intellij {
Expand All @@ -34,4 +36,8 @@ tasks {
buildSearchableOptions {
enabled = false
}

test {
onlyIf { mpsMajorVersion != "2020.3" } // incompatible with the intellij plugin
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@ import com.intellij.openapi.project.ex.ProjectManagerEx
import com.intellij.openapi.util.Disposer
import com.intellij.testFramework.TestApplicationManager
import com.intellij.testFramework.UsefulTestCase
import com.intellij.util.io.delete
import jetbrains.mps.ide.ThreadUtils
import jetbrains.mps.ide.project.ProjectHelper
import jetbrains.mps.project.AbstractModule
import jetbrains.mps.project.MPSProject
import jetbrains.mps.smodel.Language
import jetbrains.mps.smodel.MPSModuleRepository
import org.jetbrains.mps.openapi.model.EditableSModel
import org.jetbrains.mps.openapi.model.SaveOptions
import org.modelix.model.api.PBranch
import org.modelix.model.api.getRootNode
import org.modelix.model.client.IdGenerator
Expand All @@ -25,21 +25,20 @@ import org.modelix.model.data.asData
import org.modelix.model.lazy.CLTree
import org.modelix.model.lazy.CLVersion
import org.modelix.model.lazy.ObjectStoreCache
import org.modelix.model.mpsadapters.MPSContextProject
import org.modelix.model.mpsadapters.asReadableNode
import org.modelix.model.mpsadapters.asWritableNode
import org.modelix.model.persistent.MapBaseStore
import org.modelix.model.sync.bulk.ModelSynchronizer
import org.modelix.model.sync.bulk.NodeAssociationFromModelServer
import org.modelix.model.sync.bulk.NodeAssociationToModelServer
import org.modelix.mps.api.ModelixMpsApi
import org.modelix.mps.model.sync.bulk.MPSProjectSyncFilter
import org.w3c.dom.Element
import java.io.File
import java.nio.file.Files
import java.nio.file.Path
import kotlin.io.path.ExperimentalPathApi
import kotlin.io.path.absolute
import kotlin.io.path.deleteRecursively

class RecreateProjectFromModelServerTest : UsefulTestCase() {

Expand Down Expand Up @@ -114,7 +113,7 @@ class RecreateProjectFromModelServerTest : UsefulTestCase() {
targetRoot = mpsRoot,
nodeAssociation = NodeAssociationFromModelServer(branch, mpsRoot.getModel()),
)
MPSContextProject.contextValue.computeWith(mpsProject) {
ModelixMpsApi.runWithProject(mpsProject) {
modelSynchronizer.synchronize()
}
}
Expand Down Expand Up @@ -145,7 +144,7 @@ class RecreateProjectFromModelServerTest : UsefulTestCase() {
val projectDirParent = Path.of("build", "test-projects").absolute()
projectDirParent.toFile().mkdirs()
val projectDir = Files.createTempDirectory(projectDirParent, "mps-project")
projectDir.deleteRecursively()
projectDir.delete(recursively = true)
projectDir.toFile().mkdirs()
projectDir.toFile().deleteOnExit()
val options = OpenProjectTask().withProjectName("test-project")
Expand Down Expand Up @@ -223,7 +222,7 @@ private fun Project.captureFileContents(): Map<String, String> {
module as AbstractModule

Check warning

Code scanning / detekt

Use separate null assertion and type cast like ('(module ?: error("null assertion message")) as AbstractModule') instead of 'module as AbstractModule'. Warning test

Use separate null assertion and type cast like ('(module ?: error("null assertion message")) as AbstractModule') instead of 'module as AbstractModule'.
module.save()
for (model in module.models.filterIsInstance<EditableSModel>()) {
model.save(SaveOptions.FORCE)
ModelixMpsApi.forceSave(model)
}
}
}
Expand Down
1 change: 1 addition & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ dokka-base = { group = "org.jetbrains.dokka", name = "dokka-base", version.ref="

modelix-buildtools-gradle = { group = "org.modelix.mps", name = "build-tools-gradle", version.ref = "modelixBuildtools"}
modelix-buildtools-lib = { group = "org.modelix.mps", name = "build-tools-lib", version.ref = "modelixBuildtools"}
modelix-mpsApi = { group = "org.modelix.mps", name = "stable-api", version = "1.1.0" }

micrometer-registry-prometheus = { group = "io.micrometer", name = "micrometer-registry-prometheus", version.ref = "micrometer"}

Expand Down
6 changes: 6 additions & 0 deletions mps-model-adapters-plugin/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import org.modelix.copyMps
import org.modelix.mpsMajorVersion

plugins {
`modelix-kotlin-jvm`
Expand All @@ -14,6 +15,7 @@ dependencies {
exclude(group = "org.jetbrains.kotlinx", module = "kotlinx-coroutines-core")
exclude(group = "org.jetbrains.kotlinx", module = "kotlinx-coroutines-jdk8")
}
testImplementation(kotlin("test"))
}

intellij {
Expand All @@ -35,6 +37,10 @@ tasks {
autoReloadPlugins.set(true)
}

test {
onlyIf { mpsMajorVersion != "2020.3" } // incompatible with the intellij plugin
}

val mpsPluginDir = project.findProperty("mps.plugins.dir")?.toString()?.let { file(it) }
if (mpsPluginDir != null && mpsPluginDir.isDirectory) {
create<Sync>("installMpsPlugin") {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import org.modelix.model.api.BuiltinLanguages
import org.modelix.model.api.ConceptReference
import org.modelix.model.api.INode
import org.modelix.model.api.IReplaceableNode
import kotlin.test.assertFailsWith

class ReplaceNodeTest : MpsAdaptersTestBase("SimpleProject") {

Expand Down Expand Up @@ -86,23 +87,23 @@ class ReplaceNodeTest : MpsAdaptersTestBase("SimpleProject") {
assertEquals(newConcept, newNode.getConceptReference())
}

fun `test fail to replace node with null concept`() = runCommandOnEDT {
fun `test fail to replace node with null concept`(): Unit = runCommandOnEDT {
val rootNode = getRootUnderTest()
val nodeToReplace = rootNode.allChildren.first() as IReplaceableNode

val expectedMessage = "Cannot replace node `method1` with a null concept. Explicitly specify a concept (e.g., `BaseConcept`)."
assertThrows(IllegalArgumentException::class.java, expectedMessage) {
assertFailsWith(IllegalArgumentException::class, expectedMessage) {
nodeToReplace.replaceNode(null)
}
}

fun `test fail to replace node with non mps concept`() = runCommandOnEDT {
fun `test fail to replace node with non mps concept`(): Unit = runCommandOnEDT {
val rootNode = getRootUnderTest()
val nodeToReplace = rootNode.allChildren.first() as IReplaceableNode
val newConcept = ConceptReference("notMpsConcept")

val expectedMessage = "Concept UID `notMpsConcept` cannot be parsed as MPS concept."
assertThrows(IllegalArgumentException::class.java, expectedMessage) {
assertFailsWith(IllegalArgumentException::class, expectedMessage) {
nodeToReplace.replaceNode(newConcept)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<project version="4">
<component name="MPSProject">
<projectModules>
<modulePath path="$PROJECT_DIR$/solutions/Solution1/Solution1.msd" folder="" />
<modulePath path="$PROJECT_DIR$/solutions/Solution1/Solution1.msd" folder="myFolder" />
</projectModules>
</component>
</project>
1 change: 1 addition & 0 deletions mps-model-adapters/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ dependencies {
implementation(libs.trove4j)
implementation(kotlin("stdlib"))
implementation(libs.kotlin.logging)
implementation(libs.modelix.mpsApi)
}

group = "org.modelix.mps"
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@ package org.modelix.model.mpsadapters
import jetbrains.mps.persistence.MementoImpl
import jetbrains.mps.project.AbstractModule
import jetbrains.mps.project.DevKit
import jetbrains.mps.project.MPSProject
import jetbrains.mps.project.ModuleId
import jetbrains.mps.project.ProjectBase
import jetbrains.mps.project.ProjectManager
import jetbrains.mps.project.Solution
import jetbrains.mps.project.facets.JavaModuleFacet
import jetbrains.mps.smodel.Generator
Expand All @@ -30,6 +29,7 @@ import org.modelix.model.api.IReadableNode
import org.modelix.model.api.IReferenceLinkReference
import org.modelix.model.api.IWritableNode
import org.modelix.model.data.asData
import org.modelix.mps.api.ModelixMpsApi
import java.util.UUID

fun MPSModuleAsNode(module: SModule) = MPSModuleAsNode.create(module)

Check warning

Code scanning / detekt

The function MPSModuleAsNode is missing documentation. Warning

The function MPSModuleAsNode is missing documentation.

Check warning

Code scanning / detekt

Function names should match the pattern: [a-z][a-zA-Z0-9]* Warning

Function names should match the pattern: [a-z][a-zA-Z0-9]*
Expand All @@ -55,16 +55,11 @@ abstract class MPSModuleAsNode<E : SModule> : MPSGenericNodeAdapter<E>() {
},
BuiltinLanguages.jetbrains_mps_lang_core.BaseConcept.virtualPackage.toReference() to object : IPropertyAccessor<SModule> {
override fun read(element: SModule): String? {
return ProjectManager.getInstance().openedProjects.asSequence()
.filterIsInstance<ProjectBase>()
.mapNotNull { it.getPath(element) }
.firstOrNull()
?.virtualFolder
?.takeIf { it.isNotEmpty() }
return ModelixMpsApi.getVirtualFolder(element)
}

override fun write(element: SModule, value: String?) {
MPSContextProject.contextValue.getValue().setVirtualFolder(element, value ?: "")
ModelixMpsApi.setVirtualFolder(element, value)
}
},
BuiltinLanguages.MPSRepositoryConcepts.Module.id.toReference() to object : IPropertyAccessor<SModule> {
Expand Down Expand Up @@ -318,7 +313,7 @@ data class MPSLanguageAsNode(override val module: Language) : MPSModuleAsNode<La
index: Int,
sourceNode: SpecWithResolvedConcept,
): IWritableNode {
return GeneratorProducer(MPSContextProject.contextValue.getValue()).create(
return GeneratorProducer(ModelixMpsApi.getMPSProjects().first() as MPSProject).create(
element,
sourceNode.getNode().getPropertyValue(BuiltinLanguages.jetbrains_mps_lang_core.INamedConcept.name.toReference())!!,

Check warning

Code scanning / detekt

Calling !! on a nullable type will throw a NullPointerException at runtime in case the value is null. It should be avoided. Warning

Calling !! on a nullable type will throw a NullPointerException at runtime in case the value is null. It should be avoided.
sourceNode.getNode().getPropertyValue(BuiltinLanguages.MPSRepositoryConcepts.Module.id.toReference())!!.let { ModuleId.fromString(it) },

Check warning

Code scanning / detekt

Calling !! on a nullable type will throw a NullPointerException at runtime in case the value is null. It should be avoided. Warning

Calling !! on a nullable type will throw a NullPointerException at runtime in case the value is null. It should be avoided.
Expand All @@ -342,6 +337,7 @@ data class MPSDevkitAsNode(override val module: DevKit) : MPSModuleAsNode<DevKit
val originalRef = requireNotNull(refNode.getReferenceTargetRef(BuiltinLanguages.MPSRepositoryConcepts.ModuleReference.module.toReference())) {
"Reference to module is not set: ${refNode.asLegacyNode().asData().toJson()}"
}
@Suppress("removal")
MPSArea(contextModule.repository ?: MPSModuleRepository.getInstance()).resolveNode(originalRef)?.asWritableNode()
}
checkNotNull(moduleNode)
Expand Down Expand Up @@ -425,8 +421,9 @@ private fun <T : SModel> Iterable<T>.withoutDescriptorModel(): List<T> {

private fun SModule.getCompileInMPS(): Boolean {
val module = this
if (module is DevKit || module !is AbstractModule) {
if (module !is Solution) {
return false
}
return module.moduleDescriptor?.compileInMPS ?: false
@Suppress("removal")
return module.moduleDescriptor.compileInMPS
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import org.modelix.model.api.INodeReference
import org.modelix.model.api.IProperty
import org.modelix.model.api.IReferenceLink
import org.modelix.model.area.IArea
import org.modelix.mps.api.ModelixMpsApi

data class MPSProjectModuleAsNode(val project: ProjectBase, val module: SModule) : IDefaultNodeAdapter {

Expand Down Expand Up @@ -39,7 +40,7 @@ data class MPSProjectModuleAsNode(val project: ProjectBase, val module: SModule)

override fun getPropertyValue(property: IProperty): String? {
return if (property.conformsTo(BuiltinLanguages.MPSRepositoryConcepts.ProjectModule.virtualFolder)) {
project.getPath(module)?.virtualFolder
ModelixMpsApi.getVirtualFolders(module).firstOrNull()
} else {
null
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package org.modelix.model.mpsadapters

import jetbrains.mps.project.MPSProject
import jetbrains.mps.project.ModuleId
import jetbrains.mps.project.ProjectBase
import jetbrains.mps.project.ProjectManager
import jetbrains.mps.smodel.Generator
import jetbrains.mps.smodel.tempmodel.TempModule
import jetbrains.mps.smodel.tempmodel.TempModule2
Expand All @@ -18,6 +18,7 @@ import org.modelix.model.api.IReadableNode
import org.modelix.model.api.IReferenceLinkReference
import org.modelix.model.api.IWritableNode
import org.modelix.model.api.NullChildLinkReference
import org.modelix.mps.api.ModelixMpsApi

fun SRepository.asLegacyNode(): INode = MPSRepositoryAsNode(this).asLegacyNode()
fun SRepository.asWritableNode(): IWritableNode = MPSRepositoryAsNode(this)
Expand All @@ -41,19 +42,19 @@ data class MPSRepositoryAsNode(@get:JvmName("getRepository_") val repository: SR
): IWritableNode {
return when (sourceNode.getConceptReference()) {
BuiltinLanguages.MPSRepositoryConcepts.Solution.getReference() -> {
SolutionProducer(MPSContextProject.contextValue.getValue()).create(
SolutionProducer(ModelixMpsApi.getMPSProject() as MPSProject).create(
sourceNode.getNode().getPropertyValue(BuiltinLanguages.jetbrains_mps_lang_core.INamedConcept.name.toReference())!!,
sourceNode.getNode().getPropertyValue(BuiltinLanguages.MPSRepositoryConcepts.Module.id.toReference())!!.let { ModuleId.fromString(it) },
).let { MPSModuleAsNode(it) }
}
BuiltinLanguages.MPSRepositoryConcepts.Language.getReference() -> {
LanguageProducer(MPSContextProject.contextValue.getValue()).create(
LanguageProducer(ModelixMpsApi.getMPSProject() as MPSProject).create(
sourceNode.getNode().getPropertyValue(BuiltinLanguages.jetbrains_mps_lang_core.INamedConcept.name.toReference())!!,
sourceNode.getNode().getPropertyValue(BuiltinLanguages.MPSRepositoryConcepts.Module.id.toReference())!!.let { ModuleId.fromString(it) },
).let { MPSModuleAsNode(it) }
}
BuiltinLanguages.MPSRepositoryConcepts.DevKit.getReference() -> {
DevkitProducer(MPSContextProject.contextValue.getValue()).create(
DevkitProducer(ModelixMpsApi.getMPSProject() as MPSProject).create(
sourceNode.getNode().getPropertyValue(BuiltinLanguages.jetbrains_mps_lang_core.INamedConcept.name.toReference())!!,
sourceNode.getNode().getPropertyValue(BuiltinLanguages.MPSRepositoryConcepts.Module.id.toReference())!!.let { ModuleId.fromString(it) },
).let { MPSModuleAsNode(it) }
Expand All @@ -69,10 +70,8 @@ data class MPSRepositoryAsNode(@get:JvmName("getRepository_") val repository: SR
},
BuiltinLanguages.MPSRepositoryConcepts.Repository.projects.toReference() to object : IChildAccessor<SRepository> {
override fun read(element: SRepository): List<IWritableNode> {
return ProjectManager.getInstance().openedProjects
.filterIsInstance<ProjectBase>()
.plus(listOfNotNull(MPSContextProject.contextValue.getValueOrNull()))
.map { MPSProjectAsNode(it).asWritableNode() }
return ModelixMpsApi.getMPSProjects()
.map { MPSProjectAsNode(it as ProjectBase).asWritableNode() }
}
},
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import org.modelix.model.api.ISyncTargetNode
import org.modelix.model.api.IWritableNode
import org.modelix.model.api.NewNodeSpec
import org.modelix.model.api.meta.NullConcept
import org.modelix.mps.api.ModelixMpsApi

data class MPSWritableNode(val node: SNode) : IWritableNode, ISyncTargetNode {
override fun getModel(): IMutableModel {
Expand Down Expand Up @@ -97,7 +98,7 @@ data class MPSWritableNode(val node: SNode) : IWritableNode, ISyncTargetNode {
}

node.properties.forEach { newNode.setProperty(it, node.getProperty(it)) }
node.references.forEach { newNode.setReference(it.link, it.targetNodeReference) }
node.references.forEach { ModelixMpsApi.setReference(newNode, it.link, it.targetNodeReference) }
node.children.forEach { child ->
val link = checkNotNull(child.containmentLink) { "Containment link of child node not found" }
node.removeChild(child)
Expand Down
Loading

0 comments on commit 9e302a5

Please sign in to comment.