Skip to content

Commit 3a4c1d9

Browse files
committed
feat(mps-sync-plugin): readonly bindings will have readonly modules (like stubs)
1 parent 760e30f commit 3a4c1d9

File tree

7 files changed

+63
-20
lines changed

7 files changed

+63
-20
lines changed

model-api/src/commonMain/kotlin/org/modelix/model/api/BuiltinLanguages.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,11 @@ object BuiltinLanguages {
146146
uid = "0a7577d1-d4e5-431d-98b1-fae38f9aee80/474657388638618895/2206727074858242373",
147147
).also(this::addProperty)
148148

149+
val readonlyStubModule = SimpleProperty(
150+
"readonlyStubModule",
151+
uid = "0a7577d1-d4e5-431d-98b1-fae38f9aee80/474657388638618895/4225291355523310000",
152+
).also(this::addProperty)
153+
149154
val models = SimpleChildLink(
150155
simpleName = "models",
151156
isMultiple = true,

mps-model-adapters/src/main/kotlin/org/modelix/model/mpsadapters/MPSRepositoryAsNode.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ data class MPSRepositoryAsNode(@get:JvmName("getRepository_") val repository: SR
6666
requireNotNull(sourceNode.getNode().getPropertyValue(BuiltinLanguages.MPSRepositoryConcepts.Module.id.toReference())) {
6767
"Solution has no ID: ${sourceNode.getNode()}"
6868
}.let { ModuleId.fromString(it) },
69+
sourceNode.getNode().getPropertyValue(BuiltinLanguages.MPSRepositoryConcepts.Module.readonlyStubModule.toReference()).toBoolean(),
6970
).let { MPSModuleAsNode(it) }
7071
}
7172
BuiltinLanguages.MPSRepositoryConcepts.Language.getReference() -> {

mps-model-adapters/src/main/kotlin/org/modelix/model/mpsadapters/SolutionProducer.kt

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,23 +20,23 @@ import jetbrains.mps.vfs.IFile
2020

2121
class SolutionProducer(private val myProject: IMPSProject) {
2222

23-
fun create(name: String, id: ModuleId): Solution {
23+
fun create(name: String, id: ModuleId, readOnly: Boolean = false): Solution {
2424
val basePath = checkNotNull(myProject.getBasePath()) { "Project has no base path: $myProject" }
2525
val projectBaseDir = myProject.getFileSystem().getFile(basePath)
2626
val solutionBaseDir = projectBaseDir.findChild("solutions").findChild(name)
27-
return create(name, id, solutionBaseDir)
27+
return create(name, id, solutionBaseDir, readOnly)
2828
}
2929

30-
fun create(namespace: String, id: ModuleId, moduleDir: IFile): Solution {
30+
fun create(namespace: String, id: ModuleId, moduleDir: IFile, readOnly: Boolean): Solution {
3131
val descriptorFile = moduleDir.findChild(namespace + MPSExtentions.DOT_SOLUTION)
32-
val descriptor: SolutionDescriptor = createSolutionDescriptor(namespace, id, descriptorFile)
32+
val descriptor: SolutionDescriptor = createSolutionDescriptor(namespace, id, descriptorFile, readOnly)
3333
val module = GeneralModuleFactory().instantiate(descriptor, descriptorFile) as Solution
3434
myProject.addModule(module)
3535
module.save()
3636
return module
3737
}
3838

39-
private fun createSolutionDescriptor(namespace: String, id: ModuleId, descriptorFile: IFile): SolutionDescriptor {
39+
private fun createSolutionDescriptor(namespace: String, id: ModuleId, descriptorFile: IFile, readOnly: Boolean): SolutionDescriptor {
4040
val descriptor = SolutionDescriptor()
4141
// using outputPath instead of outputRoot for backwards compatibility
4242
// descriptor.outputRoot = "\${module}/source_gen"
@@ -53,6 +53,7 @@ class SolutionProducer(private val myProject: IMPSProject) {
5353

5454
descriptor.modelRootDescriptors.add(DefaultModelRoot.createDescriptor(modelsDir.parent!!, modelsDir))
5555
descriptor.outputPath = descriptorFile.parent!!.findChild("source_gen").path
56+
descriptor.readOnlyStubModule(readOnly)
5657
return descriptor
5758
}
5859
}

mps-sync-plugin3/src/main/kotlin/org/modelix/mps/sync3/BindingWorker.kt

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,10 @@ class BindingWorker(
154154

155155
private inline fun <R> forEachTarget(body: SyncTarget.() -> R): List<R> = syncTargets.map { body(it) }
156156
private inline fun <R> forEachTargetIndexed(body: SyncTarget.(index: Int) -> R): List<R> = syncTargets.withIndex().map { body(it.value, it.index) }
157+
private inline fun forEachTargetIndexedMaybeReadonly(body: SyncTarget.(index: Int) -> IVersion): List<NewVersionSpec> =
158+
forEachTargetIndexed { index ->
159+
NewVersionSpec(body(this, index), bindingId.readonly)
160+
}
157161

158162
private suspend fun checkInSync(): String? {
159163
check(activated.get()) { "Binding is deactivated" }
@@ -268,7 +272,7 @@ class BindingWorker(
268272
// Binding was never activated before. Overwrite local changes or do initial upload.
269273

270274
val existingRemoteVersions = forEachTargetIndexed { client().pullIfExists(branchRef) }
271-
val createdRemoteVersions = forEachTargetIndexed { index ->
275+
val createdRemoteVersions = forEachTargetIndexedMaybeReadonly { index ->
272276
existingRemoteVersions[index] ?: client().initRepository(branchRef.repositoryId)
273277
}
274278

@@ -291,7 +295,7 @@ class BindingWorker(
291295
// push local changes that happened while the binding was deactivated
292296
val localChanges = doSyncFromMPS(createdBaseVersions, incremental = false)
293297

294-
val mergedVersions = forEachTargetIndexed { index ->
298+
val mergedVersions = forEachTargetIndexedMaybeReadonly { index ->
295299
val baseVersion = createdBaseVersions[index]
296300
val localChange = localChanges?.get(index)?.takeIf { it != baseVersion }
297301
if (localChange != null) {
@@ -311,7 +315,7 @@ class BindingWorker(
311315

312316
suspend fun syncToMPS(incremental: Boolean): List<IVersion> {
313317
return runSync { oldVersions ->
314-
val newVersions = forEachTargetIndexed { index ->
318+
val newVersions = forEachTargetIndexedMaybeReadonly { index ->
315319
val oldVersion = oldVersions?.get(index)
316320
client().pull(branchRef, oldVersion)
317321
}
@@ -332,13 +336,15 @@ class BindingWorker(
332336
}
333337
}
334338

335-
private suspend fun doSyncToMPS(oldVersions: List<IVersion?>, newVersions: List<IVersion>, incremental: Boolean) {
339+
data class NewVersionSpec(val version: IVersion, val readonly: Boolean) : IVersion by version
340+
341+
private suspend fun doSyncToMPS(oldVersions: List<IVersion?>, newVersions: List<NewVersionSpec>, incremental: Boolean) {
336342
if (oldVersions.zip(newVersions).all { it.first?.getContentHash() == it.second.getContentHash() }) return
337343

338344
LOG.debug { "Updating MPS project from $oldVersions to $newVersions" }
339345

340346
val newTrees = newVersions.map { it.getModelTree() }
341-
val sourceModel = SyncTargetModel(newTrees.map { it.asModelSingleThreaded() })
347+
val sourceModel = SyncTargetModel(newVersions.map { MaybeReadonlyIModel(it.getModelTree().asModelSingleThreaded(), it.readonly) })
342348
val baseVersions = oldVersions
343349
val filter = if (baseVersions.all { it != null } && incremental) {
344350
val invalidationTree = DefaultInvalidationTree(sourceModel.getRootNode().getNodeReference(), 100_000)
@@ -446,7 +452,7 @@ class BindingWorker(
446452
fun sync(invalidationTree: ModelSynchronizer.IIncrementalUpdateInformation): List<IVersion>? {
447453
val idGenerator = DummyIdGenerator<INodeReference>()
448454
val versionedTrees = oldVersions.map { VersionedModelTree(it, idGenerator) }
449-
val model = SyncTargetModel(versionedTrees.map { it.asModel() })
455+
val model = SyncTargetModel(versionedTrees.map { MaybeReadonlyIModel(it.asModel(), false) })
450456
model.executeWrite {
451457
val targetRoot = model.getRootNode()
452458
MPSProjectAsNode.runWithProject(mpsProject) {

mps-sync-plugin3/src/main/kotlin/org/modelix/mps/sync3/ModelSyncService.kt

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,7 @@ class ModelSyncService(val project: Project) :
236236
it.children.add(Element("repository").also { it.text = bindingEntry.key.branchRef.repositoryId.id })
237237
it.children.add(Element("branch").also { it.text = bindingEntry.key.branchRef.branchName })
238238
it.children.add(Element("versionHash").also { it.text = bindingEntry.value.versionHash })
239+
it.children.add(Element("readonly").also { it.text = bindingEntry.key.readonly.toString() })
239240
}
240241
},
241242
)
@@ -256,6 +257,7 @@ class ModelSyncService(val project: Project) :
256257
repositoryId,
257258
element.getChild("branch")?.text ?: return@mapNotNull null,
258259
),
260+
readonly = element.getChild("readonly")?.text.toBoolean(),
259261
) to BindingState(
260262
versionHash = element.getChild("versionHash")?.text,
261263
enabled = element.getChild("enabled")?.text.toBoolean(),
@@ -462,9 +464,10 @@ suspend fun jobLoop(
462464
}
463465
}
464466

465-
data class BindingId(val connectionProperties: ModelServerConnectionProperties, val branchRef: BranchReference) {
467+
data class BindingId(val connectionProperties: ModelServerConnectionProperties, val branchRef: BranchReference, val readonly: Boolean = false) {
466468
override fun toString(): String {
467-
return "BindingId($connectionProperties, ${branchRef.repositoryId}, ${branchRef.branchName})"
469+
val readonlyStr = if (readonly) " readonly" else ""
470+
return "BindingId($connectionProperties, ${branchRef.repositoryId}, ${branchRef.branchName}$readonlyStr)"
468471
}
469472
}
470473

mps-sync-plugin3/src/main/kotlin/org/modelix/mps/sync3/SyncTargetModel.kt

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ import org.modelix.mps.multiplatform.model.MPSModuleReference
2020
import org.modelix.mps.multiplatform.model.MPSProjectModuleReference
2121
import org.modelix.mps.multiplatform.model.MPSProjectReference
2222

23-
class SyncTargetModel(val models: List<IMutableModel>) : IMutableModel {
23+
data class MaybeReadonlyIModel(val model: IMutableModel, val readonly: Boolean) : IMutableModel by model
24+
class SyncTargetModel(val models: List<MaybeReadonlyIModel>) : IMutableModel {
2425
private val repositoryNode: RepositoryWrapper = RepositoryWrapper()
2526

2627
override fun getRootNode(): IWritableNode = repositoryNode
@@ -74,9 +75,11 @@ class SyncTargetModel(val models: List<IMutableModel>) : IMutableModel {
7475
}
7576

7677
fun getMPSModules(): List<IWritableNode> {
77-
return getRepositories()
78-
.flatMap { it.getChildren(modulesRole) }
79-
.distinctBy { it.getNodeReference() }
78+
return models.flatMap { model ->
79+
NodeWrapper(model, model.getRootNode())
80+
.getChildren(modulesRole)
81+
.map { ModuleWrapper(model, it, model.readonly) }
82+
}.distinctBy { it.getNodeReference() }
8083
}
8184

8285
fun getMPSProjects(): List<IWritableNode> {
@@ -467,7 +470,7 @@ class SyncTargetModel(val models: List<IMutableModel>) : IMutableModel {
467470

468471
private fun IWritableNode.unwrap() = if (this is NodeWrapper) this.node else this
469472

470-
inner class NodeWrapper(private val model: IMutableModel, val node: IWritableNode) : IWritableNode by node, ISyncTargetNode {
473+
open inner class NodeWrapper(private val model: IMutableModel, val node: IWritableNode) : IWritableNode by node, ISyncTargetNode {
471474
private fun IWritableNode.wrap() = NodeWrapper(model, this)
472475
private fun Iterable<IWritableNode>.wrap() = map { it.wrap() }
473476

@@ -559,6 +562,24 @@ class SyncTargetModel(val models: List<IMutableModel>) : IMutableModel {
559562
}
560563
}
561564

565+
inner class ModuleWrapper(model: IMutableModel, node: IWritableNode, val readonly: Boolean) : NodeWrapper(model, node) {
566+
override fun getPropertyValue(property: IPropertyReference): String? {
567+
if (property.matches(BuiltinLanguages.MPSRepositoryConcepts.Module.readonlyStubModule.toReference())) {
568+
return readonly.toString()
569+
} else {
570+
return super.getPropertyValue(property)
571+
}
572+
}
573+
574+
override fun setPropertyValue(property: IPropertyReference, value: String?) {
575+
if (property.matches(BuiltinLanguages.MPSRepositoryConcepts.Module.readonlyStubModule.toReference())) {
576+
return // not supported
577+
} else {
578+
return super.setPropertyValue(property, value)
579+
}
580+
}
581+
}
582+
562583
abstract inner class WrapperBase : IWritableNode, ISyncTargetNode {
563584
abstract fun delegates(): Sequence<IWritableNode>
564585

mps-sync-plugin3/src/test/kotlin/org/modelix/mps/sync3/MultipleBindingsTest.kt

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ class MultipleBindingsTest : ProjectSyncTestBase() {
9595
<url>http://localhost:$port</url>
9696
<repository>${branchRefLib.repositoryId.id}</repository>
9797
<branch>${branchRefLib.branchName}</branch>
98+
<readonly>true</readonly>
9899
</binding>
99100
</component>
100101
</project>
@@ -114,8 +115,13 @@ class MultipleBindingsTest : ProjectSyncTestBase() {
114115
"module4",
115116
)
116117
assertEquals(
117-
setOf("module1", "module2", "module3", "module4"),
118-
readAction { mpsProject.projectModules.map { it.moduleName }.toSet() },
118+
setOf(
119+
"module1" to false,
120+
"module2" to false,
121+
"module3" to true,
122+
"module4" to true,
123+
),
124+
readAction { mpsProject.projectModules.map { it.moduleName to it.isReadOnly }.toSet() },
119125
)
120126
}
121127
}

0 commit comments

Comments
 (0)