Skip to content

Commit

Permalink
Merge pull request #150 from kolmar/MpsExecute-task
Browse files Browse the repository at this point in the history
Add MpsExecute task
  • Loading branch information
sergej-koscejev authored Nov 23, 2023
2 parents 390e059 + 026105d commit f359ee2
Show file tree
Hide file tree
Showing 13 changed files with 396 additions and 12 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@
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.21.0

### Added

- `MpsExecute` custom task to execute specified method from a generated class.

## 1.20.0

### Added
Expand Down
40 changes: 40 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,46 @@ Parameters:

Compatibility note: `MpsCheck` task currently extends `JavaExec` but this may change in the future. Do not rely on this.

## `MpsExecute` Task Type

A custom task to execute a specified method in a generated class.

### Usage

```groovy
import de.itemis.mps.gradle.tasks.MpsExecute
plugins {
// Required in order to use the MpsExecute task
id("de.itemis.mps.gradle.common")
}
tasks.register('executeMyTask', MpsExecute) {
mpsHome = file("...") // MPS home directory
module = "my.module"
className = "my.module.GeneratedClass"
method = "myMethod"
methodArguments = ["arg1", "arg2"]
}
```

Parameters:

* `projectLocation` - the location of the MPS project. Default is the Gradle project directory.
* `additionalExecuteBackendClasspath` - any extra libraries that should be on the classpath of the execute backend.
* `macros` - variables/macros that are necessary to open the project.
* `mpsHome` - the home directory of the MPS distribution (or RCP) to use for testing.
* `mpsVersion` - the MPS version, such as "2021.3". Default is autodetection by reading `$mpsHome/build.properties`.
* `pluginRoots` - directories containing additional plugins to load.
* `module` - the module that contains the generated class.
* `className` - fully qualified name of the generated class, that contains the method to execute.
* `method` - name of the method. The method should be public and static. Supported signatures are `(Project)` (from
`jetbrains.mps.project` model) or `(Project, String[])`.
* `methodArguments` - list of arguments to pass to the method. Default is an empty list. If arguments are provided the
method signature must be `(Project, String[])`.


## Run migrations

Run all pending migrations in the project.
Expand Down
15 changes: 15 additions & 0 deletions api/mps-gradle-plugin.api
Original file line number Diff line number Diff line change
Expand Up @@ -323,3 +323,18 @@ public abstract class de/itemis/mps/gradle/tasks/MpsCheck : org/gradle/api/tasks
public final fun getWarningAsError ()Lorg/gradle/api/provider/Property;
}

public abstract class de/itemis/mps/gradle/tasks/MpsExecute : org/gradle/api/tasks/JavaExec {
public fun <init> ()V
public fun exec ()V
public final fun getAdditionalExecuteBackendClasspath ()Lorg/gradle/api/file/ConfigurableFileCollection;
public abstract fun getClassName ()Lorg/gradle/api/provider/Property;
public abstract fun getMacros ()Lorg/gradle/api/provider/MapProperty;
public abstract fun getMethod ()Lorg/gradle/api/provider/Property;
public abstract fun getMethodArguments ()Lorg/gradle/api/provider/ListProperty;
public abstract fun getModule ()Lorg/gradle/api/provider/Property;
public abstract fun getMpsHome ()Lorg/gradle/api/file/DirectoryProperty;
public abstract fun getMpsVersion ()Lorg/gradle/api/provider/Property;
public abstract fun getPluginRoots ()Lorg/gradle/api/provider/SetProperty;
public abstract fun getProjectLocation ()Lorg/gradle/api/file/DirectoryProperty;
}

6 changes: 5 additions & 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.20.0"
val baseVersion = "1.21.0"

group = "de.itemis.mps"

Expand Down Expand Up @@ -79,6 +79,10 @@ gradlePlugin {
id = "modelcheck"
implementationClass = "de.itemis.mps.gradle.modelcheck.ModelcheckMpsProjectPlugin"
}
register("execute") {
id = "execute"
implementationClass = "de.itemis.mps.gradle.execute.ExecuteMpsProjectPlugin"
}
register("migrations-executor") {
id = "run-migrations"
implementationClass = "de.itemis.mps.gradle.runmigrations.RunMigrationsMpsProjectPlugin"
Expand Down
2 changes: 1 addition & 1 deletion gradle.lockfile
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# This is a Gradle generated file for dependency locking.
# Manual edits can break the build and are not advised.
# This file is expected to be part of source control.
de.itemis.mps.build-backends:launcher:2.1.0.58.0fb483b=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
de.itemis.mps.build-backends:launcher:2.1.0.62.f5dd7a0=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
junit:junit:4.13.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
net.java.dev.jna:jna:5.6.0=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath
net.swiftzer.semver:semver:1.1.2=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
Expand Down
4 changes: 4 additions & 0 deletions src/main/kotlin/de/itemis/mps/gradle/ErrorMessages.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
package de.itemis.mps.gradle

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"
}
5 changes: 5 additions & 0 deletions src/main/kotlin/de/itemis/mps/gradle/common.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@ package de.itemis.mps.gradle
*/

val modelcheckBackend by configurations.creating
val executeBackend by configurations.creating

modelcheckBackend.defaultDependencies {
add(dependencies.create("de.itemis.mps.build-backends:modelcheck:${MPS_BUILD_BACKENDS_VERSION}"))
}

executeBackend.defaultDependencies {
add(dependencies.create("de.itemis.mps.build-backends:execute:${MPS_BUILD_BACKENDS_VERSION}"))
}
8 changes: 2 additions & 6 deletions src/main/kotlin/de/itemis/mps/gradle/tasks/MpsCheck.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package de.itemis.mps.gradle.tasks

