Skip to content

Commit

Permalink
1.24.0: allow overriding version by setting mpsVersion
Browse files Browse the repository at this point in the history
This enables support of MPS prereleases or custom RCPs.

Also, include extension name in error messages from extensions.
  • Loading branch information
sergej-koscejev committed Apr 16, 2024
1 parent 614437e commit bc47810
Show file tree
Hide file tree
Showing 11 changed files with 103 additions and 63 deletions.
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,18 @@
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## 1.24.0

### Changed

- Extensions (`generate`, `modelcheck`, `runMigrations`) will use `mpsVersion` if specified, instead of relying on
auto-detection logic. This makes it possible to use extensions with a non-standard MPS dependency (such as a
pre-release).
- Setting `mpsVersion` but not `mpsLocation` was not supported previously but is supported now. The default location
(`$buildDir/mps`) will be used and `mpsConfig` will be unpacked there. At least one of `mpsLocation` and `mpsConfig`
must still be specified.
- Extension-related error messages now include the name of the extension.

## 1.23.1

### Fixed
Expand Down
46 changes: 26 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -279,10 +279,11 @@ dependencies {
```

Parameters:
* `mpsConfig` - the configuration used to resolve MPS. Currently only vanilla MPS is supported and no custom RCPs.
Custom plugins are supported via the `pluginLocation` parameter.
* `mpsLocation` - optional location where to place the MPS files.
* `mpsVersion` - optional if you use a [custom distribution](#Custom MPS Distribution) of MPS
* `mpsConfig` - the configuration used to resolve MPS. Custom plugins are supported via the `pluginLocation` parameter.
* `mpsLocation` - optional location where to place the MPS files if `mpsConfig` is specified, or where to take them from
otherwise.
* `mpsVersion` - optionally overrides automated version detection from `mpsConfig`. Required if you use
a [custom distribution](#Custom MPS Distribution) of MPS.
* `javaExec` - optional `java` executable to use.
* `pluginLocation` - location where to load the plugins from. Structure needs to be a flat folder structure similar to the
`plugins` directory inside of the MPS installation.
Expand Down Expand Up @@ -348,10 +349,11 @@ modelcheck {
```

Parameters:
* `mpsConfig` - the configuration used to resolve MPS. Currently only vanilla MPS is supported and no custom RCPs.
Custom plugins are supported via the `pluginLocation` parameter.
* `mpsLocation` - optional location where to place the MPS files.
* `mpsVersion` - optional if you use a [custom distribution](#Custom MPS Distribution) of MPS
* `mpsConfig` - the configuration used to resolve MPS. Custom plugins are supported via the `pluginLocation` parameter.
* `mpsLocation` - optional location where to place the MPS files if `mpsConfig` is specified, or where to take them from
otherwise.
* `mpsVersion` - optionally overrides automated version detection from `mpsConfig`. Required if you use
a [custom distribution](#Custom MPS Distribution) of MPS.
* `javaExec` - optional `java` executable to use.
* `pluginLocation` - location where to load the plugins from. Structure needs to be a flat folder structure similar to the
`plugins` directory inside of the MPS installation.
Expand Down Expand Up @@ -607,9 +609,11 @@ runMigrations {
```

Parameters:
* `mpsConfig` - configuration used to resolve MPS.
* `mpsLocation` - location where to place the MPS files.
* `mpsVersion` - if you use a [custom distribution](#custom-mps-distribution) of MPS.
* `mpsConfig` - the configuration used to resolve MPS.
* `mpsLocation` - optional location where to place the MPS files if `mpsConfig` is specified, or where to take them from
otherwise.
* `mpsVersion` - optionally overrides automated version detection from `mpsConfig`. Required if you use
a [custom distribution](#Custom MPS Distribution) of MPS.
* `projectLocation` - location of the project that should be migrated.
* `force` - ignores the marker files for projects which allow pending migrations, migrate them anyway. Supported in 2021.3.0 and higher.
* `haltOnPrecheckFailure` - controls whether migration is aborted if pre-checks fail (except the check for migrated dependecies) Default: `true`. Supported in 2021.1 and higher.
Expand Down Expand Up @@ -681,17 +685,19 @@ downloadJbr {
## Custom MPS Distribution

Features that perform an action inside an MPS project, like the `modelcheck` or `generate-models` plugin, require
an MPS available to them. While for vanilla MPS it is enough to pass in a reference to the MPS dependency via the
`mpsConfig` property this doesn't work for custom distributions of MPS. A custom distribution of MPS is also called
a MPS RCP. If you like to use your own MPS distribution with preinstalled plugins and your own versioning scheme
then this is possible but requires additional steps in the build script.
an MPS available to them. While for vanilla MPS it is enough to pass in a reference to the MPS dependency via the
`mpsConfig` property, this doesn't work for custom distributions of MPS. A custom distribution of MPS is also called
an MPS RCP. If you like to use your own MPS distribution with preinstalled plugins and your own versioning scheme
then this is possible but requires additional steps in the build script.

When you are using a custom distribution of MPS you can no longer use the `mpsConfig` property and rely on
the plugin resolving it. The plugin needs to be configured with the properties `mpsVersion` and `mpsLocation`
being set and no value set for `mpsConfig`. If you set `mpsVersion` but also set `mpsConfig` then `mpsConfig`
will take precedence over `mpsVersion` and the plugin will resolve that configuration into `mpsLocation`.
When you are using a custom distribution of MPS you can still use the `mpsConfig` property and rely on
the plugin resolving it. However, you may need to configure explicit `mpsVersion` for the plugin. You can also use a
custom `mpsLocation` with no value set for `mpsConfig`. In this case you _must_ configure `mpsVersion` as well.

`mpsVersion` needs to be set to the exact MPS version your custom distribution is based on e.g. if you build a
If you set `mpsVersion` but also set `mpsConfig` then `mpsVersion` will take precedence over the version of the
dependency in the configuration. The plugin will resolve the specified configuration into `mpsLocation`.

`mpsVersion` needs to be set to the exact MPS version your custom distribution is based on. For example, if you build an
RCP with MPS 2020.3.3 you need to set this property to `2020.3.3`. `mpsLocation` needs to point to the location
where you extracted your custom MPS distribution into e.g. `$buildDir/myAwesomeMPS` if you extracted into that location.

Expand Down
1 change: 1 addition & 0 deletions api/mps-gradle-plugin.api
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ public final class de/itemis/mps/gradle/CommonKt {
public static final field MPS_SUPPORT_MSG Ljava/lang/String;
public static final fun argsFromBaseExtension (Lde/itemis/mps/gradle/BasePluginExtensions;)Lorg/gradle/process/CommandLineArgumentProvider;
public static final fun getMPSVersion (Lde/itemis/mps/gradle/BasePluginExtensions;)Ljava/lang/String;
public static final fun getMPSVersion (Lde/itemis/mps/gradle/BasePluginExtensions;Ljava/lang/String;)Ljava/lang/String;
public static final fun validateDefaultJvm ()V
}

Expand Down
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ plugins {
id("org.jetbrains.kotlinx.binary-compatibility-validator") version "0.13.2"
}

val baseVersion = "1.23.1"
val baseVersion = "1.24.0"

group = "de.itemis.mps"

Expand Down
42 changes: 21 additions & 21 deletions src/main/kotlin/de/itemis/mps/gradle/Common.kt
Original file line number Diff line number Diff line change
Expand Up @@ -89,26 +89,26 @@ fun argsFromBaseExtension(extensions: BasePluginExtensions): CommandLineArgument
result
}

fun BasePluginExtensions.getMPSVersion(): String {
/*
If the user supplies a MPS config we use this one to resolve MPS and get the version. For other scenarios the user
can supply mpsLocation and mpsVersion then we do not resolve anything and the users build script is responsible for
resolving a compatible MPS into th mpsLocation before the
*/
if(mpsConfig != null) {
return mpsConfig!!
.resolvedConfiguration
.firstLevelModuleDependencies.find { it.moduleGroup == "com.jetbrains" && it.moduleName == "mps" }
?.moduleVersion ?: throw GradleException("MPS configuration doesn't contain MPS")
}

if(mpsVersion != null) {
if(mpsLocation == null) {
throw GradleException(ErrorMessages.MUST_SET_VERSION_AND_LOCATION)
}
return mpsVersion!!
@Deprecated("Use getMPSVersion(extensionName)", replaceWith = ReplaceWith("getMPSVersion(this.javaClass.name)"))
fun BasePluginExtensions.getMPSVersion(): String = getMPSVersion(this.javaClass.name)

/**
* [extensionName]: extension name, for diagnostics.
*/
fun BasePluginExtensions.getMPSVersion(extensionName: String): String {
// If the user supplies explicit mpsVersion, we use it.
if (mpsVersion != null) return mpsVersion!!

val mpsConfig = mpsConfig
if (mpsConfig != null) {
// If the user supplies a configuration, we use it to detect MPS version.
return mpsConfig.resolvedConfiguration.firstLevelModuleDependencies
.find { it.moduleGroup == "com.jetbrains" && it.moduleName == "mps" }
?.moduleVersion
?: throw GradleException(ErrorMessages.couldNotDetermineMpsVersionFromConfiguration(mpsConfig)
)
}

throw GradleException(ErrorMessages.MUST_SET_CONFIG_OR_VERSION)

}
// Otherwise, the version has to be provided explicitly.
throw GradleException(ErrorMessages.mustSetVersionWhenNoMpsConfiguration(extensionName))
}
13 changes: 10 additions & 3 deletions src/main/kotlin/de/itemis/mps/gradle/ErrorMessages.kt
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
package de.itemis.mps.gradle

import org.gradle.api.artifacts.Configuration
import java.io.File

internal object ErrorMessages {
const val MUST_SET_CONFIG_OR_VERSION = "Either mpsConfig or mpsVersion needs to specified!"
const val MUST_SET_VERSION_AND_LOCATION = "Setting an MPS version but no MPS location is not supported!"
const val MPS_VERSION_NOT_SUPPORTED = "This version of mps-gradle-plugin only supports MPS 2020.1 and above. Please use version 1.4 with an older version of MPS."

fun noMpsProjectIn(dir: File): String = "Directory does not contain an MPS project: $dir"
internal fun noMpsProjectIn(dir: File): String = "Directory does not contain an MPS project: $dir"

internal fun couldNotDetermineMpsVersionFromConfiguration(mpsConfig: Configuration) =
"Could not determine MPS version from configuration ${mpsConfig.name} (configuration must contain com.jetbrains:mps dependency)."

internal fun mustSetConfigOrLocation(extensionName: String) = "Either mpsConfig or mpsLocation needs to specified for extension $extensionName."
internal fun mustSetVersionWhenNoMpsConfiguration(extensionName: String) =
"Could not determine MPS version because mpsConfiguration was not specified. Set mpsVersion of $extensionName" +
" extension explicitly."
}
11 changes: 7 additions & 4 deletions src/main/kotlin/de/itemis/mps/gradle/generate/Plugin.kt
Original file line number Diff line number Diff line change
Expand Up @@ -29,27 +29,30 @@ open class GenerateMpsProjectPlugin : Plugin<Project> {

override fun apply(project: Project) {
project.run {
val extension = extensions.create("generate", GeneratePluginExtensions::class.java)
val extensionName = "generate"
val extension = extensions.create(extensionName, GeneratePluginExtensions::class.java)
val generate = tasks.register("generate", JavaExec::class.java)
val fake = tasks.register("fakeBuildNumber", FakeBuildNumberTask::class.java)

afterEvaluate {
val mpsLocation = extension.mpsLocation ?: File(project.buildDir, "mps")
val mpsVersion = extension.getMPSVersion()
val mpsVersion = extension.getMPSVersion(extensionName)

val genConfig = extension.backendConfig ?: createDetachedBackendConfig(project)

if(mpsVersion.substring(0..3).toInt() < 2020) {
throw GradleException(MPS_SUPPORT_MSG)
}

val mpsLocation = extension.mpsLocation ?: File(project.buildDir, "mps")
val resolveMps = if (extension.mpsConfig != null) {
tasks.register("resolveMpsForGeneration", Copy::class.java) {
from({ extension.mpsConfig!!.resolve().map(::zipTree) })
into(mpsLocation)
}
} else {
} else if (extension.mpsLocation != null) {
tasks.register("resolveMpsForGeneration")
} else {
throw GradleException(ErrorMessages.mustSetConfigOrLocation(extensionName))
}

/*
Expand Down
12 changes: 7 additions & 5 deletions src/main/kotlin/de/itemis/mps/gradle/modelcheck/Plugin.kt
Original file line number Diff line number Diff line change
Expand Up @@ -30,27 +30,29 @@ open class ModelCheckPluginExtensions(objectFactory: ObjectFactory) : BasePlugin
open class ModelcheckMpsProjectPlugin : Plugin<Project> {
override fun apply(project: Project) {
project.run {
val extension = extensions.create("modelcheck", ModelCheckPluginExtensions::class.java)
val extensionName = "modelcheck"
val extension = extensions.create(extensionName, ModelCheckPluginExtensions::class.java)
val checkmodels = tasks.register("checkmodels", JavaExec::class.java)

afterEvaluate {
val mpsLocation = extension.mpsLocation ?: File(project.buildDir, "mps")

val mpsVersion = extension.getMPSVersion()
val mpsVersion = extension.getMPSVersion(extensionName)

val genConfig = extension.backendConfig ?: createDetachedBackendConfig(project)

if(mpsVersion.substring(0..3).toInt() < 2020) {
throw GradleException(MPS_SUPPORT_MSG)
}

val mpsLocation = extension.mpsLocation ?: File(project.buildDir, "mps")
val resolveMps = if (extension.mpsConfig != null) {
tasks.register("resolveMpsForModelcheck", Copy::class.java) {
from({ extension.mpsConfig!!.resolve().map(::zipTree) })
into(mpsLocation)
}
} else {
} else if (extension.mpsLocation != null) {
tasks.register("resolveMpsForModelcheck")
} else {
throw GradleException(ErrorMessages.mustSetConfigOrLocation(extensionName))
}

checkmodels.configure {
Expand Down
13 changes: 9 additions & 4 deletions src/main/kotlin/de/itemis/mps/gradle/runmigrations/Plugin.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package de.itemis.mps.gradle.runmigrations

import de.itemis.mps.gradle.BasePluginExtensions
import de.itemis.mps.gradle.ErrorMessages
import de.itemis.mps.gradle.getMPSVersion
import de.itemis.mps.gradle.runAnt
import groovy.xml.MarkupBuilder
Expand Down Expand Up @@ -43,17 +44,18 @@ open class RunMigrationsMpsProjectPlugin : Plugin<Project> {

override fun apply(project: Project) {
project.run {
val extension = extensions.create("runMigrations", MigrationExecutorPluginExtensions::class.java)
val extensionName = "runMigrations"
val extension = extensions.create(extensionName, MigrationExecutorPluginExtensions::class.java)

tasks.register("runMigrations")

afterEvaluate {
val mpsLocation = extension.mpsLocation ?: File(project.buildDir, "mps")
val projectLocation = extension.projectLocation ?: throw GradleException("No project path set")
if (!file(projectLocation).exists()) {
throw GradleException("The path to the project doesn't exist: $projectLocation")
}

val mpsVersion = extension.getMPSVersion()
val mpsVersion = extension.getMPSVersion(extensionName)
val parsedMPSVersion = SemVer.parse(mpsVersion)

if (extension.force != null && parsedMPSVersion < MIN_VERSION_FOR_FORCE) {
Expand All @@ -68,13 +70,16 @@ open class RunMigrationsMpsProjectPlugin : Plugin<Project> {
throw GradleException("The 'do not halt on dependency error' option is only supported for MPS version $MIN_VERSION_FOR_HALT_ON_DEPENDENCY_ERROR and higher.")
}

val mpsLocation = extension.mpsLocation ?: File(project.buildDir, "mps")
val resolveMps: Task = if (extension.mpsConfig != null) {
tasks.create("resolveMpsForMigrations", Copy::class.java) {
from({ extension.mpsConfig!!.resolve().map(::zipTree) })
into(mpsLocation)
}
} else {
} else if (extension.mpsLocation != null) {
tasks.create("resolveMpsForMigrations")
} else {
throw GradleException(ErrorMessages.mustSetConfigOrLocation(extensionName))
}

tasks.named("runMigrations") {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package test.de.itemis.mps.gradle

import de.itemis.mps.gradle.ErrorMessages
import de.itemis.mps.gradle.generate.GeneratePluginExtensions
import org.gradle.testkit.runner.GradleRunner
import org.gradle.testkit.runner.TaskOutcome
import org.hamcrest.CoreMatchers
Expand Down Expand Up @@ -256,7 +257,7 @@ class GenerateModelsTest {
.withArguments()
.withPluginClasspath()
.buildAndFail()
MatcherAssert.assertThat(result.output, CoreMatchers.containsString(ErrorMessages.MUST_SET_VERSION_AND_LOCATION))
MatcherAssert.assertThat(result.output, CoreMatchers.containsString(ErrorMessages.mustSetConfigOrLocation("generate")))
}
@Test
fun `generate fails with only MPS path set`() {
Expand Down Expand Up @@ -299,7 +300,7 @@ class GenerateModelsTest {
.withArguments()
.withPluginClasspath()
.buildAndFail()
MatcherAssert.assertThat(result.output, CoreMatchers.containsString(ErrorMessages.MUST_SET_CONFIG_OR_VERSION))
MatcherAssert.assertThat(result.output, CoreMatchers.containsString(ErrorMessages.mustSetVersionWhenNoMpsConfiguration("generate")))
}

@Test
Expand Down Expand Up @@ -345,4 +346,4 @@ class GenerateModelsTest {
Assert.assertTrue("generate.javaLauncher should not be present",
result.output.contains("generate.javaLauncher.isPresent: false"))
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package test.de.itemis.mps.gradle

import de.itemis.mps.gradle.ErrorMessages
import de.itemis.mps.gradle.modelcheck.ModelCheckPluginExtensions
import org.gradle.testkit.runner.GradleRunner
import org.gradle.testkit.runner.TaskOutcome
import org.hamcrest.CoreMatchers
Expand Down Expand Up @@ -273,8 +274,9 @@ class ModelCheckWithPluginTest {
.withPluginClasspath()
.buildAndFail()

MatcherAssert.assertThat(result.output, CoreMatchers.containsString(ErrorMessages.MUST_SET_VERSION_AND_LOCATION))
MatcherAssert.assertThat(result.output, CoreMatchers.containsString(ErrorMessages.mustSetConfigOrLocation("modelcheck")))
}

@Test
fun `check model fails with only MPS path set`() {
settingsFile.writeText(settingsBoilerplate())
Expand All @@ -296,6 +298,7 @@ class ModelCheckWithPluginTest {
.withPluginClasspath()
.buildAndFail()

MatcherAssert.assertThat(result.output, CoreMatchers.containsString(ErrorMessages.MUST_SET_CONFIG_OR_VERSION))
MatcherAssert.assertThat(result.output, CoreMatchers.containsString(
ErrorMessages.mustSetVersionWhenNoMpsConfiguration("modelcheck")))
}
}

0 comments on commit bc47810

Please sign in to comment.