Skip to content

Commit

Permalink
Merge pull request #20 from mbeddr/feature/generate-user-project
Browse files Browse the repository at this point in the history
generate user project
  • Loading branch information
coolya authored Jul 25, 2018
2 parents 983f94f + e468a7f commit 93a2a08
Show file tree
Hide file tree
Showing 14 changed files with 557 additions and 55 deletions.
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,9 @@
/buildSrc/build
/out/
*.iml
.gradletasknamecache
.classpath
.settings/
bin/
.project
build/
47 changes: 47 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,3 +122,50 @@ present). `destination` is then generated based on the properties.
Each property represents an entry in `destination` (a project library),
where the property name is the library name and the property value is
the path to the library.

## Generate

Generate a specific or all models in a project without the need for a MPS model.

While technically possible generating languages with this task makes little sense as there is no way of packaging the
generated artifacts into JAR files. We only recommend using this for simple tasks where user defined models should be
generated in the CI build or from the commandline.

### Usage

A minimal build script to generate a MPS project with no external plugins would look like this:

```
apply plugin: 'generate-models'
configurations {
mps
}
ext.mpsVersion = '2017.3.5'
generate {
projectLocation = new File("./mps-prj")
mpsConfig = configurations.mps
}
dependencies {
mps "com.jetbrains:mps:$mpsVersion"
}
```

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.
* `plugins` - optional list of plugins to load before generation is attempted.
The notation is `new Plugin("someID", "somePath")`. Where the first parameter is the plugin id and the second the `short (folder) name`.
* `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.
* `models` - optional list of models to generate. If omitted all models in the project will be generated. Only full name
matched are supported and no RegEx or partial name matching.
* `macros` - optional list of path macros. The notation is `new Macro("name", "value")`.
* `projectLocation` - location of the MPS project to generate.
* `debug` - optionally allows to start the JVM that is used to generated with a debugger. Setting it to `true` will cause
the started JVM to suspend until a debugger is attached. Useful for debugging classloading problems or exceptions during
the build.
49 changes: 0 additions & 49 deletions build.gradle

This file was deleted.

83 changes: 83 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import java.net.URI

plugins {
kotlin("jvm") version "1.2.50"
`maven-publish`
groovy
`java-gradle-plugin`
`kotlin-dsl`
}

val versionMajor = 1
val versionMinor = 0

group = "de.itemis.mps"
version = "1.0.0"




val nexusUsername: String? by project
val nexusPassword: String? by project



if (project.hasProperty("forceCI") || project.hasProperty("teamcity")) {
version = de.itemis.mps.gradle.GitBasedVersioning.getVersion(versionMajor, versionMinor)
} else {
version = "$versionMajor.$versionMinor-SNAPSHOT"
}


val mpsConfiguration = configurations.create("mps")


repositories {
mavenCentral()
maven {
url = URI("https://projects.itemis.de/nexus/content/repositories/mbeddr")
}
}


dependencies {
compile(localGroovy())
compile(kotlin("stdlib"))

}

gradlePlugin {
(plugins) {
"generate-models" {
id = "generate-models"
implementationClass = "de.itemis.mps.gradle.generate.GenerateMpsProjectPlugin"
}
}
}

tasks {
"wrapper"(Wrapper::class) {
gradleVersion = "4.8"
distributionType = Wrapper.DistributionType.ALL
}

"setTeamCityBuildNumber" {
doLast {
println("##teamcity[buildNumber '$version']")
}
}
}

publishing {
repositories {
maven {
url = uri("https://projects.itemis.de/nexus/content/repositories/mbeddr")
credentials {
username = nexusUsername
password = nexusPassword
}
}
}
}


59 changes: 59 additions & 0 deletions execute-generators/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import java.net.URI

group = "de.itemis.mps"

plugins {
kotlin("jvm")
`maven-publish`
`java-gradle-plugin`
}

repositories {
mavenCentral()
maven {
url = URI("https://projects.itemis.de/nexus/content/repositories/mbeddr")
}
}

val nexusUsername: String? by project
val nexusPassword: String? by project


//define directories
val artifactsDir = File(buildDir, "artifacts")
val mpsDir = File(artifactsDir, "mps")
val kotlin_argparser_version = "2.0.7"
val pluginVersion = "1"
val mpsVersion = "2017.3.5"


version = if (project.hasProperty("forceCI") || project.hasProperty("teamcity")) {
de.itemis.mps.gradle.GitBasedVersioning.getVersion(mpsVersion, pluginVersion)
} else {
"$mpsVersion.$pluginVersion-SNAPSHOT"
}


val mpsConfiguration = configurations.create("mps")

dependencies {
compile(kotlin("stdlib"))
compile("com.xenomachina:kotlin-argparser:$kotlin_argparser_version")
compileOnly(fileTree(mpsDir).include("**/*.jar"))
mpsConfiguration("com.jetbrains:mps:$mpsVersion")
}


tasks {
val resolveMps by creating(Copy::class) {
from(mpsConfiguration.resolve().map { zipTree(it) })
into(mpsDir)
}

getByName("compileKotlin").dependsOn(resolveMps)
}

tasks.withType<KotlinCompile> {
kotlinOptions.jvmTarget = "1.8"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package de.itemis.mps.gradle.generate

import com.intellij.openapi.util.AsyncResult
import jetbrains.mps.make.MakeSession
import jetbrains.mps.make.facet.FacetRegistry
import jetbrains.mps.make.facet.IFacet
import jetbrains.mps.make.facet.ITarget
import jetbrains.mps.make.script.IScript
import jetbrains.mps.make.script.ScriptBuilder
import jetbrains.mps.messages.IMessage
import jetbrains.mps.messages.IMessageHandler
import jetbrains.mps.messages.MessageKind
import jetbrains.mps.project.Project
import jetbrains.mps.smodel.SLanguageHierarchy
import jetbrains.mps.smodel.language.LanguageRegistry
import jetbrains.mps.smodel.resources.ModelsToResources
import jetbrains.mps.smodel.runtime.MakeAspectDescriptor
import jetbrains.mps.tool.builder.make.BuildMakeService
import org.apache.log4j.Logger
import org.jetbrains.mps.openapi.language.SLanguage
import org.jetbrains.mps.openapi.model.SModel

private val logger = Logger.getLogger("de.itemis.mps.gradle.generate")

private val DEFAULT_FACETS = listOf(
IFacet.Name("jetbrains.mps.lang.core.Generate"),
IFacet.Name("jetbrains.mps.lang.core.TextGen"),
IFacet.Name("jetbrains.mps.make.facets.Make"))

private class MsgHandler : IMessageHandler {
val logger = Logger.getLogger("de.itemis.mps.gradle.generate.messages")
override fun handle(msg: IMessage) {
when (msg.kind) {
MessageKind.INFORMATION -> logger.info(msg.text, msg.exception)
MessageKind.WARNING -> logger.warn(msg.text, msg.exception)
MessageKind.ERROR -> logger.error(msg.text, msg.exception)
}
}

}

private fun createScript(proj: Project, models: List<org.jetbrains.mps.openapi.model.SModel>): IScript {

val allUsedLanguagesAR: AsyncResult<Set<SLanguage>> = AsyncResult()
val registry = LanguageRegistry.getInstance(proj.repository)

proj.modelAccess.runReadAction {
val allDirectlyUsedLanguages = models.map { it.module }.distinct().flatMap { it.usedLanguages }.distinct()
allUsedLanguagesAR.setDone(SLanguageHierarchy(registry, allDirectlyUsedLanguages).extended)
}

val allUsedLanguages = allUsedLanguagesAR.resultSync

val scb = ScriptBuilder()

scb.withFacetNames(allUsedLanguages
.mapNotNull { registry.getLanguage(it) }
.mapNotNull { it.getAspect(MakeAspectDescriptor::class.java) }
.flatMap { it.manifest.facets() }
.map { it.name }
)

scb.withFacetNames(allUsedLanguages
.flatMap { FacetRegistry.getInstance().getFacetsForLanguage(it.qualifiedName) }
.map { it.name }
)

// For some reason MPS doesn't explicitly stat that there is a dependency on Generate, TextGen and Make, so we have
// to make sure they are always included in the set of facets even if for MPS there is no dependency on them.

// todo: not sure if we really need the final target to be Make.make all the time. The code was taken fom #BuildMakeService.defaultMakeScript
return scb.withFacetNames(DEFAULT_FACETS).withFinalTarget(ITarget.Name("jetbrains.mps.make.facets.Make.make")).toScript()
}

private fun makeModels(proj: Project, models: List<org.jetbrains.mps.openapi.model.SModel>) {
val session = MakeSession(proj, MsgHandler(), true)
val res = ModelsToResources(models).resources().toList()
val makeService = BuildMakeService()

if (res.isEmpty()) {
logger.warn("nothing to generate")
return
}
logger.info("starting generation")
val future = makeService.make(session, res, createScript(proj, models))
try {
future.get()
logger.info("generation finished")
} catch (ex: Exception) {
logger.error("failed to generate", ex)
}
}


fun generateProject(parsed: Args, project: Project) {
val ftr = AsyncResult<List<SModel>>()

project.modelAccess.runReadAction {
var modelsToGenerate = project.projectModels
if (parsed.models.isNotEmpty()) {
modelsToGenerate = modelsToGenerate.filter { parsed.models.contains(it.name.longName) }
}
ftr.setDone(modelsToGenerate.toList())
}

val modelsToGenerate = ftr.resultSync

makeModels(project, modelsToGenerate)
}



Loading

0 comments on commit 93a2a08

Please sign in to comment.