Skip to content

Commit

Permalink
feat: support lambdas that access variables outside the lambda
Browse files Browse the repository at this point in the history
  • Loading branch information
slisson committed Nov 1, 2023
1 parent b8901c4 commit bdc7372
Show file tree
Hide file tree
Showing 3 changed files with 24 additions and 9 deletions.
3 changes: 2 additions & 1 deletion build-tools-gradle-test/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,9 @@ mpsBuild {
}

runMPS("runLambdaInMPS") {
val variableOutsideTheLambda = "a"
implementation {
println("Lambda invoked")
println("Lambda invoked: $variableOutsideTheLambda")
}
}
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
package org.modelix.gradle.mpsbuild

import java.io.FileInputStream
import java.io.ObjectInputStream

object InvokeLambda {
const val PROPERTY_KEY = "runMPS.lambda.name"
const val PROPERTY_KEY = "runMPS.lambda.file"

@JvmStatic
fun invoke() {
// System.getProperty(PROPERTY_KEY) would get instrumented which introduces a Gradle dependency
val lambdaName = System::class.java.getMethod("getProperty", String::class.java)
.invoke(null, PROPERTY_KEY) as String
val cls = Class.forName(lambdaName)
val lambdaInstance = cls.getField("INSTANCE").also { it.trySetAccessible() }
.get(null) as () -> Unit
val lambdaFileName = System.getProperty(PROPERTY_KEY)
val lambdaInstance = ObjectInputStream(FileInputStream(lambdaFileName)).use {
it.readObject() as () -> Unit
}
lambdaInstance()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import org.modelix.buildtools.runner.MPSRunner
import org.modelix.buildtools.xmlToString
import org.zeroturnaround.zip.ZipUtil
import java.io.File
import java.io.ObjectOutputStream
import java.nio.file.Path
import java.nio.file.Paths
import java.text.SimpleDateFormat
Expand Down Expand Up @@ -411,14 +412,18 @@ class MPSBuildPlugin : Plugin<Project> {
val exportImplTask: TaskProvider<Task>? = if (mainMethodImpl != null) {
val implJarFile = workDir.resolve(entry.key + "-impl.jar")
val implClass = mainMethodImpl::class.java
val serializedLambdaFile = workDir.resolve(entry.key + "-impl.obj")
runnerConfig = runnerConfig.copy(
classPathElements = runnerConfig.classPathElements + implJarFile,
mainClassName = InvokeLambda::class.java.name,
mainMethodName = InvokeLambda::invoke.name,
jvmArgs = runnerConfig.jvmArgs + "-D${InvokeLambda.PROPERTY_KEY}=${implClass.name}"
jvmArgs = runnerConfig.jvmArgs + "-D${InvokeLambda.PROPERTY_KEY}=${serializedLambdaFile.absolutePath}"
)
project.tasks.register(entry.key + "_exportImpl") {
doLast {
ObjectOutputStream(serializedLambdaFile.outputStream()).use {
it.writeObject(mainMethodImpl)
}
ZipOutputStream(implJarFile.outputStream()).use { zos ->
for (cls in listOf(implClass, InvokeLambda::class.java)) {
zos.putNextEntry(ZipEntry(cls.getResourceName().trimStart('/')))
Expand Down Expand Up @@ -704,9 +709,17 @@ class MPSBuildPlugin : Plugin<Project> {
private fun String.firstLetterUppercase() = if (isEmpty()) this else substring(0, 1).toUpperCase() + drop(1)

private fun Class<*>.readClassBytes(): ByteArray {
// Try to get the non-instrumented original class file first
val classLoaders = generateSequence(classLoader) { it.parent }
for (currentLoader in classLoaders.toList().asReversed()) {
val resource = currentLoader.getResource(getResourceName()) ?: continue
return resource.readBytes()
}

val resource = getResource(getResourceName())
?: throw RuntimeException("Resource ${getResourceName()} not found in $classLoader")
return resource.readBytes()

}

private fun Class<*>.getResourceName() = "/${name.replace(".", "/")}.class"

0 comments on commit bdc7372

Please sign in to comment.