import de.itemis.mps.gradle.ErrorMessages
import de.itemis.mps.gradle.launcher.MpsBackendBuilder
import de.itemis.mps.gradle.launcher.MpsVersionDetection
import org.gradle.api.GradleException
Expand All @@ -13,7 +14,6 @@ import org.gradle.api.tasks.*
import org.gradle.kotlin.dsl.*
import org.gradle.language.base.plugins.LifecycleBasePlugin
import org.gradle.process.CommandLineArgumentProvider
import java.io.File

@CacheableTask
abstract class MpsCheck : JavaExec(), VerificationTask {
Expand Down Expand Up @@ -150,7 +150,7 @@ abstract class MpsCheck : JavaExec(), VerificationTask {
override fun exec() {
val projectLocationAsFile = projectLocation.get().asFile
if (!projectLocationAsFile.resolve(".mps").isDirectory) {
throw GradleException(MpsCheckErrors.noMpsProjectIn(projectLocationAsFile))
throw GradleException(ErrorMessages.noMpsProjectIn(projectLocationAsFile))
}

super.exec()
Expand All @@ -169,7 +169,3 @@ abstract class MpsCheck : JavaExec(), VerificationTask {
include("plugins/git4idea/**/*.jar")
}
}

internal object MpsCheckErrors {
fun noMpsProjectIn(dir: File): String = "Directory does not contain an MPS project: " + dir
}
108 changes: 108 additions & 0 deletions src/main/kotlin/de/itemis/mps/gradle/tasks/MpsExecute.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package de.itemis.mps.gradle.tasks

import de.itemis.mps.gradle.ErrorMessages
import de.itemis.mps.gradle.launcher.MpsBackendBuilder
import de.itemis.mps.gradle.launcher.MpsVersionDetection
import org.gradle.api.GradleException
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.Directory
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.logging.LogLevel
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.MapProperty
import org.gradle.api.provider.Property
import org.gradle.api.provider.SetProperty
import org.gradle.api.tasks.*
import org.gradle.kotlin.dsl.newInstance
import org.gradle.work.DisableCachingByDefault


@DisableCachingByDefault(because = "calls arbitrary user code")
abstract class MpsExecute : JavaExec() {

@get:Internal
abstract val mpsHome: DirectoryProperty

@get:Internal
abstract val mpsVersion: Property<String>

@get:Internal
abstract val projectLocation: DirectoryProperty

@get:Classpath
abstract val pluginRoots: SetProperty<Directory>

@get:Internal
abstract val macros: MapProperty<String, String>

@get:Internal
abstract val module: Property<String>

@get:Internal
abstract val className: Property<String>

@get:Internal
abstract val method: Property<String>

@get:Internal
abstract val methodArguments: ListProperty<String>

@get:Internal
val additionalExecuteBackendClasspath: ConfigurableFileCollection =
objectFactory.fileCollection().from(initialExecuteBackendClasspath())

init {
mpsVersion.convention(MpsVersionDetection.fromMpsHome(project.layout, providerFactory, mpsHome.asFile))
projectLocation.convention(project.layout.projectDirectory)

objectFactory.newInstance(MpsBackendBuilder::class)
.withMpsHomeDirectory(mpsHome)
.withMpsVersion(mpsVersion)
.configure(this)

argumentProviders.add {
mutableListOf<String>().apply {
add("--project=${projectLocation.get().asFile}")

pluginRoots.get().forEach {
findPluginsRecursively(it.asFile).forEach {
add("--plugin=${it.id}::${it.path}")
}
}
macros.get().forEach { add("--macro=${it.key}::${it.value}") }

add("--module=${module.get()}")
add("--class=${className.get()}")
add("--method=${method.get()}")
methodArguments.get().forEach { add("--arg=$it") }

val effectiveLogLevel = logging.level ?: project.logging.level ?: project.gradle.startParameter.logLevel
if (effectiveLogLevel <= LogLevel.INFO) {
add("--log-level=info")
}
}
}

description = "Execute specified method from a generated class to modify the MPS project"
group = "execute"

classpath(project.configurations.named("executeBackend"))
classpath(additionalExecuteBackendClasspath)

mainClass.set("de.itemis.mps.gradle.execute.MainKt")
}

@TaskAction
override fun exec() {
val projectLocationAsFile = projectLocation.get().asFile
if (!projectLocationAsFile.resolve(".mps").isDirectory) {
throw GradleException(ErrorMessages.noMpsProjectIn(projectLocationAsFile))
}

super.exec()
}

private fun initialExecuteBackendClasspath() = mpsHome.asFileTree.matching {
include("lib/**/*.jar")
}
}
6 changes: 2 additions & 4 deletions src/test/kotlin/test/de/itemis/mps/gradle/MpsCheckTaskTest.kt
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package test.de.itemis.mps.gradle

import de.itemis.mps.gradle.tasks.MpsCheckErrors
import org.gradle.api.GradleException
import org.gradle.api.invocation.Gradle
import de.itemis.mps.gradle.ErrorMessages
import org.gradle.testkit.runner.GradleRunner
import org.gradle.testkit.runner.TaskOutcome
import org.hamcrest.CoreMatchers.containsString
Expand Down Expand Up @@ -67,7 +65,7 @@ class MpsCheckTaskTest {
val result = gradleRunner().withArguments("checkProject").buildAndFail()

Assert.assertEquals(TaskOutcome.FAILED, result.task(":checkProject")?.outcome)
assertThat(result.output, containsString(MpsCheckErrors.noMpsProjectIn(testProjectDir.root.canonicalFile)))
assertThat(result.output, containsString(ErrorMessages.noMpsProjectIn(testProjectDir.root.canonicalFile)))
}

@Test
Expand Down
Loading

0 comments on commit f359ee2

Please sign in to comment.