From bc47810ef82a7afbd4377beaf27a58d4cffe9fec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergej=20Ko=C5=A1=C4=8Dejev?= Date: Tue, 16 Apr 2024 14:16:33 +0200 Subject: [PATCH] 1.24.0: allow overriding version by setting mpsVersion This enables support of MPS prereleases or custom RCPs. Also, include extension name in error messages from extensions. --- CHANGELOG.md | 12 +++++ README.md | 46 +++++++++++-------- api/mps-gradle-plugin.api | 1 + build.gradle.kts | 2 +- .../kotlin/de/itemis/mps/gradle/Common.kt | 42 ++++++++--------- .../de/itemis/mps/gradle/ErrorMessages.kt | 13 ++++-- .../de/itemis/mps/gradle/generate/Plugin.kt | 11 +++-- .../de/itemis/mps/gradle/modelcheck/Plugin.kt | 12 +++-- .../itemis/mps/gradle/runmigrations/Plugin.kt | 13 ++++-- .../itemis/mps/gradle/GenerateModelsTest.kt | 7 +-- .../mps/gradle/ModelCheckWithPluginTest.kt | 7 ++- 11 files changed, 103 insertions(+), 63 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ceea6886..e6575c2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/README.md b/README.md index 648a20ab..b5c86377 100644 --- a/README.md +++ b/README.md @@ -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. @@ -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. @@ -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. @@ -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. diff --git a/api/mps-gradle-plugin.api b/api/mps-gradle-plugin.api index 31e0320a..217b6d11 100644 --- a/api/mps-gradle-plugin.api +++ b/api/mps-gradle-plugin.api @@ -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 } diff --git a/build.gradle.kts b/build.gradle.kts index bf1fc6ef..b1ac5278 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -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" diff --git a/src/main/kotlin/de/itemis/mps/gradle/Common.kt b/src/main/kotlin/de/itemis/mps/gradle/Common.kt index 6d8c2eb3..96b40cef 100644 --- a/src/main/kotlin/de/itemis/mps/gradle/Common.kt +++ b/src/main/kotlin/de/itemis/mps/gradle/Common.kt @@ -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) - -} \ No newline at end of file + // Otherwise, the version has to be provided explicitly. + throw GradleException(ErrorMessages.mustSetVersionWhenNoMpsConfiguration(extensionName)) +} diff --git a/src/main/kotlin/de/itemis/mps/gradle/ErrorMessages.kt b/src/main/kotlin/de/itemis/mps/gradle/ErrorMessages.kt index 9cf025f6..15142fbf 100644 --- a/src/main/kotlin/de/itemis/mps/gradle/ErrorMessages.kt +++ b/src/main/kotlin/de/itemis/mps/gradle/ErrorMessages.kt @@ -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." } diff --git a/src/main/kotlin/de/itemis/mps/gradle/generate/Plugin.kt b/src/main/kotlin/de/itemis/mps/gradle/generate/Plugin.kt index e3cdd6dd..06ca46b7 100644 --- a/src/main/kotlin/de/itemis/mps/gradle/generate/Plugin.kt +++ b/src/main/kotlin/de/itemis/mps/gradle/generate/Plugin.kt @@ -29,13 +29,13 @@ open class GenerateMpsProjectPlugin : Plugin { 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) @@ -43,13 +43,16 @@ open class GenerateMpsProjectPlugin : Plugin { 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)) } /* diff --git a/src/main/kotlin/de/itemis/mps/gradle/modelcheck/Plugin.kt b/src/main/kotlin/de/itemis/mps/gradle/modelcheck/Plugin.kt index cc6ae144..701e16cc 100644 --- a/src/main/kotlin/de/itemis/mps/gradle/modelcheck/Plugin.kt +++ b/src/main/kotlin/de/itemis/mps/gradle/modelcheck/Plugin.kt @@ -30,13 +30,12 @@ open class ModelCheckPluginExtensions(objectFactory: ObjectFactory) : BasePlugin open class ModelcheckMpsProjectPlugin : Plugin { 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) @@ -44,13 +43,16 @@ open class ModelcheckMpsProjectPlugin : Plugin { 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 { diff --git a/src/main/kotlin/de/itemis/mps/gradle/runmigrations/Plugin.kt b/src/main/kotlin/de/itemis/mps/gradle/runmigrations/Plugin.kt index 5c5e0b80..833e79fa 100644 --- a/src/main/kotlin/de/itemis/mps/gradle/runmigrations/Plugin.kt +++ b/src/main/kotlin/de/itemis/mps/gradle/runmigrations/Plugin.kt @@ -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 @@ -43,17 +44,18 @@ open class RunMigrationsMpsProjectPlugin : Plugin { 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) { @@ -68,13 +70,16 @@ open class RunMigrationsMpsProjectPlugin : Plugin { 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") { diff --git a/src/test/kotlin/test/de/itemis/mps/gradle/GenerateModelsTest.kt b/src/test/kotlin/test/de/itemis/mps/gradle/GenerateModelsTest.kt index 62439b2d..9d0f4120 100644 --- a/src/test/kotlin/test/de/itemis/mps/gradle/GenerateModelsTest.kt +++ b/src/test/kotlin/test/de/itemis/mps/gradle/GenerateModelsTest.kt @@ -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 @@ -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`() { @@ -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 @@ -345,4 +346,4 @@ class GenerateModelsTest { Assert.assertTrue("generate.javaLauncher should not be present", result.output.contains("generate.javaLauncher.isPresent: false")) } -} \ No newline at end of file +} diff --git a/src/test/kotlin/test/de/itemis/mps/gradle/ModelCheckWithPluginTest.kt b/src/test/kotlin/test/de/itemis/mps/gradle/ModelCheckWithPluginTest.kt index 672664e1..8258e12e 100644 --- a/src/test/kotlin/test/de/itemis/mps/gradle/ModelCheckWithPluginTest.kt +++ b/src/test/kotlin/test/de/itemis/mps/gradle/ModelCheckWithPluginTest.kt @@ -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 @@ -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()) @@ -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"))) } }