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
12 changes: 6 additions & 6 deletions .github/workflows/mps-compatibility.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@ jobs:
# after being introduced. It's not expected that an incompatibility only exists in some intermediate version and then
# becomes compatible again.
# - "2020.3.6" VersionFixer was replaced by ModuleDependencyVersions in 2021.1 (used by model-sync-plugin)
- "2021.1.4"
- "2021.2.6"
- "2021.3.5"
- "2022.2.2"
- "2022.3.1"
- "2021.1"
- "2021.2"
- "2021.3"
- "2022.2"
- "2022.3"
- "2023.2"

steps:
Expand All @@ -43,7 +43,7 @@ jobs:
run: >-
./gradlew --build-cache
build
-Pmps.version=${{ matrix.version }}
-Pmps.version.major=${{ matrix.version }}
- name: Archive test report
uses: actions/upload-artifact@v4
if: always()
Expand Down
57 changes: 2 additions & 55 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
import org.jetbrains.kotlin.gradle.plugin.KotlinMultiplatformPluginWrapper
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformJvmPlugin
import java.util.zip.ZipInputStream
import org.modelix.copyMps

buildscript {
dependencies {
Expand Down Expand Up @@ -147,57 +147,4 @@ fun MavenPublication.setMetadata() {
}
}

val mpsVersion = project.findProperty("mps.version")?.toString()?.takeIf { it.isNotEmpty() }
?: "2021.1.4".also { ext["mps.version"] = it }
val mpsPlatformVersion = mpsVersion.replace(Regex("""20(\d\d)\.(\d+).*"""), "$1$2").toInt()
ext["mps.platform.version"] = mpsPlatformVersion
println("Building for MPS version $mpsVersion")

// Extract MPS during configuration phase, because using it in intellij.localPath requires it to already exist.
val mpsHome = project.layout.buildDirectory.dir("mps-$mpsVersion")
val mpsZip by configurations.creating
dependencies { mpsZip("com.jetbrains:mps:$mpsVersion") }
mpsHome.get().asFile.let { baseDir ->
if (baseDir.exists()) return@let // content of MPS zip is not expected to change

println("Extracting MPS ...")
sync {
from(zipTree({ mpsZip.singleFile }))
into(mpsHome)
}

// The IntelliJ gradle plugin doesn't search in jar files when reading plugin descriptors, but the IDE does.
// Copy the XML files from the jars to the META-INF folders to fix that.
for (pluginFolder in (mpsHome.get().asFile.resolve("plugins").listFiles() ?: emptyArray())) {
val jars = (pluginFolder.resolve("lib").listFiles() ?: emptyArray()).filter { it.extension == "jar" }
for (jar in jars) {
jar.inputStream().use {
ZipInputStream(it).use { zip ->
val entries = generateSequence { zip.nextEntry }
for (entry in entries) {
if (entry.name.substringBefore("/") != "META-INF") continue
val outputFile = pluginFolder.resolve(entry.name)
if (outputFile.extension != "xml") continue
if (outputFile.exists()) {
println("already exists: $outputFile")
continue
}
outputFile.parentFile.mkdirs()
outputFile.writeBytes(zip.readAllBytes())
println("copied $outputFile")
}
}
}
}
}

// The build number of a local IDE is expected to contain a product code, otherwise an exception is thrown.
val buildTxt = mpsHome.get().asFile.resolve("build.txt")
val buildNumber = buildTxt.readText()
val prefix = "MPS-"
if (!buildNumber.startsWith(prefix)) {
buildTxt.writeText("$prefix$buildNumber")
}

println("Extracting MPS done.")
}
copyMps()
11 changes: 11 additions & 0 deletions buildSrc/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
plugins {
`kotlin-dsl`
}

dependencies {
implementation(kotlin("stdlib"))
}

repositories {
mavenCentral()
}
139 changes: 139 additions & 0 deletions buildSrc/src/main/kotlin/org/modelix/CopyMps.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
/*
* Copyright (c) 2024.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.modelix

import org.gradle.api.Project
import org.gradle.api.artifacts.ModuleDependency
import org.gradle.api.file.Directory
import org.gradle.api.provider.Provider
import org.gradle.kotlin.dsl.dependencies
import org.gradle.kotlin.dsl.exclude
import java.io.File
import java.util.zip.ZipInputStream

val Project.mpsMajorVersion: String get() {
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"
}

val Project.mpsVersion: String get() {
if (project != rootProject) return rootProject.mpsVersion
return project.findProperty("mps.version")?.toString()?.takeIf { it.isNotEmpty() }
?: mpsMajorVersion.let {
requireNotNull(
mapOf(
// https://artifacts.itemis.cloud/service/rest/repository/browse/maven-mps/com/jetbrains/mps/
"2020.3" to "2020.3.6",
"2021.1" to "2021.1.4",
"2021.2" to "2021.2.6",
"2021.3" to "2021.3.5",
"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" }
}
}

val Project.mpsPlatformVersion: Int get() {
return mpsVersion.replace(Regex("""20(\d\d)\.(\d+).*"""), "$1$2").toInt()
}

val Project.mpsJavaVersion: Int get() = if (mpsPlatformVersion >= 223) 17 else 11

val Project.mpsHomeDir: Provider<Directory> get() {
if (project != rootProject) return rootProject.mpsHomeDir
return project.layout.buildDirectory.dir("mps-$mpsVersion")
}

val Project.mpsPluginsDir: File? get() {
val candidates = listOfNotNull(
project.findProperty("mps$mpsPlatformVersion.plugins.dir")?.toString()?.let { file(it) },
System.getProperty("user.home")?.let { file(it).resolve("Library/Application Support/JetBrains/MPS$mpsMajorVersion/plugins/") },
)
return candidates.firstOrNull { it.isDirectory }
}

val excludeMPSLibraries: (ModuleDependency).() -> Unit = {
exclude("org.jetbrains.kotlinx", "kotlinx-coroutines-core")
exclude("org.jetbrains.kotlinx", "kotlinx-coroutines-jdk8")
exclude("org.jetbrains.kotlin", "kotlin-stdlib")
exclude("org.jetbrains.kotlin", "kotlin-stdlib-common")
exclude("org.jetbrains.kotlin", "kotlin-stdlib-jdk7")
exclude("org.jetbrains.kotlin", "kotlin-stdlib-jdk8")
exclude("org.jetbrains", "annotations")
}

fun Project.copyMps(): File {
if (project != rootProject) return rootProject.copyMps()

val mpsHome = mpsHomeDir.get().asFile
if (mpsHome.exists()) return mpsHome

println("Extracting MPS ...")

// Extract MPS during configuration phase, because using it in intellij.localPath requires it to already exist.
val mpsZip = configurations.create("mpsZip")
dependencies {
mpsZip("com.jetbrains:mps:$mpsVersion")
}
sync {
from(zipTree({ mpsZip.singleFile }))
into(mpsHomeDir)
}

// The IntelliJ gradle plugin doesn't search in jar files when reading plugin descriptors, but the IDE does.
// Copy the XML files from the jars to the META-INF folders to fix that.
for (pluginFolder in (mpsHomeDir.get().asFile.resolve("plugins").listFiles() ?: emptyArray())) {
val jars = (pluginFolder.resolve("lib").listFiles() ?: emptyArray()).filter { it.extension == "jar" }
for (jar in jars) {
jar.inputStream().use {
ZipInputStream(it).use { zip ->
val entries = generateSequence { zip.nextEntry }
for (entry in entries) {
if (entry.name.substringBefore("/") != "META-INF") continue
val outputFile = pluginFolder.resolve(entry.name)
if (outputFile.extension != "xml") continue
if (outputFile.exists()) {
println("already exists: $outputFile")
continue
}
outputFile.parentFile.mkdirs()
outputFile.writeBytes(zip.readAllBytes())
println("copied $outputFile")
}
}
}
}
}

// The build number of a local IDE is expected to contain a product code, otherwise an exception is thrown.
val buildTxt = mpsHomeDir.get().asFile.resolve("build.txt")
val buildNumber = buildTxt.readText()
val prefix = "MPS-"
if (!buildNumber.startsWith(prefix)) {
buildTxt.writeText("$prefix$buildNumber")
}

println("Extracting MPS done.")
return mpsHome
}
3 changes: 2 additions & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ modelix-model-api-gen = { id = "org.modelix.model-api-gen", version.ref = "model
npm-publish = { id = "dev.petuska.npm.publish", version = "3.5.2" }

[versions]
modelixCore = "11.1.3"
modelixCore = "11.2.1"
kotlinCoroutines="1.10.1"
ktor="3.0.3"

Expand All @@ -25,6 +25,7 @@ kotlin-logging = { group = "io.github.oshai", name = "kotlin-logging", version =
kotlin-logging-microutils = { group = "io.github.microutils", name = "kotlin-logging", version = "3.0.5" }
kotlin-html = "org.jetbrains.kotlinx:kotlinx-html:0.12.0"
kotlin-coroutines-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version.ref = "kotlinCoroutines" }
kotlin-coroutines-swing = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-swing", version.ref = "kotlinCoroutines" }

ktor-client-core = { group = "io.ktor", name = "ktor-client-core", version.ref = "ktor" }

Expand Down
62 changes: 16 additions & 46 deletions mps-diff-plugin/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.dsl.KotlinVersion
import java.util.zip.ZipInputStream
import org.modelix.excludeMPSLibraries
import org.modelix.mpsHomeDir
import org.modelix.mpsPlatformVersion

buildscript {
dependencies {
Expand All @@ -14,9 +16,6 @@ plugins {
}

group = "org.modelix.mps"
val mpsVersion = project.findProperty("mps.version").toString()
val mpsPlatformVersion = project.findProperty("mps.platform.version").toString().toInt()
val mpsHome = rootProject.layout.buildDirectory.dir("mps-$mpsVersion")

java {
sourceCompatibility = JavaVersion.VERSION_11
Expand Down Expand Up @@ -53,23 +52,16 @@ kotlin {
}

dependencies {
fun ModuleDependency.excludedBundledLibraries() {
exclude(group = "org.jetbrains.kotlin")
exclude(group = "org.jetbrains.kotlinx", module = "kotlinx-coroutines-core")
exclude(group = "org.jetbrains.kotlinx", module = "kotlinx-coroutines-jdk8")
}
fun implementationWithoutBundled(dependencyNotation: Provider<*>) {
implementation(dependencyNotation) {
excludedBundledLibraries()
}
implementation(dependencyNotation, excludeMPSLibraries)
}

implementationWithoutBundled(coreLibs.ktor.server.html.builder)
implementationWithoutBundled(coreLibs.ktor.server.netty)
implementationWithoutBundled(coreLibs.ktor.server.cors)
implementationWithoutBundled(coreLibs.ktor.server.status.pages)
implementationWithoutBundled(coreLibs.kotlin.logging)
implementationWithoutBundled(coreLibs.kotlin.coroutines.swing)
implementationWithoutBundled(libs.kotlin.coroutines.swing)

testImplementation(coreLibs.kotlin.coroutines.test)
testImplementation(coreLibs.ktor.server.test.host)
Expand All @@ -80,7 +72,7 @@ dependencies {
// Configure Gradle IntelliJ Plugin
// Read more: https://plugins.jetbrains.com/docs/intellij/tools-gradle-intellij-plugin.html
intellij {
localPath = mpsHome.map { it.asFile.absolutePath }
localPath = mpsHomeDir.map { it.asFile.absolutePath }
instrumentCode = false
plugins = listOf(
"Git4Idea",
Expand All @@ -96,12 +88,15 @@ tasks {

test {
// tests currently fail for these versions
enabled = !setOf(
211, // jetbrains.mps.vcs plugin cannot be loaded
212, // timeout because of some deadlock
213, // timeout because of some deadlock
222, // timeout because of some deadlock
).contains(mpsPlatformVersion)
// enabled = !setOf(
// 211, // jetbrains.mps.vcs plugin cannot be loaded
// 212, // timeout because of some deadlock
// 213, // timeout because of some deadlock
// 222, // timeout because of some deadlock
// ).contains(mpsPlatformVersion)

// incompatibility of ktor 3 with the bundled coroutines version
enabled = false
}

buildSearchableOptions {
Expand All @@ -113,39 +108,14 @@ tasks {
autoReloadPlugins.set(true)
}

val shortPlatformVersion = mpsVersion.replace(Regex("""20(\d\d)\.(\d+).*"""), "$1$2")
val mpsPluginDir = project.findProperty("mps$shortPlatformVersion.plugins.dir")?.toString()?.let { file(it) }
val mpsPluginDir = project.findProperty("mps$mpsPlatformVersion.plugins.dir")?.toString()?.let { file(it) }
if (mpsPluginDir != null && mpsPluginDir.isDirectory) {
create<Sync>("installMpsPlugin") {
dependsOn(prepareSandbox)
from(buildDir.resolve("idea-sandbox/plugins/mps-diff-plugin"))
into(mpsPluginDir.resolve("mps-diff-plugin"))
}
}

val checkBinaryCompatibility by registering {
group = "verification"
doLast {
val ignoredFiles = setOf(
"META-INF/MANIFEST.MF",
)
fun loadEntries(fileName: String) = rootProject.layout.buildDirectory
.dir("binary-compatibility")
.dir(project.name)
.file(fileName)
.get().asFile.inputStream().use {
val zip = ZipInputStream(it)
val entries = generateSequence { zip.nextEntry }
entries.associate { it.name to "size:${it.size},crc:${it.crc}" }
} - ignoredFiles
val entriesA = loadEntries("a.jar")
val entriesB = loadEntries("b.jar")
val mismatches = (entriesA.keys + entriesB.keys).map { it to (entriesA[it] to entriesB[it]) }.filter { it.second.first != it.second.second }
check(mismatches.isEmpty()) {
"The following files have a different content:\n" + mismatches.joinToString("\n") { " ${it.first}: ${it.second.first} != ${it.second.second}" }
}
}
}
}

publishing {
Expand Down
Loading
Loading