diff --git a/src/main/kotlin/de/itemis/mps/gradle/Common.kt b/src/main/kotlin/de/itemis/mps/gradle/Common.kt index 6cecb983..a06022df 100644 --- a/src/main/kotlin/de/itemis/mps/gradle/Common.kt +++ b/src/main/kotlin/de/itemis/mps/gradle/Common.kt @@ -3,8 +3,17 @@ package de.itemis.mps.gradle import org.apache.log4j.Logger import org.gradle.api.GradleException import org.gradle.api.JavaVersion +import org.gradle.api.Project import org.gradle.api.artifacts.Configuration +import org.gradle.api.tasks.Copy +import org.w3c.dom.Document +import org.w3c.dom.Node import java.io.File +import java.util.zip.ZipFile +import javax.xml.parsers.DocumentBuilderFactory +import javax.xml.xpath.XPath +import javax.xml.xpath.XPathConstants +import javax.xml.xpath.XPathFactory private val logger = Logger.getLogger("de.itemis.mps.gradle.common") @@ -20,6 +29,7 @@ data class Macro( open class BasePluginExtensions { lateinit var mpsConfig: Configuration + var mpsPluginConfig: Configuration? = null var mpsLocation: File? = null var plugins: List = emptyList() var pluginLocation: File? = null @@ -29,7 +39,7 @@ open class BasePluginExtensions { var javaExec: File? = null } -fun validateDefaultJvm(){ +fun validateDefaultJvm() { if (JavaVersion.current() != JavaVersion.VERSION_11) logger.error("MPS requires Java 11 but current JVM uses ${JavaVersion.current()}, starting MPS will most probably fail!") } @@ -48,4 +58,47 @@ fun argsFromBaseExtension(extensions: BasePluginExtensions): MutableList extensions.plugins.map { "--plugin=${it.id}::${it.path}" }.asSequence(), extensions.macros.map { "--macro=${it.name}::${it.value}" }.asSequence(), prj).flatten().toMutableList() -} \ No newline at end of file +} + +fun Project.initializeMpsPluginDependencies(extension: BasePluginExtensions) { + val mpsLocation = extension.mpsLocation ?: File(buildDir, "mps") + extension.pluginLocation = extension.pluginLocation ?: File(mpsLocation, "plugins") + val pluginDeps = (extension.mpsPluginConfig?.dependencies?.asSequence() ?: emptySequence()) + .flatMap { extension.mpsPluginConfig!!.files(it).asSequence() } + .map { toPlugin(it) } + .asSequence() + extension.plugins = sequenceOf( + extension.plugins.asSequence(), + pluginDeps + ).flatten().toList() + + +} + +fun Project.addCopyMpsPluginDepsTask(extension: BasePluginExtensions, dependencies: Any, pluginTail: String) = + tasks.create("copyMpsPluginsFor$pluginTail", Copy::class.java) { + dependsOn(dependencies) + into(extension.pluginLocation!!) + extension.mpsPluginConfig?.asFileTree?.forEach { + from(zipTree(it)) + } + } + +private fun toPlugin(file: File): de.itemis.mps.gradle.Plugin { + return ZipFile(file).use { zipFile -> + zipFile.entries().asSequence() + .first { it.name.endsWith("META-INF/plugin.xml") } + .let { entry -> + val name = entry.name.substring(0, entry.name.indexOf('/')) + val xmlDoc: Document = DocumentBuilderFactory.newInstance() + .newDocumentBuilder() + .parse(zipFile.getInputStream(entry)) + xmlDoc.documentElement.normalize() + val xPath: XPath = XPathFactory.newInstance().newXPath() + val evaluate: Node = + xPath.compile("/idea-plugin/id") + .evaluate(xmlDoc, XPathConstants.NODE) as Node + de.itemis.mps.gradle.Plugin(evaluate.textContent, name) + } + } +} 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 bee7f527..87d1058f 100644 --- a/src/main/kotlin/de/itemis/mps/gradle/generate/Plugin.kt +++ b/src/main/kotlin/de/itemis/mps/gradle/generate/Plugin.kt @@ -1,12 +1,13 @@ package de.itemis.mps.gradle.generate import de.itemis.mps.gradle.BasePluginExtensions +import de.itemis.mps.gradle.addCopyMpsPluginDepsTask import de.itemis.mps.gradle.argsFromBaseExtension +import de.itemis.mps.gradle.initializeMpsPluginDependencies import de.itemis.mps.gradle.validateDefaultJvm import org.gradle.api.GradleException import org.gradle.api.Plugin import org.gradle.api.Project -import org.gradle.api.artifacts.Configuration import org.gradle.api.tasks.Copy import org.gradle.api.tasks.JavaExec import org.gradle.kotlin.dsl.support.zipTo @@ -28,6 +29,8 @@ open class GenerateMpsProjectPlugin : Plugin { val mpsLocation = extension.mpsLocation ?: File(project.buildDir, "mps") afterEvaluate { + initializeMpsPluginDependencies(extension) + val mpsVersion = extension .mpsConfig .resolvedConfiguration @@ -89,8 +92,10 @@ open class GenerateMpsProjectPlugin : Plugin { dependsOn(resolveMps) } + val copyPlugins = addCopyMpsPluginDepsTask(extension, fake, "Generation") + tasks.create("generate", JavaExec::class.java) { - dependsOn(fake) + dependsOn(copyPlugins) args(args) if (extension.javaExec != null) executable(extension.javaExec!!) @@ -110,4 +115,4 @@ open class GenerateMpsProjectPlugin : Plugin { } } } -} \ No newline at end of file +} 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 cb40e3bc..cfde888e 100644 --- a/src/main/kotlin/de/itemis/mps/gradle/modelcheck/Plugin.kt +++ b/src/main/kotlin/de/itemis/mps/gradle/modelcheck/Plugin.kt @@ -1,8 +1,6 @@ package de.itemis.mps.gradle.modelcheck -import de.itemis.mps.gradle.BasePluginExtensions -import de.itemis.mps.gradle.argsFromBaseExtension -import de.itemis.mps.gradle.validateDefaultJvm +import de.itemis.mps.gradle.* import org.gradle.api.GradleException import org.gradle.api.Plugin import org.gradle.api.Project @@ -30,6 +28,7 @@ open class ModelcheckMpsProjectPlugin : Plugin { afterEvaluate { val mpsLocation = extension.mpsLocation ?: File(project.buildDir, "mps") + initializeMpsPluginDependencies(extension) val mpsVersion = extension .mpsConfig @@ -94,8 +93,11 @@ open class ModelcheckMpsProjectPlugin : Plugin { } into(mpsLocation) } + + val copyPlugins = addCopyMpsPluginDepsTask(extension, resolveMps, "Generation") + tasks.create("checkmodels", JavaExec::class.java) { - dependsOn(resolveMps) + dependsOn(copyPlugins) args(args) if (extension.javaExec != null) executable(extension.javaExec!!) diff --git a/src/test/kotlin/GenerateModel.kt b/src/test/kotlin/GenerateModel.kt new file mode 100644 index 00000000..25da0699 --- /dev/null +++ b/src/test/kotlin/GenerateModel.kt @@ -0,0 +1,123 @@ +import de.itemis.mps.gradle.generate.GenerateMpsProjectPlugin +import org.gradle.testkit.runner.GradleRunner +import org.junit.Assert +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TemporaryFolder +import java.io.File + + +class GenerateModelTest { + + @Rule + @JvmField + val testProjectDir: TemporaryFolder = TemporaryFolder() + + private lateinit var settingsFile: File + private lateinit var buildFile: File + private lateinit var cp: List + private lateinit var file: File + + @Before + fun setup() { + val pkgCount = GenerateMpsProjectPlugin::class.java.packageName.count { it == '.' } + file = File(GenerateMpsProjectPlugin::class.java.getResource("").toURI()) + for (i in 0..pkgCount) { + file = file.parentFile + } + + settingsFile = testProjectDir.newFile("settings.gradle") + buildFile = testProjectDir.newFile("build.gradle") + cp = javaClass.classLoader.getResource( + "plugin-classpath.txt")!!.readText().lines().map { File(it) } + testProjectDir.newFolder("buildSrc") + val buildSrcGradle = testProjectDir.newFile("buildSrc/build.gradle") + buildSrcGradle.writeText(""" + plugins { + id 'java-gradle-plugin' + } + dependencies { + implementation files('${file.absolutePath}') + } + gradlePlugin { + plugins { + myPlugins { + id = 'generate-models' + implementationClass = 'de.itemis.mps.gradle.generate.GenerateMpsProjectPlugin' + } + } + } + """.trimIndent()) + } + + @Test + fun `test generate model`() { + val testProjFolder = File(javaClass.classLoader.getResource("test-project")!!.toURI()) + val langPlugin = File(javaClass.classLoader.getResource("zargari-lang.zip")!!.toURI()) + testProjFolder.copyRecursively(testProjectDir.root) + settingsFile.writeText(""" + rootProject.name = "test-project" + """.trimIndent()) + buildFile.writeText(""" + ext.mpsMajor = "2020.1" + ext.mpsMinor = "6" + + buildscript { + dependencies { + classpath files('${file.absolutePath}') + classpath files(${cp.map { """"${it.invariantSeparatorsPath}"""" }.joinToString()}) + } + } + + repositories { + maven { url 'https://projects.itemis.de/nexus/content/repositories/mbeddr' } + jcenter() + ivy { + url "https://download.jetbrains.com/mps/${'$'}mpsMajor/" + layout 'pattern', { + artifact "[module]-[revision].[ext]" + } + metadataSources { // skip downloading ivy.xml + artifact() + } + } + } + + apply plugin: 'generate-models' + + configurations { + mps + mpsPlugin + } + + ext.mpsVersion = '2020.1.6' + + generate { + projectLocation = new File("${testProjectDir.root.absolutePath}") + mpsConfig = configurations.mps + mpsPluginConfig = configurations.mpsPlugin + } + + dependencies { + mps "com.jetbrains:mps:${'$'}mpsVersion" + mpsPlugin files('${langPlugin.absolutePath}') + } + """.trimIndent()) + + GradleRunner.create() + .withProjectDir(testProjectDir.root) + .withArguments("generate") + .withPluginClasspath(cp) + .build() + val generatedSrcDir = testProjectDir.root + .resolve("solutions") + .resolve("ZargariSolution") + .resolve("source_gen") + .resolve("ZargariSolution") + .resolve("java") + Assert.assertTrue(generatedSrcDir.exists()) + Assert.assertTrue(generatedSrcDir.resolve("MyClass.java").exists()) + } +} + diff --git a/src/test/resources/test-project/.mps/modules.xml b/src/test/resources/test-project/.mps/modules.xml new file mode 100644 index 00000000..6edf4f1a --- /dev/null +++ b/src/test/resources/test-project/.mps/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/src/test/resources/test-project/solutions/ZargariSolution/ZargariSolution.msd b/src/test/resources/test-project/solutions/ZargariSolution/ZargariSolution.msd new file mode 100644 index 00000000..2673a952 --- /dev/null +++ b/src/test/resources/test-project/solutions/ZargariSolution/ZargariSolution.msd @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/test/resources/test-project/solutions/ZargariSolution/models/ZargariSolution.java.mps b/src/test/resources/test-project/solutions/ZargariSolution/models/ZargariSolution.java.mps new file mode 100644 index 00000000..34230850 --- /dev/null +++ b/src/test/resources/test-project/solutions/ZargariSolution/models/ZargariSolution.java.mps @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/resources/zargari-lang.zip b/src/test/resources/zargari-lang.zip new file mode 100644 index 00000000..25f1b8aa Binary files /dev/null and b/src/test/resources/zargari-lang.zip differ