build.gradle.kts (373 lines of code) (raw):

import com.specificlanguages.mps.MainBuild import com.specificlanguages.mps.MpsBuild import com.specificlanguages.mps.RunAnt import com.specificlanguages.mps.TestBuild import de.itemis.mps.gradle.EnvironmentKind import de.itemis.mps.gradle.GitBasedVersioning import de.itemis.mps.gradle.tasks.MpsGenerate import de.itemis.mps.gradle.tasks.MpsMigrate import de.itemis.mps.gradle.tasks.Remigrate import groovy.xml.XmlSlurper import groovy.xml.slurpersupport.GPathResult import java.util.* plugins { id("de.itemis.mps.gradle.common") version "1.29.3.+" id("com.github.breadmoirai.github-release") version "2.5.2" id("maven-publish") id("base") id("de.itemis.mps.gradle.launcher") version "2.8.0.+" id("org.cyclonedx.bom") version "3.1.0" id("com.specificlanguages.mps") version "2.0.1" } // Detect if we are in a CI build val ciBuild = project.hasProperty("forceCI") || // On TeamCity we are in a CI build, except if mpsHomeDir is set (used on JetBrains TeamCity to test MPS-extensions // against unreleased MPS versions) project.hasProperty("teamcity") && !project.hasProperty("mpsHomeDir") // Dependency versions val mpsVersion = libs.mps.get().version!! // major version, e.g. '2021.1', '2021.2' val mpsMajor = "9999.9" if (ciBuild) { val branch = GitBasedVersioning.getGitBranch() val buildMajor = mpsMajor.split(".").first() val buildMinor = mpsMajor.split(".").last() val buildNumber = System.getenv("BUILD_NUMBER").toInt() // GitBasedVersioning returns branch with '/' replaced by '-' if (branch.startsWith("maintenance-mps")) { version = "$buildMajor.$buildMinor.$buildNumber.${GitBasedVersioning.getGitShortCommitHash()}" } else { version = GitBasedVersioning.getVersionWithCount(buildMajor, buildMinor, buildNumber) + "-SNAPSHOT" } println("##teamcity[buildNumber '${version}']") } else { version = "$mpsVersion-SNAPSHOT" println("Local build detected, version will be $version") } val releaseRepository = "https://artifacts.itemis.cloud/repository/maven-mps-releases/" val snapshotRepository = "https://artifacts.itemis.cloud/repository/maven-mps-snapshots/" val publishingRepository = if (version.toString().endsWith("-SNAPSHOT")) snapshotRepository else releaseRepository group = "de.itemis.mps" dependencies { mps(libs.mps) jbr(libs.jbr) } repositories { maven { url = uri("https://artifacts.itemis.cloud/repository/maven-mps/") } mavenCentral() } val skipResolveMps = project.hasProperty("mpsHomeDir") if (skipResolveMps) { val mpsHomeDir = rootProject.file(project.findProperty("mpsHomeDir")?.toString() ?: "$buildDir/mps") mpsDefaults.mpsHome = mpsHomeDir } val codeDir = layout.projectDirectory.dir("code") val reportsDir = layout.buildDirectory.dir("reports") bundledDependencies { create("diagram") { destinationDir = codeDir.dir("diagram/solutions/de.itemis.mps.editor.diagram.runtime/lib") val elkVersion = "0.11.0" dependency("de.itemis.mps:jgraphx:1.0.0") dependency("org.eclipse.elk:org.eclipse.elk.alg.common:$elkVersion") dependency("org.eclipse.elk:org.eclipse.elk.alg.layered:$elkVersion") dependency("org.eclipse.elk:org.eclipse.elk.alg.mrtree:$elkVersion") dependency("org.eclipse.elk:org.eclipse.elk.alg.radial:$elkVersion") dependency("org.eclipse.elk:org.eclipse.elk.alg.force:$elkVersion") dependency("org.eclipse.elk:org.eclipse.elk.alg.disco:$elkVersion") dependency("org.eclipse.elk:org.eclipse.elk.alg.rectpacking:$elkVersion") dependency("org.eclipse.elk:org.eclipse.elk.alg.spore:$elkVersion") dependency("org.eclipse.elk:org.eclipse.elk.alg.topdownpacking:$elkVersion") dependency("org.eclipse.elk:org.eclipse.elk.core:$elkVersion") dependency("org.eclipse.elk:org.eclipse.elk.graph:$elkVersion") dependency("org.eclipse.emf:org.eclipse.emf.common:2.44.0") dependency("org.eclipse.emf:org.eclipse.emf.ecore:2.41.0") dependency("org.eclipse.emf:org.eclipse.emf.ecore.xmi:2.39.0") // xbase lib appears to be an undeclared runtime dependency of elk.alg.layered since 0.11.0 dependency("org.eclipse.xtext:org.eclipse.xtext.xbase.lib:2.41.0") configuration { exclude(group = "com.google.guava") attributes.attribute(Attribute.of("org.gradle.jvm.environment", String::class.java), "standard-jvm") } } create("batik") { destinationDir = codeDir.dir("batik/solutions/lib") dependency("org.apache.xmlgraphics:batik-all:1.19") configuration { exclude(group = "commons-io") exclude(group = "commons-logging") } } create("commons") { destinationDir = codeDir.dir("apache-commons/solutions/org.apache.commons/lib") configuration { isTransitive = false } dependency("org.apache.commons:commons-csv:1.14.1") dependency("commons-io:commons-io:2.21.0") dependency("org.apache.commons:commons-lang3:3.20.0") dependency("org.apache.commons:commons-math3:3.6.1") dependency("org.apache.commons:commons-csv:1.14.1") dependency("commons-primitives:commons-primitives:1.0") dependency("com.miglayout:miglayout-core:11.4.2") dependency("com.miglayout:miglayout-swing:11.4.2") } create("collections") { destinationDir = codeDir.dir("shadowmodels/solutions/de.q60.mps.collections.libs/lib") dependency("org.apache.commons:commons-collections4:4.5.0") dependency("com.google.guava:guava:33.5.0-jre") dependency("net.sf.trove4j:trove4j:3.0.3") dependency("io.vavr:vavr:0.10.7") configuration { isTransitive = false attributes.attribute(TargetJvmEnvironment.TARGET_JVM_ENVIRONMENT_ATTRIBUTE, objects.named(TargetJvmEnvironment::class.java, TargetJvmEnvironment.STANDARD_JVM)) } } create("xml") { destinationDir = codeDir.dir("xml/solutions/lib") dependency("xerces:xercesImpl:2.12.2") dependency("xml-apis:xml-apis-ext:1.3.04") configuration { exclude(group = "system") exclude(module = "xml-apis") } } create("modelApi") { destinationDir = codeDir.dir("model-api/org.modelix.model.api/lib") dependency("org.modelix:model-api:2.1.9") configuration { exclude(group = "log4j", module = "log4j") exclude(group = "org.slf4j", module = "slf4j-api") exclude(group = "org.jetbrains", module = "annotations") } resolveTask { doLast { val versionsFile = File(destinationDir, "versions.txt") val resolvedArtifacts = configuration.get().resolvedConfiguration.resolvedArtifacts versionsFile.writer().use { for (artifact in resolvedArtifacts) { it.appendLine(artifact.file.name + "\n") } } } } } } val languages by mpsBuilds.creating(MainBuild::class) { mpsProjectDirectory = codeDir buildArtifactsDirectory = layout.buildDirectory.dir("artifacts/de.itemis.mps.extensions") buildSolutionDescriptor = codeDir.file("build/solutions/de.itemis.mps.extensions.build/de.itemis.mps.extensions.build.msd") buildFile = layout.buildDirectory.file("generated/languages/build.xml") } val tests by mpsBuilds.creating(TestBuild::class) { dependsOn(languages) mpsProjectDirectory = codeDir buildArtifactsDirectory = layout.buildDirectory.dir("artifacts/de.itemis.mps.extensions.tests") buildSolutionDescriptor = codeDir.file("build/solutions/de.itemis.mps.extensions.build/de.itemis.mps.extensions.build.msd") buildFile = layout.buildDirectory.file("generated/tests/build.xml") assembleAndCheckTask { finalizedBy("failOnTestError") doLast { val reportDir = layout.buildDirectory.dir("junitreport").get() ant.withGroovyBuilder { "taskdef"( "name" to "junitreport", "classname" to "org.apache.tools.ant.taskdefs.optional.junit.XMLResultAggregator", "classpath" to mpsDefaults.antClasspath.asPath ) "junitreport" { "fileset"("dir" to "$buildDir", "includes" to "**/TEST*.xml") "report"("format" to "frames", "todir" to reportDir) } } println("JUnit report placed into file://$reportDir/index.html") } } } val buildDate = Date().toString() val pluginVersion = version.toString() tasks.withType<RunAnt>().configureEach { valueProperties.put("buildDate", buildDate) valueProperties.put("pluginVersion", pluginVersion) } // ___________________ utilities ___________________ tasks.register("failOnTestError") { description = "evaluate junit result and fail on error" doLast { val juniXml = file("TESTS-TestSuites.xml") if (juniXml.exists()) { val junitResult = XmlSlurper().parse(juniXml) val failures = junitResult.depthFirst().asSequence().filter { (it as GPathResult).name() == "failure" } val errors = junitResult.depthFirst().asSequence().filter { (it as GPathResult).name() == "error" } if (failures.any() || errors.any()) { val amount = (failures + errors).count() throw GradleException("$amount JUnit tests failed. Check the test report for details.") } } } } tasks.zip { dependsOn(tasks.cyclonedxBom) eachFile { if (path == "de.itemis.mps.extensions/MPS.ThirdParty.jar") { exclude() } } from(reportsDir) { include("sbom.json") into("de.itemis.mps.extensions") } } tasks.register<Delete>("cleanMps") { delete(fileTree(projectDir) { include("**/classes_gen/**", "**/source_gen/**", "**/source_gen.caches/**", "tmp/**", "artifacts/**") }) } tasks.cyclonedxDirectBom { jsonOutput = reportsDir.get().file("sbom.json") // Generate JSON only xmlOutput.unsetConvention() } tasks.clean { dependsOn("cleanMps") } publishing { repositories { if (rootProject.hasProperty("artifacts.itemis.cloud.user") && rootProject.hasProperty("artifacts.itemis.cloud.pw")) { maven { name = "itemisCloud" url = uri(publishingRepository) credentials { username = rootProject.findProperty("artifacts.itemis.cloud.user") as String? password = rootProject.findProperty("artifacts.itemis.cloud.pw") as String? } } } if (rootProject.hasProperty("gpr.token")) { maven { name = "GitHubPackages" url = uri("https://maven.pkg.github.com/JetBrains/MPS-extensions") credentials { username = rootProject.findProperty("gpr.user") as String? password = rootProject.findProperty("gpr.token") as String? } } } } publications { create<MavenPublication>("extensions") { from(components["mps"]) artifactId = "extensions" pom { licenses { // official SPDX identifier // see https://spdx.org/licenses/ for list license { name = "Apache-2.0" url = "http://www.apache.org/licenses/LICENSE-2.0.txt" comments = "A business-friendly OSS license" distribution = "repo" } } organization { name = "JetBrains s.r.o" url = "https://www.jetbrains.com" } withXml { val dependenciesNode = asNode().appendNode("dependencies") forEachBundledDependency { val dependencyNode = dependenciesNode.appendNode("dependency") dependencyNode.appendNode("groupId", it.moduleGroup) dependencyNode.appendNode("artifactId", it.moduleName) dependencyNode.appendNode("version", it.moduleVersion) if (it.moduleArtifacts.isNotEmpty()) { dependencyNode.appendNode("type", it.moduleArtifacts.first().type) } dependencyNode.appendNode("scope", "provided") } } } } } } /** * Visit each bundled dependency, including its transitive dependencies. */ fun forEachBundledDependency(action: (ResolvedDependency) -> Unit) { val seen = mutableSetOf<ResolvedDependency>() val queue = ArrayDeque<ResolvedDependency>() for (bundledDependency in bundledDependencies) { queue.addAll(bundledDependency.configuration.get().resolvedConfiguration.firstLevelModuleDependencies) while (queue.isNotEmpty()) { val dep = queue.removeFirst() if (seen.add(dep)) { action(dep) queue.addAll(dep.children) } } } } tasks.register<Exec>("pipInstall") { inputs.file("requirements.txt") commandLine("python3", "-m", "pip", "install", "-r", "requirements.txt") } tasks.register<Exec>("previewDocs") { dependsOn("pipInstall") commandLine("python3", "-m", "mkdocs", "serve") } tasks.register<Exec>("deployDocs") { dependsOn("pipInstall") commandLine("python3", "-", "mkdocs", "gh-deploy", "--clean", "-r", "gh-pages", "--force") } var releaseNotes: String? = null var releaseName: String? = null var releaseTagName: String? = null if (rootProject.hasProperty("nightly_build")) { releaseName = "Nightly Build $version" releaseTagName = "nightly-$version" releaseNotes = """Automated Nightly build from ${buildDate}.""" } else { releaseNotes = rootProject.findProperty("releaseNotes") as String? releaseTagName = "release-$version" releaseName = version.toString() } githubRelease { owner = "jetbrains" repo = "MPS-extensions" token(rootProject.findProperty("github.token")?.toString() ?: "empty") tagName = releaseTagName targetCommitish = GitBasedVersioning.getGitCommitHash() body = releaseNotes prerelease = rootProject.hasProperty("nightly_build") releaseAssets(tasks.zip) dryRun = false } // Use all plugins in MPS, it doesn't seem to make any real difference compared to using a subset of plugins. val usedPluginRoots = listOf(mpsDefaults.mpsHome.dir("plugins")) tasks.register<MpsMigrate>("migrate") { dependsOn(provider { mpsBuilds.map(MpsBuild::generateTask) }) javaLauncher = jbrToolchain.javaLauncher mpsHome = mpsDefaults.mpsHome haltOnPrecheckFailure = true haltOnDependencyError = true projectDirectories.from(codeDir) pluginRoots.from(usedPluginRoots) maxHeapSize = "4G" } tasks.register<Remigrate>("remigrate") { mustRunAfter("migrate") mustRunAfter(provider { mpsBuilds.map(MpsBuild::generateTask) }) javaLauncher = jbrToolchain.javaLauncher mpsHome = mpsDefaults.mpsHome projectDirectories.from(codeDir) pluginRoots.from(usedPluginRoots) maxHeapSize = "4G" // diagram migration from version 0 is currently not rerunnable, although it claims to be excludeModuleMigration("de.itemis.mps.editor.diagram", 0) // not rerunnable until MPS-39315 is fixed excludeModuleMigration("jetbrains.mps.baseLanguage.javadoc", 0) } val generateChangelog by tasks.registering(MpsGenerate::class) { dependsOn(languages.generateTask) javaLauncher = jbrToolchain.javaLauncher mpsHome = mpsDefaults.mpsHome environmentKind = EnvironmentKind.MPS projectLocation = codeDir pluginRoots.from(usedPluginRoots) modules = listOf("de.itemis.mps.extensions.changelog") } val copyChangelog by tasks.registering { dependsOn(generateChangelog) doLast { // Using a copy action here instead of making this task a Copy. Otherwise Gradle considers the entire project to // be the output of this task (because the destination directory is the project root) and complains about // implicit dependencies. copy { from(codeDir.dir("solutions/de.itemis.mps.extensions.changelog/source_gen/de/itemis/mps/extensions/changelog")) into(layout.settingsDirectory) include("*.md") } } } tasks.build { dependsOn(copyChangelog) }