Run unit tests with truffle-compiler (#8467)

This commit is contained in:
Pavel Marek 2023-12-18 18:22:16 +01:00 committed by GitHub
parent b2a1cd55d2
commit 21d164ec3e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
47 changed files with 900 additions and 472 deletions

461
build.sbt
View File

@ -12,9 +12,14 @@ import src.main.scala.licenses.{
DistributionDescription, DistributionDescription,
SBTDistributionComponent SBTDistributionComponent
} }
import com.sandinh.javamodule.moduleinfo.JpmsModule import sbt.librarymanagement.DependencyFilter
// This import is unnecessary, but bit adds a proper code completion features
// to IntelliJ.
import JPMSPlugin.autoImport.*
import java.io.File import java.io.File
import scala.collection.mutable.ListBuffer
// ============================================================================ // ============================================================================
// === Global Configuration =================================================== // === Global Configuration ===================================================
@ -295,8 +300,6 @@ lazy val enso = (project in file("."))
`runtime-instrument-id-execution`, `runtime-instrument-id-execution`,
`runtime-instrument-repl-debugger`, `runtime-instrument-repl-debugger`,
`runtime-instrument-runtime-server`, `runtime-instrument-runtime-server`,
`runtime-with-instruments`,
`runtime-with-polyglot`,
`runtime-version-manager`, `runtime-version-manager`,
`runtime-version-manager-test`, `runtime-version-manager-test`,
editions, editions,
@ -519,6 +522,8 @@ lazy val componentModulesPaths =
) )
} }
lazy val compileModuleInfo = taskKey[Unit]("Compiles `module-info.java`")
// ============================================================================ // ============================================================================
// === Internal Libraries ===================================================== // === Internal Libraries =====================================================
// ============================================================================ // ============================================================================
@ -879,6 +884,7 @@ lazy val `refactoring-utils` = project
.dependsOn(testkit % Test) .dependsOn(testkit % Test)
lazy val `project-manager` = (project in file("lib/scala/project-manager")) lazy val `project-manager` = (project in file("lib/scala/project-manager"))
.enablePlugins(JPMSPlugin)
.settings( .settings(
(Compile / mainClass) := Some("org.enso.projectmanager.boot.ProjectManager") (Compile / mainClass) := Some("org.enso.projectmanager.boot.ProjectManager")
) )
@ -907,6 +913,8 @@ lazy val `project-manager` = (project in file("lib/scala/project-manager"))
"org.typelevel" %% "kind-projector" % kindProjectorVersion cross CrossVersion.full "org.typelevel" %% "kind-projector" % kindProjectorVersion cross CrossVersion.full
) )
) )
/** Fat jar assembly settings
*/
.settings( .settings(
assembly / assemblyJarName := "project-manager.jar", assembly / assemblyJarName := "project-manager.jar",
assembly / test := {}, assembly / test := {},
@ -934,26 +942,42 @@ lazy val `project-manager` = (project in file("lib/scala/project-manager"))
case "application.conf" => MergeStrategy.concat case "application.conf" => MergeStrategy.concat
case "reference.conf" => MergeStrategy.concat case "reference.conf" => MergeStrategy.concat
case _ => MergeStrategy.first case _ => MergeStrategy.first
}
)
/** JPMS related settings for tests
*/
.settings(
Test / fork := true,
// These dependencies are here so that we can use them in `--module-path` later on.
libraryDependencies ++= {
val necessaryModules =
GraalVM.modules.map(_.withConfigurations(Some(Test.name))) ++
GraalVM.langsPkgs.map(_.withConfigurations(Some(Test.name)))
necessaryModules
}, },
Test / javaOptions ++= Test / addModules := Seq(
Seq( (`runtime-fat-jar` / javaModuleName).value
"-Dpolyglot.engine.WarnInterpreterOnly=false", ),
"-Dpolyglotimpl.DisableClassPathIsolation=true", Test / modulePath := {
s"-Dconfig.file=${sourceDirectory.value}/test/resources/application.conf", val updateReport = (Test / update).value
"-Dslf4j.provider=ch.qos.logback.classic.spi.LogbackServiceProvider" val requiredModIds =
), GraalVM.modules ++ GraalVM.langsPkgs ++ logbackPkg ++ Seq(
// Append enso language on the class-path "org.slf4j" % "slf4j-api" % slf4jVersion
Test / unmanagedClasspath := )
(LocalProject( val requiredMods = JPMSUtils.filterModulesFromUpdate(
"runtime-with-instruments" updateReport,
) / Compile / fullClasspath).value, requiredModIds,
// In project-manager tests, we test installing projects and for that, we need streams.value.log,
// to launch engine-runner properly. For that, we need all the JARs that we shouldContainAll = true
// normally use in engine distribution. That is why there is dependency on )
// `buildEngineDistributionNoIndex`. val runtimeMod =
(Test / test) := (Test / test) (`runtime-fat-jar` / Compile / productDirectories).value.head
.dependsOn(buildEngineDistributionNoIndex)
.value, requiredMods ++ Seq(runtimeMod)
},
Test / javaOptions ++= testLogProviderOptions
)
.settings(
rebuildNativeImage := NativeImage rebuildNativeImage := NativeImage
.buildNativeImage( .buildNativeImage(
"project-manager", "project-manager",
@ -1201,7 +1225,7 @@ lazy val `polyglot-api` = project
// Append enso language on the class-path // Append enso language on the class-path
Test / unmanagedClasspath := Test / unmanagedClasspath :=
(LocalProject( (LocalProject(
"runtime-with-instruments" "runtime-fat-jar"
) / Compile / fullClasspath).value, ) / Compile / fullClasspath).value,
libraryDependencies ++= Seq( libraryDependencies ++= Seq(
"org.graalvm.sdk" % "polyglot-tck" % graalMavenPackagesVersion % "provided", "org.graalvm.sdk" % "polyglot-tck" % graalMavenPackagesVersion % "provided",
@ -1220,6 +1244,7 @@ lazy val `polyglot-api` = project
.dependsOn(testkit % Test) .dependsOn(testkit % Test)
lazy val `language-server` = (project in file("engine/language-server")) lazy val `language-server` = (project in file("engine/language-server"))
.enablePlugins(JPMSPlugin)
.settings( .settings(
commands += WithDebugCommand.withDebug, commands += WithDebugCommand.withDebug,
frgaalJavaCompilerSetting, frgaalJavaCompilerSetting,
@ -1237,7 +1262,11 @@ lazy val `language-server` = (project in file("engine/language-server"))
"org.scalatest" %% "scalatest" % scalatestVersion % Test, "org.scalatest" %% "scalatest" % scalatestVersion % Test,
"org.scalacheck" %% "scalacheck" % scalacheckVersion % Test, "org.scalacheck" %% "scalacheck" % scalacheckVersion % Test,
"org.graalvm.sdk" % "polyglot-tck" % graalMavenPackagesVersion % "provided", "org.graalvm.sdk" % "polyglot-tck" % graalMavenPackagesVersion % "provided",
"org.eclipse.jgit" % "org.eclipse.jgit" % jgitVersion "org.eclipse.jgit" % "org.eclipse.jgit" % jgitVersion,
"org.bouncycastle" % "bcutil-jdk18on" % "1.76" % Test,
"org.bouncycastle" % "bcpkix-jdk18on" % "1.76" % Test,
"org.bouncycastle" % "bcprov-jdk18on" % "1.76" % Test,
"org.apache.tika" % "tika-core" % tikaVersion % Test
), ),
Test / testOptions += Tests Test / testOptions += Tests
.Argument(TestFrameworks.ScalaCheck, "-minSuccessfulTests", "1000"), .Argument(TestFrameworks.ScalaCheck, "-minSuccessfulTests", "1000"),
@ -1256,17 +1285,93 @@ lazy val `language-server` = (project in file("engine/language-server"))
) )
) )
.settings( .settings(
// These settings are needed by language-server tests that create a runtime context.
Test / fork := true, Test / fork := true,
Test / javaOptions ++= testLogProviderOptions ++ Seq( // These dependencies are here so that we can use them in `--module-path` later on.
"-Dpolyglot.engine.WarnInterpreterOnly=false", libraryDependencies ++= {
"-Dpolyglotimpl.DisableClassPathIsolation=true" val necessaryModules =
GraalVM.modules.map(_.withConfigurations(Some(Test.name))) ++
GraalVM.langsPkgs.map(_.withConfigurations(Some(Test.name)))
necessaryModules
},
Test / addModules := Seq(
(`runtime-fat-jar` / javaModuleName).value
), ),
// Append enso language on the class-path Test / modulePath := {
Test / unmanagedClasspath := val updateReport = (Test / update).value
(LocalProject( // org.bouncycastle is a module required by `org.enso.runtime` module.
"runtime-with-instruments" val requiredModIds =
) / Compile / fullClasspath).value, GraalVM.modules ++ GraalVM.langsPkgs ++ logbackPkg ++ Seq(
"org.slf4j" % "slf4j-api" % slf4jVersion,
"org.bouncycastle" % "bcutil-jdk18on" % "1.76",
"org.bouncycastle" % "bcpkix-jdk18on" % "1.76",
"org.bouncycastle" % "bcprov-jdk18on" % "1.76"
)
val requiredMods = JPMSUtils.filterModulesFromUpdate(
updateReport,
requiredModIds,
streams.value.log,
shouldContainAll = true
)
val runtimeMod =
(`runtime-fat-jar` / Compile / productDirectories).value.head
requiredMods ++ Seq(runtimeMod)
},
Test / javaOptions ++= testLogProviderOptions,
Test / patchModules := {
/** All these modules will be in --patch-module cmdline option to java, which means that
* for the JVM, it will appear that all the classes contained in these sbt projects are contained
* in the `org.enso.runtime` module. In this way, we do not have to assembly the `runtime.jar`
* fat jar.
*/
val modulesToPatchIntoRuntime: Seq[File] =
(LocalProject(
"runtime-instrument-common"
) / Compile / productDirectories).value ++
(LocalProject(
"runtime-instrument-id-execution"
) / Compile / productDirectories).value ++
(LocalProject(
"runtime-instrument-repl-debugger"
) / Compile / productDirectories).value ++
(LocalProject(
"runtime-instrument-runtime-server"
) / Compile / productDirectories).value ++
(LocalProject(
"runtime-language-epb"
) / Compile / productDirectories).value ++
(LocalProject(
"runtime-compiler"
) / Compile / productDirectories).value ++
(LocalProject("runtime-parser") / Compile / productDirectories).value ++
(LocalProject(
"interpreter-dsl"
) / Compile / productDirectories).value ++
// We have to patch the `runtime` project as well, as it contains BuiltinTypes.metadata in
// runtime/target/classes/META-INF directory
(LocalProject("runtime") / Compile / productDirectories).value ++
(LocalProject(
"syntax-rust-definition"
) / Compile / productDirectories).value
val extraModsToPatch = JPMSUtils.filterModulesFromUpdate(
(Test / update).value,
Seq(
"org.apache.tika" % "tika-core" % tikaVersion
),
streams.value.log,
shouldContainAll = true
)
Map(
(`runtime-fat-jar` / javaModuleName).value -> (modulesToPatchIntoRuntime ++ extraModsToPatch)
)
},
Test / addReads := {
Map(
(`runtime-fat-jar` / javaModuleName).value -> Seq("ALL-UNNAMED")
)
}
)
.settings(
Test / compile := (Test / compile) Test / compile := (Test / compile)
.dependsOn(LocalProject("enso") / updateLibraryManifests) .dependsOn(LocalProject("enso") / updateLibraryManifests)
.value, .value,
@ -1388,8 +1493,37 @@ lazy val `runtime-language-epb` =
) )
) )
/** `runtime-test-instruments` project contains Truffle instruments that are used solely for testing.
* It is compiled into an explicit Java module. Note that this project cannot have compile-time dependency on `runtime`
* project, so if you need access to classes from `runtime`, you need to use reflection.
*/
lazy val `runtime-test-instruments` =
(project in file("engine/runtime-test-instruments"))
.enablePlugins(JPMSPlugin)
.settings(
inConfig(Compile)(truffleRunOptionsSettings),
truffleDslSuppressWarnsSetting,
instrumentationSettings,
javaModuleName := "org.enso.runtime.test",
modulePath := {
JPMSUtils.filterModulesFromUpdate(
update.value,
GraalVM.modules ++ Seq(
"org.netbeans.api" % "org-openide-util-lookup" % netbeansApiVersion
),
streams.value.log,
shouldContainAll = true
)
},
libraryDependencies ++= GraalVM.modules,
libraryDependencies ++= Seq(
"org.netbeans.api" % "org-openide-util-lookup" % netbeansApiVersion % "provided"
)
)
lazy val runtime = (project in file("engine/runtime")) lazy val runtime = (project in file("engine/runtime"))
.configs(Benchmark) .configs(Benchmark)
.enablePlugins(JPMSPlugin)
.settings( .settings(
frgaalJavaCompilerSetting, frgaalJavaCompilerSetting,
truffleDslSuppressWarnsSetting, truffleDslSuppressWarnsSetting,
@ -1426,7 +1560,8 @@ lazy val runtime = (project in file("engine/runtime"))
"junit" % "junit" % junitVersion % Test, "junit" % "junit" % junitVersion % Test,
"com.github.sbt" % "junit-interface" % junitIfVersion % Test, "com.github.sbt" % "junit-interface" % junitIfVersion % Test,
"org.hamcrest" % "hamcrest-all" % hamcrestVersion % Test, "org.hamcrest" % "hamcrest-all" % hamcrestVersion % Test,
"org.slf4j" % "slf4j-nop" % slf4jVersion % Benchmark "org.slf4j" % "slf4j-nop" % slf4jVersion % Benchmark,
"org.slf4j" % "slf4j-api" % slf4jVersion % Test
), ),
// Add all GraalVM packages with Runtime scope - we don't need them for compilation, // Add all GraalVM packages with Runtime scope - we don't need them for compilation,
// just provide them at runtime (in module-path). // just provide them at runtime (in module-path).
@ -1438,10 +1573,87 @@ lazy val runtime = (project in file("engine/runtime"))
val tools = val tools =
GraalVM.toolsPkgs.map(_.withConfigurations(Some(Runtime.name))) GraalVM.toolsPkgs.map(_.withConfigurations(Some(Runtime.name)))
necessaryModules ++ langs ++ tools necessaryModules ++ langs ++ tools
}, }
Test / javaOptions ++= testLogProviderOptions ++ Seq( )
"-Dpolyglotimpl.DisableClassPathIsolation=true" .settings(
Test / unmanagedClasspath := (LocalProject(
"runtime-fat-jar"
) / Compile / exportedProducts).value,
Test / addModules := Seq(
(`runtime-test-instruments` / javaModuleName).value,
(`runtime-fat-jar` / javaModuleName).value
), ),
Test / modulePath := {
val updateReport = (Test / update).value
val requiredModIds =
GraalVM.modules ++ GraalVM.langsPkgs ++ GraalVM.insightPkgs ++ logbackPkg ++ Seq(
"org.slf4j" % "slf4j-api" % slf4jVersion,
"org.netbeans.api" % "org-openide-util-lookup" % netbeansApiVersion
)
val requiredMods = JPMSUtils.filterModulesFromUpdate(
updateReport,
requiredModIds,
streams.value.log,
shouldContainAll = true
)
val runtimeTestInstrumentsMod =
(`runtime-test-instruments` / Compile / exportedProducts).value.head.data
val runtimeMod =
(`runtime-fat-jar` / Compile / exportedProducts).value.head.data
requiredMods ++
Seq(runtimeTestInstrumentsMod) ++
Seq(runtimeMod)
},
Test / patchModules := {
/** All these modules will be in --patch-module cmdline option to java, which means that
* for the JVM, it will appear that all the classes contained in these sbt projects are contained
* in the `org.enso.runtime` module. In this way, we do not have to assembly the `runtime.jar`
* fat jar.
*/
val modulesToPatchIntoRuntime: Seq[File] =
(LocalProject(
"runtime-instrument-common"
) / Compile / productDirectories).value ++
(LocalProject(
"runtime-instrument-id-execution"
) / Compile / productDirectories).value ++
(LocalProject(
"runtime-instrument-repl-debugger"
) / Compile / productDirectories).value ++
(LocalProject(
"runtime-instrument-runtime-server"
) / Compile / productDirectories).value ++
(LocalProject(
"runtime-language-epb"
) / Compile / productDirectories).value ++
(LocalProject(
"runtime-compiler"
) / Compile / productDirectories).value ++
(LocalProject("refactoring-utils") / Compile / productDirectories).value
// Patch test-classes into the runtime module. This is standard way to deal with the
// split package problem in unit tests. For example, Maven's surefire plugin does this.
val testClassesDir = (Test / productDirectories).value.head
Map(
(`runtime-fat-jar` / javaModuleName).value -> (modulesToPatchIntoRuntime ++ Seq(
testClassesDir
))
)
},
Test / addReads := {
val runtimeModName = (`runtime-fat-jar` / javaModuleName).value
val testInstrumentsModName =
(`runtime-test-instruments` / javaModuleName).value
Map(
// We patched the test-classes into the runtime module. These classes access some stuff from
// unnamed module. Thus, let's add ALL-UNNAMED.
runtimeModName -> Seq("ALL-UNNAMED", testInstrumentsModName),
testInstrumentsModName -> Seq(runtimeModName)
)
},
Test / javaOptions ++= testLogProviderOptions
)
.settings(
Test / fork := true, Test / fork := true,
Test / envVars ++= distributionEnvironmentOverrides ++ Map( Test / envVars ++= distributionEnvironmentOverrides ++ Map(
"ENSO_TEST_DISABLE_IR_CACHE" -> "false" "ENSO_TEST_DISABLE_IR_CACHE" -> "false"
@ -1479,7 +1691,7 @@ lazy val runtime = (project in file("engine/runtime"))
// runtime.jar fat jar needs to be assembled as it is used in the // runtime.jar fat jar needs to be assembled as it is used in the
// benchmarks. This dependency is here so that `runtime/bench` works // benchmarks. This dependency is here so that `runtime/bench` works
// after clean build. // after clean build.
LocalProject("runtime-with-instruments") / assembly LocalProject("runtime-fat-jar") / assembly
) )
.value, .value,
benchOnly := Def.inputTaskDyn { benchOnly := Def.inputTaskDyn {
@ -1519,6 +1731,7 @@ lazy val runtime = (project in file("engine/runtime"))
.dependsOn(`connected-lock-manager`) .dependsOn(`connected-lock-manager`)
.dependsOn(testkit % Test) .dependsOn(testkit % Test)
.dependsOn(`logging-service-logback` % "test->test") .dependsOn(`logging-service-logback` % "test->test")
.dependsOn(`runtime-test-instruments` % "test->compile")
lazy val `runtime-parser` = lazy val `runtime-parser` =
(project in file("engine/runtime-parser")) (project in file("engine/runtime-parser"))
@ -1583,7 +1796,9 @@ lazy val `runtime-instrument-common` =
) )
.dependsOn(`refactoring-utils`) .dependsOn(`refactoring-utils`)
.dependsOn( .dependsOn(
runtime % "compile->compile;test->test;runtime->runtime;bench->bench" LocalProject(
"runtime"
) % "compile->compile;test->test;runtime->runtime;bench->bench"
) )
lazy val `runtime-instrument-id-execution` = lazy val `runtime-instrument-id-execution` =
@ -1593,7 +1808,7 @@ lazy val `runtime-instrument-id-execution` =
inConfig(Compile)(truffleRunOptionsSettings), inConfig(Compile)(truffleRunOptionsSettings),
instrumentationSettings instrumentationSettings
) )
.dependsOn(runtime) .dependsOn(LocalProject("runtime"))
.dependsOn(`runtime-instrument-common`) .dependsOn(`runtime-instrument-common`)
lazy val `runtime-instrument-repl-debugger` = lazy val `runtime-instrument-repl-debugger` =
@ -1602,7 +1817,7 @@ lazy val `runtime-instrument-repl-debugger` =
inConfig(Compile)(truffleRunOptionsSettings), inConfig(Compile)(truffleRunOptionsSettings),
instrumentationSettings instrumentationSettings
) )
.dependsOn(runtime) .dependsOn(LocalProject("runtime"))
.dependsOn(`runtime-instrument-common`) .dependsOn(`runtime-instrument-common`)
lazy val `runtime-instrument-runtime-server` = lazy val `runtime-instrument-runtime-server` =
@ -1611,65 +1826,64 @@ lazy val `runtime-instrument-runtime-server` =
inConfig(Compile)(truffleRunOptionsSettings), inConfig(Compile)(truffleRunOptionsSettings),
instrumentationSettings instrumentationSettings
) )
.dependsOn(runtime) .dependsOn(LocalProject("runtime"))
.dependsOn(`runtime-instrument-common`) .dependsOn(`runtime-instrument-common`)
lazy val `runtime-with-instruments` = /** A "meta" project that exists solely to provide logic for assembling the `runtime.jar` fat Jar.
(project in file("engine/runtime-with-instruments")) * We do not want to put this task into any other existing project, as it internally copies some
.configs(Benchmark) * classes from other projects into the `classes` directory, therefore, pollutes the build.
* There is only one Java source in this project - `module-info.java`. During the assembling of the
* fat jar, all the classes from the dependent projects are copied into the `classes` directory of
* this project and then, a custom task is invoked to compile the `module-info.java`.
*/
lazy val `runtime-fat-jar` =
(project in file("engine/runtime-fat-jar"))
.enablePlugins(JPMSPlugin)
.settings( .settings(
frgaalJavaCompilerSetting, Compile / compileModuleInfo := {
inConfig(Compile)(truffleRunOptionsSettings), JPMSUtils.compileModuleInfo(
commands += WithDebugCommand.withDebug, copyDepsFilter = ScopeFilter(
Test / javaOptions ++= testLogProviderOptions ++ Seq( inProjects(
"-Dpolyglotimpl.DisableClassPathIsolation=true" LocalProject("runtime"),
), LocalProject("runtime-language-epb"),
Test / fork := true, LocalProject("runtime-instrument-common"),
Test / envVars ++= distributionEnvironmentOverrides ++ Map( LocalProject("runtime-instrument-id-execution"),
"ENSO_TEST_DISABLE_IR_CACHE" -> "false" LocalProject("runtime-instrument-repl-debugger"),
), LocalProject("runtime-instrument-runtime-server")
libraryDependencies ++= Seq( ),
"org.scalatest" %% "scalatest" % scalatestVersion % Test, inConfigurations(Compile)
"org.graalvm.truffle" % "truffle-api" % graalMavenPackagesVersion % Test, ),
"org.graalvm.truffle" % "truffle-dsl-processor" % graalMavenPackagesVersion % Test, modulePath = JPMSUtils.componentModules
"org.slf4j" % "slf4j-nop" % slf4jVersion % Benchmark )
), }
// Add all GraalVM packages with Runtime scope - we don't need them for compilation, .dependsOn(Compile / compile)
// just provide them at runtime (in module-path). .value,
libraryDependencies ++= {
val necessaryModules =
GraalVM.modules.map(_.withConfigurations(Some(Runtime.name)))
val langs =
GraalVM.langsPkgs.map(_.withConfigurations(Some(Runtime.name)))
necessaryModules ++ langs
},
// Note [Unmanaged Classpath]
Test / unmanagedClasspath += (baseDirectory.value / ".." / ".." / "app" / "gui" / "view" / "graph-editor" / "src" / "builtin" / "visualization" / "native" / "inc"),
// Filter module-info.java from the compilation // Filter module-info.java from the compilation
excludeFilter := excludeFilter.value || "module-info.java", excludeFilter := excludeFilter.value || "module-info.java",
moduleInfos := Seq( javaModuleName := "org.enso.runtime",
JpmsModule("org.enso.runtime") compileOrder := CompileOrder.JavaThenScala
) )
/** The following libraryDependencies are provided in Runtime scope.
* Later, we will collect them into --module-path option.
* We don't collect them in Compile scope as it does not even make sense
* to run `compile` task in this project.
*/
.settings(
libraryDependencies ++= {
val graalMods =
GraalVM.modules.map(_.withConfigurations(Some(Runtime.name)))
val langMods =
GraalVM.langsPkgs.map(_.withConfigurations(Some(Runtime.name)))
val logbackMods =
logbackPkg.map(_.withConfigurations(Some(Runtime.name)))
graalMods ++ langMods ++ logbackMods
}
) )
/** Assembling Uber Jar */ /** Assembling Uber Jar */
.settings( .settings(
assembly := assembly assembly := assembly
.dependsOn( .dependsOn(Compile / compile)
JPMSUtils.compileModuleInfo( .dependsOn(Compile / compileModuleInfo)
copyDepsFilter = ScopeFilter(
inProjects(
LocalProject("runtime"),
LocalProject("runtime-language-epb"),
LocalProject("runtime-instrument-common"),
LocalProject("runtime-instrument-id-execution"),
LocalProject("runtime-instrument-repl-debugger"),
LocalProject("runtime-instrument-runtime-server")
),
inConfigurations(Compile)
),
modulePath = JPMSUtils.componentModules
)
)
.value, .value,
assembly / assemblyJarName := "runtime.jar", assembly / assemblyJarName := "runtime.jar",
assembly / test := {}, assembly / test := {},
@ -1704,61 +1918,12 @@ lazy val `runtime-with-instruments` =
case _ => MergeStrategy.first case _ => MergeStrategy.first
} }
) )
/** Benchmark settings */
.settings(
inConfig(Benchmark)(Defaults.testSettings),
Benchmark / javacOptions --= Seq(
"-source",
frgaalSourceLevel,
"--enable-preview"
),
(Benchmark / javaOptions) :=
(LocalProject("std-benchmarks") / Benchmark / run / javaOptions).value
)
.dependsOn(runtime % "compile->compile;test->test;runtime->runtime")
.dependsOn(`runtime-instrument-common`) .dependsOn(`runtime-instrument-common`)
.dependsOn(`runtime-instrument-id-execution`) .dependsOn(`runtime-instrument-id-execution`)
.dependsOn(`runtime-instrument-repl-debugger`) .dependsOn(`runtime-instrument-repl-debugger`)
.dependsOn(`runtime-instrument-runtime-server`) .dependsOn(`runtime-instrument-runtime-server`)
.dependsOn(`runtime-language-epb`) .dependsOn(`runtime-language-epb`)
.dependsOn(`logging-service-logback` % "test->test") .dependsOn(LocalProject("runtime"))
/* runtime-with-polyglot
* ~~~~~~~~~~~~~~~~~~~~~
* Unlike `runtime`, this project includes the truffle language JARs on the
* class-path.
*/
lazy val `runtime-with-polyglot` =
(project in file("engine/runtime-with-polyglot"))
.configs(Benchmark)
.settings(
frgaalJavaCompilerSetting,
inConfig(Compile)(truffleRunOptionsSettings),
inConfig(Benchmark)(Defaults.testSettings),
commands += WithDebugCommand.withDebug,
Benchmark / javacOptions --= Seq(
"-source",
frgaalSourceLevel,
"--enable-preview"
),
Test / javaOptions ++= testLogProviderOptions ++ Seq(
"-Dpolyglotimpl.DisableClassPathIsolation=true"
),
Test / fork := true,
Test / envVars ++= distributionEnvironmentOverrides ++ Map(
"ENSO_TEST_DISABLE_IR_CACHE" -> "false"
),
libraryDependencies ++= GraalVM.langsPkgs ++ Seq(
"org.graalvm.polyglot" % "polyglot" % graalMavenPackagesVersion % "provided",
"org.graalvm.tools" % "insight-tool" % graalMavenPackagesVersion % "provided",
"org.scalatest" %% "scalatest" % scalatestVersion % Test
),
(Benchmark / javaOptions) :=
(LocalProject("std-benchmarks") / Benchmark / run / javaOptions).value
)
.dependsOn(runtime % "compile->compile;test->test;runtime->runtime")
.dependsOn(`runtime-with-instruments`)
/* Note [Unmanaged Classpath] /* Note [Unmanaged Classpath]
* ~~~~~~~~~~~~~~~~~~~~~~~~~~ * ~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -1833,7 +1998,7 @@ lazy val `engine-runner` = project
) )
.settings( .settings(
assembly := assembly assembly := assembly
.dependsOn(`runtime-with-instruments` / assembly) .dependsOn(`runtime-fat-jar` / assembly)
.value, .value,
rebuildNativeImage := rebuildNativeImage :=
NativeImage NativeImage
@ -2016,7 +2181,7 @@ lazy val `bench-processor` = (project in file("lib/scala/bench-processor"))
), ),
// Append enso language on the class-path // Append enso language on the class-path
(Test / unmanagedClasspath) := (Test / unmanagedClasspath) :=
(LocalProject("runtime-with-instruments") / Compile / fullClasspath).value (LocalProject("runtime-fat-jar") / Compile / fullClasspath).value
) )
.dependsOn(`polyglot-api`) .dependsOn(`polyglot-api`)
.dependsOn(runtime) .dependsOn(runtime)
@ -2055,11 +2220,11 @@ lazy val `std-benchmarks` = (project in file("std-bits/benchmarks"))
(Benchmark / parallelExecution) := false, (Benchmark / parallelExecution) := false,
(Benchmark / run / fork) := true, (Benchmark / run / fork) := true,
(Benchmark / run / connectInput) := true, (Benchmark / run / connectInput) := true,
// This ensures that the full class-path of runtime-with-instruments is put on // This ensures that the full class-path of runtime-fat-jar is put on
// class-path of the Java compiler (and thus the benchmark annotation processor). // class-path of the Java compiler (and thus the benchmark annotation processor).
(Benchmark / compile / unmanagedClasspath) ++= (Benchmark / compile / unmanagedClasspath) ++=
(LocalProject( (LocalProject(
"runtime-with-instruments" "runtime-fat-jar"
) / Compile / fullClasspath).value, ) / Compile / fullClasspath).value,
(Benchmark / compile / javacOptions) ++= Seq( (Benchmark / compile / javacOptions) ++= Seq(
"-s", "-s",
@ -2083,13 +2248,13 @@ lazy val `std-benchmarks` = (project in file("std-bits/benchmarks"))
.map(_.data.getAbsolutePath) .map(_.data.getAbsolutePath)
val runtimeJar = val runtimeJar =
(LocalProject( (LocalProject(
"runtime-with-instruments" "runtime-fat-jar"
) / assembly / assemblyOutputPath).value.getAbsolutePath ) / assembly / assemblyOutputPath).value.getAbsolutePath
val allModulePaths = requiredModulesPaths ++ Seq(runtimeJar) val allModulePaths = requiredModulesPaths ++ Seq(runtimeJar)
val runtimeModuleName = val runtimeModuleName =
(LocalProject( (LocalProject(
"runtime-with-instruments" "runtime-fat-jar"
) / moduleInfos).value.head.moduleName ) / javaModuleName).value
Seq( Seq(
// To enable logging in benchmarks, add ch.qos.logback module on the modulePath // To enable logging in benchmarks, add ch.qos.logback module on the modulePath
"-Dslf4j.provider=org.slf4j.nop.NOPServiceProvider", "-Dslf4j.provider=org.slf4j.nop.NOPServiceProvider",

View File

@ -0,0 +1,12 @@
module org.enso.runtime.test {
requires org.graalvm.truffle;
requires org.openide.util.lookup.RELEASE180;
exports org.enso.interpreter.test.instruments;
exports org.enso.interpreter.test.instruments.service;
provides com.oracle.truffle.api.instrumentation.provider.TruffleInstrumentProvider with
org.enso.interpreter.test.instruments.CodeIdsTestInstrumentProvider,
org.enso.interpreter.test.instruments.CodeLocationsTestInstrumentProvider,
org.enso.interpreter.test.instruments.NodeCountingTestInstrumentProvider;
}

View File

@ -1,13 +1,14 @@
package org.enso.interpreter.test; package org.enso.interpreter.test.instruments;
import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.instrumentation.*; import com.oracle.truffle.api.instrumentation.*;
import com.oracle.truffle.api.nodes.Node; import com.oracle.truffle.api.nodes.Node;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
import org.enso.interpreter.node.ExpressionNode; import org.enso.interpreter.test.instruments.service.RuntimeTestService;
import org.enso.interpreter.runtime.control.TailCallException; import org.openide.util.Lookup;
/** /**
* A debug instrument used to test code locations. * A debug instrument used to test code locations.
@ -20,8 +21,13 @@ import org.enso.interpreter.runtime.control.TailCallException;
services = CodeIdsTestInstrument.class) services = CodeIdsTestInstrument.class)
public class CodeIdsTestInstrument extends TruffleInstrument { public class CodeIdsTestInstrument extends TruffleInstrument {
public static final String INSTRUMENT_ID = "ids-test"; public static final String INSTRUMENT_ID = "ids-test";
private static final RuntimeTestService runtimeTestService;
private Env env; private Env env;
static {
runtimeTestService = Lookup.getDefault().lookup(RuntimeTestService.class);
}
/** /**
* Initializes the instrument. Substitute for a constructor, called by the Truffle framework. * Initializes the instrument. Substitute for a constructor, called by the Truffle framework.
* *
@ -94,7 +100,6 @@ public class CodeIdsTestInstrument extends TruffleInstrument {
/** /**
* Checks if the node to be executed is the node this listener was created to observe. * Checks if the node to be executed is the node this listener was created to observe.
* *
* @param context current execution context
* @param frame current execution frame * @param frame current execution frame
* @param result the result of executing the node * @param result the result of executing the node
*/ */
@ -104,11 +109,11 @@ public class CodeIdsTestInstrument extends TruffleInstrument {
return; return;
} }
Node node = context.getInstrumentedNode(); Node node = context.getInstrumentedNode();
if (!(node instanceof ExpressionNode)) { if (!runtimeTestService.isExpressionNode(node)) {
return; return;
} }
nodes.put(this, result); nodes.put(this, result);
UUID id = ((ExpressionNode) node).getId(); UUID id = runtimeTestService.getNodeID(node);
if (id == null || !id.equals(expectedId)) { if (id == null || !id.equals(expectedId)) {
return; return;
} }
@ -120,19 +125,18 @@ public class CodeIdsTestInstrument extends TruffleInstrument {
/** /**
* Checks if the specified was called, if its execution triggered TCO. * Checks if the specified was called, if its execution triggered TCO.
* *
* @param context current execution context.
* @param frame current execution frame. * @param frame current execution frame.
* @param exception the exception thrown from this node's execution. * @param exception the exception thrown from this node's execution.
*/ */
@Override @Override
public void onReturnExceptional(VirtualFrame frame, Throwable exception) { public void onReturnExceptional(VirtualFrame frame, Throwable exception) {
if (!(exception instanceof TailCallException)) { if (!runtimeTestService.isTailCallException(exception)) {
return; return;
} }
if (!(context.getInstrumentedNode() instanceof ExpressionNode)) { if (!runtimeTestService.isExpressionNode(context.getInstrumentedNode())) {
return; return;
} }
UUID id = ((ExpressionNode) context.getInstrumentedNode()).getId(); UUID id = runtimeTestService.getNodeID(context.getInstrumentedNode());
if (expectedResult == null) { if (expectedResult == null) {
successful = true; successful = true;
} }
@ -142,8 +146,9 @@ public class CodeIdsTestInstrument extends TruffleInstrument {
public String toString() { public String toString() {
var sb = new StringBuilder(); var sb = new StringBuilder();
sb.append(context.getInstrumentedNode().getClass().getSimpleName()); sb.append(context.getInstrumentedNode().getClass().getSimpleName());
if (context.getInstrumentedNode() instanceof ExpressionNode expr) { if (runtimeTestService.isExpressionNode(context.getInstrumentedNode())) {
sb.append("@").append(expr.getId()); UUID id = runtimeTestService.getNodeID(context.getInstrumentedNode());
sb.append("@").append(id);
} }
sb.append(" "); sb.append(" ");
sb.append(context.getInstrumentedSourceSection()); sb.append(context.getInstrumentedSourceSection());

View File

@ -1,4 +1,4 @@
package org.enso.interpreter.test; package org.enso.interpreter.test.instruments;
import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.instrumentation.EventBinding; import com.oracle.truffle.api.instrumentation.EventBinding;

View File

@ -1,6 +1,4 @@
package org.enso.interpreter.test; package org.enso.interpreter.test.instruments;
import static org.junit.Assert.fail;
import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.instrumentation.EventContext; import com.oracle.truffle.api.instrumentation.EventContext;
@ -9,18 +7,17 @@ import com.oracle.truffle.api.instrumentation.ExecutionEventNodeFactory;
import com.oracle.truffle.api.instrumentation.SourceSectionFilter; import com.oracle.truffle.api.instrumentation.SourceSectionFilter;
import com.oracle.truffle.api.instrumentation.TruffleInstrument; import com.oracle.truffle.api.instrumentation.TruffleInstrument;
import com.oracle.truffle.api.nodes.Node; import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.nodes.RootNode;
import com.oracle.truffle.api.source.SourceSection; import com.oracle.truffle.api.source.SourceSection;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.Function; import java.util.function.Function;
import org.enso.interpreter.node.MethodRootNode; import org.enso.interpreter.test.instruments.service.FunctionCallInfo;
import org.enso.interpreter.node.callable.FunctionCallInstrumentationNode; import org.enso.interpreter.test.instruments.service.RuntimeTestService;
import org.enso.pkg.QualifiedName; import org.openide.util.Lookup;
/** Testing instrument to control newly created nodes. */ /** Testing instrument to control newly created nodes. */
@TruffleInstrument.Registration( @TruffleInstrument.Registration(
@ -28,12 +25,17 @@ import org.enso.pkg.QualifiedName;
services = NodeCountingTestInstrument.class) services = NodeCountingTestInstrument.class)
public class NodeCountingTestInstrument extends TruffleInstrument { public class NodeCountingTestInstrument extends TruffleInstrument {
public static final String INSTRUMENT_ID = "node-count-test"; public static final String INSTRUMENT_ID = "node-count-test";
private static final RuntimeTestService runtimeTestService;
private final Map<Node, Node> all = new ConcurrentHashMap<>(); private final Map<Node, Node> all = new ConcurrentHashMap<>();
private Map<Class, List<Node>> counter = new ConcurrentHashMap<>(); private Map<Class, List<Node>> counter = new ConcurrentHashMap<>();
private final Map<UUID, FunctionCallInfo> calls = new ConcurrentHashMap<>(); private final Map<UUID, FunctionCallInfo> calls = new ConcurrentHashMap<>();
private Env env; private Env env;
static {
runtimeTestService = Lookup.getDefault().lookup(RuntimeTestService.class);
}
@Override @Override
protected void onCreate(Env env) { protected void onCreate(Env env) {
env.registerService(this); env.registerService(this);
@ -75,10 +77,10 @@ public class NodeCountingTestInstrument extends TruffleInstrument {
}; };
if (value < min) { if (value < min) {
fail(dump.apply(msg + ". Minimal size should be " + min + ", but was: " + value + " in")); throw new AssertionError(dump.apply(msg + ". Minimal size should be " + min + ", but was: " + value + " in"));
} }
if (value > max) { if (value > max) {
fail(dump.apply(msg + ". Maximal size should be " + max + ", but was: " + value + " in")); throw new AssertionError(dump.apply(msg + ". Maximal size should be " + max + ", but was: " + value + " in"));
} }
counter = new ConcurrentHashMap<>(); counter = new ConcurrentHashMap<>();
return prev; return prev;
@ -120,73 +122,16 @@ public class NodeCountingTestInstrument extends TruffleInstrument {
public void onReturnValue(VirtualFrame frame, Object result) { public void onReturnValue(VirtualFrame frame, Object result) {
Node node = context.getInstrumentedNode(); Node node = context.getInstrumentedNode();
if (node instanceof FunctionCallInstrumentationNode instrumentableNode
&& result instanceof FunctionCallInstrumentationNode.FunctionCall functionCall) { if (runtimeTestService.isFunctionCallInstrumentationNode(node)
onFunctionReturn(instrumentableNode, functionCall); && runtimeTestService.isFunctionCall(result)) {
UUID nodeId = runtimeTestService.getNodeID(node);
if (nodeId != null) {
var funcCallInfo = runtimeTestService.extractFunctionCallInfo(result);
calls.put(nodeId, funcCallInfo);
}
} }
}
private void onFunctionReturn(
FunctionCallInstrumentationNode node, FunctionCallInstrumentationNode.FunctionCall result) {
if (node.getId() != null) {
calls.put(node.getId(), new FunctionCallInfo(result));
}
}
}
public static class FunctionCallInfo {
private final QualifiedName moduleName;
private final QualifiedName typeName;
private final String functionName;
public FunctionCallInfo(FunctionCallInstrumentationNode.FunctionCall call) {
RootNode rootNode = call.getFunction().getCallTarget().getRootNode();
if (rootNode instanceof MethodRootNode methodNode) {
moduleName = methodNode.getModuleScope().getModule().getName();
typeName = methodNode.getType().getQualifiedName();
functionName = methodNode.getMethodName();
} else {
moduleName = null;
typeName = null;
functionName = rootNode.getName();
}
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
FunctionCallInfo that = (FunctionCallInfo) o;
return Objects.equals(moduleName, that.moduleName)
&& Objects.equals(typeName, that.typeName)
&& Objects.equals(functionName, that.functionName);
}
@Override
public int hashCode() {
return Objects.hash(moduleName, typeName, functionName);
}
@Override
public String toString() {
return moduleName + "::" + typeName + "::" + functionName;
}
public QualifiedName getModuleName() {
return moduleName;
}
public QualifiedName getTypeName() {
return typeName;
}
public String getFunctionName() {
return functionName;
} }
} }
} }

View File

@ -0,0 +1,13 @@
package org.enso.interpreter.test.instruments.service;
public record FunctionCallInfo(
String moduleName,
String typeName,
String functionName
) {
@Override
public String toString() {
return moduleName + "::" + typeName + "::" + functionName;
}
}

View File

@ -0,0 +1,20 @@
package org.enso.interpreter.test.instruments.service;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.nodes.RootNode;
import java.util.UUID;
import org.enso.interpreter.test.instruments.service.FunctionCallInfo;
/**
* A service that provides information from the `runtime` project to the instruments in this project
* (`runtime-test-instruments`). Note that this project cannot have a compile time dependency on
* `runtime`, thus, we need to use a service provider mechanism.
*/
public interface RuntimeTestService {
UUID getNodeID(Node node);
boolean isExpressionNode(Object node);
boolean isTailCallException(Object obj);
boolean isFunctionCallInstrumentationNode(Object node);
boolean isFunctionCall(Object obj);
FunctionCallInfo extractFunctionCallInfo(Object functionCall);
}

View File

@ -1,77 +0,0 @@
package org.enso.interpreter.test.instrument;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import java.nio.file.Paths;
import java.util.logging.Level;
import org.enso.interpreter.test.MockLogHandler;
import org.enso.polyglot.MethodNames;
import org.enso.polyglot.RuntimeOptions;
import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.Source;
import org.graalvm.polyglot.io.IOAccess;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
public class VerifyLanguageAvailabilityTest {
private static Context ctx;
private static MockLogHandler handler;
@BeforeClass
public static void initEnsoContext() {
handler = new MockLogHandler();
ctx =
Context.newBuilder()
.allowExperimentalOptions(true)
.allowIO(IOAccess.ALL)
.option(RuntimeOptions.PREINITIALIZE, "js")
.option(
RuntimeOptions.LANGUAGE_HOME_OVERRIDE,
Paths.get("../../distribution/component").toFile().getAbsolutePath())
.option(RuntimeOptions.LOG_LEVEL, Level.FINE.getName())
.logHandler(handler)
.allowAllAccess(true)
.build();
assertNotNull("Enso language is supported", ctx.getEngine().getLanguages().get("enso"));
var fourtyTwo =
ctx.eval("enso", "mul x y = x * y").invokeMember("eval_expression", "mul").execute(6, 7);
assertEquals(42, fourtyTwo.asInt());
}
@AfterClass
public static void closeEnsoContext() throws Exception {
ctx.close();
var args =
handler.assertMessage("epb.org.enso.interpreter.epb.EpbContext", "Parsing foreign script");
assertEquals("js", args[0]);
assertEquals("mul.mul", args[1]);
}
@Test
public void javaScriptIsPresent() throws Exception {
var js = ctx.getEngine().getLanguages().get("js");
assertNotNull("JavaScript is available", js);
var src =
Source.newBuilder(
"enso",
"""
foreign js mul a b = \"\"\"
return a * b
run = mul 6 7
""",
"mul.enso")
.build();
var fourtyTwo = ctx.eval(src).invokeMember(MethodNames.Module.EVAL_EXPRESSION, "run");
assertEquals(42, fourtyTwo.asInt());
}
@Test
public void ensoIsPresent() {
var enso = ctx.getEngine().getLanguages().get("enso");
assertNotNull("Enso is available", enso);
}
}

View File

@ -85,36 +85,6 @@ public class BigNumberTest extends TestBase {
return powers; return powers;
} }
@Test
public void averageOfMixedArrayOverDouble() throws Exception {
boolean assertsOn = false;
assert assertsOn = true;
if (assertsOn) {
// skip this test when asserts are on
return;
}
var code =
"""
from Standard.Base.Data.Vector import Vector
polyglot java import org.enso.example.TestClass
powers n =
go x v b = if x > n then b.to_vector else
b.append v
@Tail_Call go x+1 v*2 b
go 1 1 Vector.new_builder
avg n = TestClass.doubleArrayAverage (powers n)
""";
var fn = evalCode(code, "avg");
var avg = fn.execute(200);
assertTrue("Got a number back " + avg, avg.isNumber());
assertFalse("It's not a long", avg.fitsInLong());
assertTrue("It's a double", avg.fitsInDouble());
assertEquals("It is big enough", Math.pow(2, 200) / 200, avg.asDouble(), 300);
}
@Test @Test
public void averageOfMixedArrayOverNumber() throws Exception { public void averageOfMixedArrayOverNumber() throws Exception {
var code = var code =

View File

@ -42,7 +42,6 @@ import org.graalvm.polyglot.io.IOAccess;
import org.junit.After; import org.junit.After;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
public class DebuggingEnsoTest { public class DebuggingEnsoTest {
@ -58,7 +57,7 @@ public class DebuggingEnsoTest {
.option( .option(
RuntimeOptions.LANGUAGE_HOME_OVERRIDE, RuntimeOptions.LANGUAGE_HOME_OVERRIDE,
Paths.get("../../distribution/component").toFile().getAbsolutePath()) Paths.get("../../distribution/component").toFile().getAbsolutePath())
.option(RuntimeOptions.LOG_LEVEL, Level.FINEST.getName()) .option(RuntimeOptions.LOG_LEVEL, Level.WARNING.getName())
.logHandler(System.err) .logHandler(System.err)
.build(); .build();
@ -504,9 +503,7 @@ public class DebuggingEnsoTest {
bar 42 # 6 bar 42 # 6
end = 0 # 7 end = 0 # 7
"""); """);
// Steps into line 2 - declaration of the method, which is fine. List<Integer> expectedLineNumbers = List.of(5, 6, 7);
// (5, 6, 7) would be better.
List<Integer> expectedLineNumbers = List.of(5, 6, 2, 7);
Queue<SuspendedCallback> steps = createStepOverEvents(expectedLineNumbers.size()); Queue<SuspendedCallback> steps = createStepOverEvents(expectedLineNumbers.size());
testStepping(src, "foo", new Object[] {0}, steps, expectedLineNumbers); testStepping(src, "foo", new Object[] {0}, steps, expectedLineNumbers);
} }
@ -514,12 +511,7 @@ public class DebuggingEnsoTest {
/** /**
* Use some methods from Vector in stdlib. Stepping over methods from different modules might be * Use some methods from Vector in stdlib. Stepping over methods from different modules might be
* problematic. * problematic.
*
* <p>TODO[pm] This test is ignored, because the current behavior of step over is that it first
* steps into the declaration (name) of the method that is being stepped over and then steps back.
* So there would be weird line numbers from std lib.
*/ */
@Ignore
@Test @Test
public void testSteppingOverUseStdLib() { public void testSteppingOverUseStdLib() {
Source src = Source src =
@ -562,7 +554,7 @@ public class DebuggingEnsoTest {
bar 42 # 4 bar 42 # 4
end = 0 # 5 end = 0 # 5
"""); """);
List<Integer> expectedLineNumbers = List.of(3, 4, 2, 1, 5); List<Integer> expectedLineNumbers = List.of(3, 4, 2, 1, 2, 4, 5);
Queue<SuspendedCallback> steps = Queue<SuspendedCallback> steps =
new ArrayDeque<>( new ArrayDeque<>(
Collections.nCopies(expectedLineNumbers.size(), (event) -> event.prepareStepInto(1))); Collections.nCopies(expectedLineNumbers.size(), (event) -> event.prepareStepInto(1)));
@ -581,7 +573,7 @@ public class DebuggingEnsoTest {
bar (baz x) # 4 bar (baz x) # 4
end = 0 # 5 end = 0 # 5
"""); """);
List<Integer> expectedLineNumbers = List.of(3, 4, 1, 2, 5); List<Integer> expectedLineNumbers = List.of(3, 4, 1, 4, 2, 4, 5);
Queue<SuspendedCallback> steps = Queue<SuspendedCallback> steps =
new ArrayDeque<>( new ArrayDeque<>(
Collections.nCopies(expectedLineNumbers.size(), (event) -> event.prepareStepInto(1))); Collections.nCopies(expectedLineNumbers.size(), (event) -> event.prepareStepInto(1)));

View File

@ -47,6 +47,7 @@ public class MetaIsATest extends TestBase {
public static void disposeCtx() { public static void disposeCtx() {
if (generator != null) { if (generator != null) {
generator.dispose(); generator.dispose();
generator = null;
} }
ctx.close(); ctx.close();
} }

View File

@ -36,6 +36,7 @@ public class MetaObjectTest extends TestBase {
public static void disposeCtx() { public static void disposeCtx() {
if (generator != null) { if (generator != null) {
generator.dispose(); generator.dispose();
generator = null;
} }
ctx.close(); ctx.close();
} }

View File

@ -0,0 +1,62 @@
package org.enso.interpreter.test;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.nodes.RootNode;
import java.util.UUID;
import org.enso.interpreter.node.ExpressionNode;
import org.enso.interpreter.node.MethodRootNode;
import org.enso.interpreter.node.callable.FunctionCallInstrumentationNode;
import org.enso.interpreter.node.callable.FunctionCallInstrumentationNode.FunctionCall;
import org.enso.interpreter.runtime.control.TailCallException;
import org.enso.interpreter.test.instruments.service.FunctionCallInfo;
import org.enso.interpreter.test.instruments.service.RuntimeTestService;
import org.openide.util.lookup.ServiceProvider;
@ServiceProvider(service = RuntimeTestService.class)
public class RuntimeTestServiceImpl implements RuntimeTestService {
@Override
public UUID getNodeID(Node node) {
if (node instanceof ExpressionNode exprNode) {
return exprNode.getId();
} else if (node instanceof FunctionCallInstrumentationNode funcNode) {
return funcNode.getId();
}
return null;
}
@Override
public boolean isExpressionNode(Object node) {
return node instanceof ExpressionNode;
}
@Override
public boolean isTailCallException(Object obj) {
return obj instanceof TailCallException;
}
@Override
public boolean isFunctionCallInstrumentationNode(Object node) {
return node instanceof FunctionCallInstrumentationNode;
}
@Override
public boolean isFunctionCall(Object obj) {
return obj instanceof FunctionCall;
}
@Override
public FunctionCallInfo extractFunctionCallInfo(Object functionCallObj) {
if (functionCallObj instanceof FunctionCall functionCall) {
RootNode rootNode = functionCall.getFunction().getCallTarget().getRootNode();
if (rootNode instanceof MethodRootNode methodNode) {
String moduleName = methodNode.getModuleScope().getModule().getName().toString();
String typeName = methodNode.getType().getQualifiedName().toString();
String functionName = methodNode.getMethodName();
return new FunctionCallInfo(moduleName, typeName, functionName);
} else {
return new FunctionCallInfo(null, null, rootNode.getName());
}
}
return null;
}
}

View File

@ -16,7 +16,7 @@ import java.util.logging.Level;
import org.enso.interpreter.node.ClosureRootNode; import org.enso.interpreter.node.ClosureRootNode;
import org.enso.interpreter.runtime.tag.AvoidIdInstrumentationTag; import org.enso.interpreter.runtime.tag.AvoidIdInstrumentationTag;
import org.enso.interpreter.runtime.tag.IdentifiedTag; import org.enso.interpreter.runtime.tag.IdentifiedTag;
import org.enso.interpreter.test.NodeCountingTestInstrument; import org.enso.interpreter.test.instruments.NodeCountingTestInstrument;
import org.enso.polyglot.RuntimeOptions; import org.enso.polyglot.RuntimeOptions;
import org.graalvm.polyglot.Context; import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.Language; import org.graalvm.polyglot.Language;

View File

@ -14,8 +14,8 @@ import org.enso.compiler.core.ir.Literal;
import org.enso.interpreter.node.expression.literal.LiteralNode; import org.enso.interpreter.node.expression.literal.LiteralNode;
import org.enso.interpreter.runtime.type.ConstantsGen; import org.enso.interpreter.runtime.type.ConstantsGen;
import org.enso.interpreter.test.Metadata; import org.enso.interpreter.test.Metadata;
import org.enso.interpreter.test.NodeCountingTestInstrument;
import org.enso.interpreter.test.instrument.RuntimeServerTest.TestContext; import org.enso.interpreter.test.instrument.RuntimeServerTest.TestContext;
import org.enso.interpreter.test.instruments.NodeCountingTestInstrument;
import org.enso.polyglot.runtime.Runtime$Api$CreateContextRequest; import org.enso.polyglot.runtime.Runtime$Api$CreateContextRequest;
import org.enso.polyglot.runtime.Runtime$Api$CreateContextResponse; import org.enso.polyglot.runtime.Runtime$Api$CreateContextResponse;
import org.enso.polyglot.runtime.Runtime$Api$EditFileNotification; import org.enso.polyglot.runtime.Runtime$Api$EditFileNotification;
@ -35,6 +35,7 @@ import org.enso.polyglot.runtime.Runtime$Api$StackItem$ExplicitCall;
import org.enso.polyglot.runtime.Runtime$Api$StackItem$LocalCall; import org.enso.polyglot.runtime.Runtime$Api$StackItem$LocalCall;
import org.enso.text.editing.model; import org.enso.text.editing.model;
import org.junit.After; import org.junit.After;
import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import scala.Option; import scala.Option;
@ -77,7 +78,7 @@ public class IncrementalUpdatesTest {
var m = context.languageContext().findModule(MODULE_NAME).orElse(null); var m = context.languageContext().findModule(MODULE_NAME).orElse(null);
assertNotNull("Module found", m); assertNotNull("Module found", m);
var numbers = m.getIr().preorder().filter((v1) -> v1 instanceof Literal.Number); var numbers = m.getIr().preorder().filter((v1) -> v1 instanceof Literal.Number);
assertEquals("One number found: " + numbers, 1, numbers.size()); Assert.assertEquals("One number found: " + numbers, 1, numbers.size());
if (numbers.head() instanceof Literal.Number n) { if (numbers.head() instanceof Literal.Number n) {
assertEquals("updated to 5", "5", n.value()); assertEquals("updated to 5", "5", n.value());
} }
@ -100,10 +101,10 @@ public class IncrementalUpdatesTest {
sendUpdatesWhenFunctionBodyIsChangedBySettingValue( sendUpdatesWhenFunctionBodyIsChangedBySettingValue(
"4", ConstantsGen.INTEGER, "4", "1000", "1000", LiteralNode.class); "4", ConstantsGen.INTEGER, "4", "1000", "1000", LiteralNode.class);
sendExpressionValue("1000", "333"); sendExpressionValue("1000", "333");
assertEquals(List.newBuilder().addOne("333"), context.consumeOut()); Assert.assertEquals(List.newBuilder().addOne("333"), context.consumeOut());
nodeCountingInstrument.assertNewNodes("No execution on 333, no nodes yet", 0, 0); nodeCountingInstrument.assertNewNodes("No execution on 333, no nodes yet", 0, 0);
sendExpressionValue("333", "22"); sendExpressionValue("333", "22");
assertEquals(List.newBuilder().addOne("22"), context.consumeOut()); Assert.assertEquals(List.newBuilder().addOne("22"), context.consumeOut());
nodeCountingInstrument.assertNewNodes("No execution on 22, no nodes yet", 0, 0); nodeCountingInstrument.assertNewNodes("No execution on 22, no nodes yet", 0, 0);
} }
@ -127,7 +128,7 @@ public class IncrementalUpdatesTest {
assertTrue( assertTrue(
"Execution succeeds: " + result, "Execution succeeds: " + result,
result.head().payload() instanceof Runtime$Api$ExecutionComplete); result.head().payload() instanceof Runtime$Api$ExecutionComplete);
assertEquals( Assert.assertEquals(
"Error is printed as a result", "Error is printed as a result",
List.newBuilder().addOne("(Error: Uninitialized value)"), List.newBuilder().addOne("(Error: Uninitialized value)"),
context.consumeOut()); context.consumeOut());
@ -273,7 +274,7 @@ public class IncrementalUpdatesTest {
Vector$.MODULE$.empty())), Vector$.MODULE$.empty())),
TestMessages.update(contextId, mainRes, ConstantsGen.NOTHING), TestMessages.update(contextId, mainRes, ConstantsGen.NOTHING),
context.executionComplete(contextId)); context.executionComplete(contextId));
assertEquals(List.newBuilder().addOne(originalOutput), context.consumeOut()); Assert.assertEquals(List.newBuilder().addOne(originalOutput), context.consumeOut());
var allNodesAfterException = var allNodesAfterException =
nodeCountingInstrument.assertNewNodes("Execution creates some nodes", 20, 35); nodeCountingInstrument.assertNewNodes("Execution creates some nodes", 20, 35);
@ -290,7 +291,7 @@ public class IncrementalUpdatesTest {
TestMessages.update(contextId, fooX, exprType), TestMessages.update(contextId, fooX, exprType),
TestMessages.update(contextId, fooRes, exprType), TestMessages.update(contextId, fooRes, exprType),
context.executionComplete(contextId)); context.executionComplete(contextId));
assertEquals(List.newBuilder().addOne(originalOutput), context.consumeOut()); Assert.assertEquals(List.newBuilder().addOne(originalOutput), context.consumeOut());
nodeCountingInstrument.assertNewNodes("No new nodes created", 0, 0); nodeCountingInstrument.assertNewNodes("No new nodes created", 0, 0);
var literalNode = findLiteralNode(truffleNodeType, allNodesAfterException); var literalNode = findLiteralNode(truffleNodeType, allNodesAfterException);
@ -302,7 +303,7 @@ public class IncrementalUpdatesTest {
var executionCompleteEvents = sendEdit.apply(originalText, newText); var executionCompleteEvents = sendEdit.apply(originalText, newText);
if (executionOutput != null) { if (executionOutput != null) {
assertSameElements(executionCompleteEvents, context.executionComplete(contextId)); assertSameElements(executionCompleteEvents, context.executionComplete(contextId));
assertEquals(List.newBuilder().addOne(executionOutput), context.consumeOut()); Assert.assertEquals(List.newBuilder().addOne(executionOutput), context.consumeOut());
nodeCountingInstrument.assertNewNodes("No new nodes created", 0, 0); nodeCountingInstrument.assertNewNodes("No new nodes created", 0, 0);
assertEquals( assertEquals(

View File

@ -1,6 +1,7 @@
package org.enso.interpreter.test.instrument; package org.enso.interpreter.test.instrument;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import com.oracle.truffle.api.instrumentation.SourceSectionFilter; import com.oracle.truffle.api.instrumentation.SourceSectionFilter;
import com.oracle.truffle.api.instrumentation.StandardTags; import com.oracle.truffle.api.instrumentation.StandardTags;
@ -10,7 +11,7 @@ import java.util.logging.Level;
import org.enso.interpreter.runtime.tag.AvoidIdInstrumentationTag; import org.enso.interpreter.runtime.tag.AvoidIdInstrumentationTag;
import org.enso.interpreter.runtime.tag.IdentifiedTag; import org.enso.interpreter.runtime.tag.IdentifiedTag;
import org.enso.interpreter.test.Metadata; import org.enso.interpreter.test.Metadata;
import org.enso.interpreter.test.NodeCountingTestInstrument; import org.enso.interpreter.test.instruments.NodeCountingTestInstrument;
import org.enso.polyglot.RuntimeOptions; import org.enso.polyglot.RuntimeOptions;
import org.graalvm.polyglot.Context; import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.Language; import org.graalvm.polyglot.Language;
@ -92,9 +93,9 @@ public class WarningInstrumentationTest {
var calls = instrument.registeredCalls(); var calls = instrument.registeredCalls();
assertEquals(calls.keySet().size(), 3); assertEquals(calls.keySet().size(), 3);
assertEquals(calls.get(idOp1).getFunctionName(), "new"); assertEquals(calls.get(idOp1).functionName(), "new");
assertEquals(calls.get(idOp2).getFunctionName(), "attach"); assertEquals(calls.get(idOp2).functionName(), "attach");
assertEquals(calls.get(idOp3).getTypeName().item(), "Table"); assertTrue(calls.get(idOp3).typeName().contains("Table"));
assertEquals(calls.get(idOp3).getFunctionName(), "get"); assertEquals(calls.get(idOp3).functionName(), "get");
} }
} }

View File

@ -1,8 +1,12 @@
package org.enso.interpreter.test package org.enso.interpreter.test
import com.oracle.truffle.api.instrumentation.EventBinding import com.oracle.truffle.api.instrumentation.EventBinding
import org.enso.interpreter.test.CodeIdsTestInstrument.IdEventListener import org.enso.interpreter.test.instruments.CodeIdsTestInstrument.IdEventListener
import org.enso.interpreter.test.CodeLocationsTestInstrument.LocationsEventListener import org.enso.interpreter.test.instruments.{
CodeIdsTestInstrument,
CodeLocationsTestInstrument
}
import org.enso.interpreter.test.instruments.CodeLocationsTestInstrument.LocationsEventListener
import org.enso.polyglot.debugger.{ import org.enso.polyglot.debugger.{
DebugServerInfo, DebugServerInfo,
DebuggerSessionManagerEndpoint, DebuggerSessionManagerEndpoint,

View File

@ -2,9 +2,9 @@ package org.enso.interpreter.test.instrument
import org.enso.polyglot.{LanguageInfo, RuntimeOptions} import org.enso.polyglot.{LanguageInfo, RuntimeOptions}
import org.graalvm.polyglot.{Context, PolyglotException} import org.graalvm.polyglot.{Context, PolyglotException}
import org.scalatest.{BeforeAndAfterEach, Suite}
import org.scalatest.matchers.should.Matchers import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpec import org.scalatest.wordspec.AnyWordSpec
import org.scalatest.{BeforeAndAfterEach, Suite}
import java.nio.file.Paths import java.nio.file.Paths
import java.util.logging.Level import java.util.logging.Level
@ -44,7 +44,6 @@ class RuntimeProjectContextTest
.toFile .toFile
.getAbsolutePath .getAbsolutePath
) )
.option("engine.WarnInterpreterOnly", "false")
.option(RuntimeOptions.EDITION_OVERRIDE, "0.0.0-dev") .option(RuntimeOptions.EDITION_OVERRIDE, "0.0.0-dev")
.option(RuntimeOptions.LOG_LEVEL, Level.WARNING.getName) .option(RuntimeOptions.LOG_LEVEL, Level.WARNING.getName)
.logHandler(System.err) .logHandler(System.err)

View File

@ -5,9 +5,9 @@ import org.enso.interpreter.runtime.`type`.ConstantsGen
import org.enso.interpreter.test.Metadata import org.enso.interpreter.test.Metadata
import org.enso.polyglot._ import org.enso.polyglot._
import org.enso.polyglot.runtime.Runtime.Api import org.enso.polyglot.runtime.Runtime.Api
import org.enso.text.{ContentBasedVersioning, Sha3_224VersionCalculator}
import org.enso.text.editing.model import org.enso.text.editing.model
import org.enso.text.editing.model.TextEdit import org.enso.text.editing.model.TextEdit
import org.enso.text.{ContentBasedVersioning, Sha3_224VersionCalculator}
import org.graalvm.polyglot.Context import org.graalvm.polyglot.Context
import org.scalatest.BeforeAndAfterEach import org.scalatest.BeforeAndAfterEach
import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.flatspec.AnyFlatSpec

View File

@ -1,7 +1,8 @@
package org.enso.interpreter.test.instrument package org.enso.interpreter.test.instrument
import org.enso.interpreter.runtime.`type`.{Constants, ConstantsGen, Types} import org.apache.commons.io.output.TeeOutputStream
import org.enso.interpreter.runtime.EnsoContext import org.enso.interpreter.runtime.EnsoContext
import org.enso.interpreter.runtime.`type`.{Constants, ConstantsGen, Types}
import org.enso.interpreter.test.Metadata import org.enso.interpreter.test.Metadata
import org.enso.polyglot._ import org.enso.polyglot._
import org.enso.polyglot.data.TypeGraph import org.enso.polyglot.data.TypeGraph
@ -16,7 +17,6 @@ import org.scalatest.matchers.should.Matchers
import java.io.{ByteArrayOutputStream, File} import java.io.{ByteArrayOutputStream, File}
import java.nio.file.{Files, Paths} import java.nio.file.{Files, Paths}
import java.util.UUID import java.util.UUID
import org.apache.commons.io.output.TeeOutputStream
@scala.annotation.nowarn("msg=multiarg infix syntax") @scala.annotation.nowarn("msg=multiarg infix syntax")
class RuntimeServerTest class RuntimeServerTest

View File

@ -83,7 +83,11 @@ object GraalVM {
"org.graalvm.tools" % "dap-tool" % version "org.graalvm.tools" % "dap-tool" % version
) )
val toolsPkgs = chromeInspectorPkgs ++ debugAdapterProtocolPkgs val insightPkgs = Seq(
"org.graalvm.tools" % "insight-tool" % version
)
val toolsPkgs = chromeInspectorPkgs ++ debugAdapterProtocolPkgs ++ insightPkgs
val langsPkgs = jsPkgs ++ pythonPkgs val langsPkgs = jsPkgs ++ pythonPkgs

182
project/JPMSPlugin.scala Normal file
View File

@ -0,0 +1,182 @@
import sbt.*
import sbt.Keys.*
import sbt.internal.inc.{CompileOutput, PlainVirtualFile}
import sbt.util.CacheStore
import sbtassembly.Assembly.{Dependency, JarEntry, Project}
import sbtassembly.{CustomMergeStrategy, MergeStrategy}
import xsbti.compile.IncToolOptionsUtil
import java.io.File
/** An automatic plugin that handles everything related to JPMS modules. One needs to explicitly
* enable this plugin in a project with `.enablePlugins(JPMSPlugin)`. The keys and tasks provided by this plugin
* corresponds to the module-related options of `javac` and `java` commands.
*
* This plugin injects all the module-specific options to `javaOptions`, based on
* the settings of this plugin.
*
* If this plugin is enabled, and no settings/tasks from this plugin are used, then the plugin will
* not inject anything into `javaOptions` or `javacOptions`.
*/
object JPMSPlugin extends AutoPlugin {
object autoImport {
val javaModuleName =
settingKey[String]("The name of the Java (JPMS) module")
val addModules = settingKey[Seq[String]](
"Module names that will be added to --add-modules option"
)
val modulePath = taskKey[Seq[File]](
"Directories (Jar archives or expanded Jar archives) that will be put into " +
"--module-path option"
)
val patchModules = taskKey[Map[String, Seq[File]]](
"""
|A map of module names to directories (Jar archives or expanded Jar archives) that will be
|put into --patch-module option.
|""".stripMargin
)
val addExports = taskKey[Map[String, Seq[String]]](
"""
|A map of module names to packages that will be put into --add-exports option.
|The format of `--add-exports` option is `module/package=target-module(,target-module)*`
|The key in the map is `module/package` and the value is a sequence of target modules
|""".stripMargin
)
val addReads = taskKey[Map[String, Seq[String]]](
"""
|A map of module names to modules that will be put into --add-reads option.
|When a module A reads a module B, it means that it "depends" on it - it has the same
|effect as if module A would have `requires B` in its module-info.java file.
|""".stripMargin
)
val compileModuleInfo = taskKey[Unit]("Compile module-info.java")
val modulePathTestOptions_ = taskKey[Seq[String]](
"Assembles options for the JVM for running tests with all the required modules. " +
"Including truffle-compiler and org.enso.runtime modules and all their dependencies."
)
}
import autoImport.*
override lazy val projectSettings: Seq[Setting[_]] = Seq(
addModules := Seq.empty,
modulePath := Seq.empty,
patchModules := Map.empty,
addExports := Map.empty,
addReads := Map.empty,
compileModuleInfo := {},
// javacOptions only inject --module-path and --add-modules, not the rest of the
// options.
Compile / javacOptions ++= {
constructOptions(
streams.value.log,
modulePath = (Compile / modulePath).value,
addModules = (Compile / addModules).value
)
},
Compile / javaOptions ++= {
constructOptions(
streams.value.log,
(Compile / modulePath).value,
(Compile / addModules).value,
(Compile / patchModules).value,
(Compile / addExports).value,
(Compile / addReads).value
)
},
Test / javacOptions ++= {
constructOptions(
streams.value.log,
modulePath = (Test / modulePath).value,
addModules = (Test / addModules).value
)
},
Test / javaOptions ++= {
constructOptions(
streams.value.log,
(Test / modulePath).value,
(Test / addModules).value,
(Test / patchModules).value,
(Test / addExports).value,
(Test / addReads).value
)
}
)
def constructOptions(
log: Logger,
modulePath: Seq[File],
addModules: Seq[String] = Seq.empty,
patchModules: Map[String, Seq[File]] = Map.empty,
addExports: Map[String, Seq[String]] = Map.empty,
addReads: Map[String, Seq[String]] = Map.empty
): Seq[String] = {
val patchOpts: Seq[String] = patchModules.flatMap {
case (moduleName, dirsToPatch) =>
ensureDirectoriesExist(dirsToPatch, log)
val patchStr = dirsToPatch
.map(_.getAbsolutePath)
.mkString(File.pathSeparator)
Seq(
"--patch-module",
s"$moduleName=$patchStr"
)
}.toSeq
ensureDirectoriesExist(modulePath, log)
val addExportsOpts: Seq[String] = addExports.flatMap {
case (modPkgName, targetModules) =>
if (!modPkgName.contains("/")) {
log.error(s"JPMSPlugin: Invalid module/package name: $modPkgName")
}
Seq(
"--add-exports",
modPkgName + "=" + targetModules.mkString(",")
)
}.toSeq
val modulePathOpts = if (modulePath.isEmpty) {
Seq.empty
} else {
Seq(
"--module-path",
modulePath.map(_.getAbsolutePath).mkString(File.pathSeparator)
)
}
val addModsOpts = if (addModules.isEmpty) {
Seq.empty
} else {
Seq(
"--add-modules",
addModules.mkString(",")
)
}
val addReadsOpts = addReads.flatMap { case (modName, targetModules) =>
Seq(
"--add-reads",
modName + "=" + targetModules.mkString(",")
)
}.toSeq
modulePathOpts ++ addModsOpts ++ patchOpts ++ addExportsOpts ++ addReadsOpts
}
/** Java does not mandate that the directories specified in the module path or
* in --patch-module exist, but it is usefull to report at least warnings.
* @param dirs
* @param log
*/
private def ensureDirectoriesExist(
dirs: Seq[File],
log: Logger
): Unit = {
dirs.foreach { dir =>
if (!dir.exists()) {
log.warn(s"JPMSPlugin: Directory $dir does not exist.")
}
}
}
}

View File

@ -1,21 +1,22 @@
import JPMSPlugin.autoImport.javaModuleName
import sbt.* import sbt.*
import sbt.Keys.* import sbt.Keys.*
import sbtassembly.Assembly.{Dependency, JarEntry, Library, Project}
import sbtassembly.MergeStrategy
import java.io.{File, FilenameFilter}
import sbtassembly.CustomMergeStrategy
import xsbti.compile.IncToolOptionsUtil
import sbt.internal.inc.{CompileOutput, PlainVirtualFile} import sbt.internal.inc.{CompileOutput, PlainVirtualFile}
import sbt.util.CacheStore
import sbtassembly.Assembly.{Dependency, JarEntry, Project}
import sbtassembly.{CustomMergeStrategy, MergeStrategy}
import xsbti.compile.IncToolOptionsUtil
import java.io.File
import java.nio.file.attribute.BasicFileAttributes import java.nio.file.attribute.BasicFileAttributes
import java.nio.file.{ import java.nio.file.{
FileVisitOption,
FileVisitResult, FileVisitResult,
FileVisitor,
Files, Files,
Path, Path,
SimpleFileVisitor SimpleFileVisitor
} }
import scala.collection.mutable
/** Collection of utility methods dealing with JPMS modules. /** Collection of utility methods dealing with JPMS modules.
* The motivation comes from the update of GraalVM to * The motivation comes from the update of GraalVM to
@ -40,8 +41,9 @@ object JPMSUtils {
) )
/** Filters modules by their IDs from the given classpath. /** Filters modules by their IDs from the given classpath.
* @param cp The classpath to filter *
* @param modules These modules are looked for in the class path, can be duplicated. * @param cp The classpath to filter
* @param modules These modules are looked for in the class path, can be duplicated.
* @param shouldContainAll If true, the method will throw an exception if not all modules were found * @param shouldContainAll If true, the method will throw an exception if not all modules were found
* in the classpath. * in the classpath.
* @return The classpath with only the provided modules searched by their IDs. * @return The classpath with only the provided modules searched by their IDs.
@ -76,6 +78,43 @@ object JPMSUtils {
ret ret
} }
/** Filters all the requested modules from the given [[UpdateReport]].
*
* @param updateReport The update report to filter. This is the result of `update.value`.
* @param modules The modules to filter from the update report. Can be duplicated.
* @param log The logger to use for logging.
* @param shouldContainAll If true, the method will log an error if not all modules were found.
* @return The list of files (Jar archives, directories, etc.) that were found in the update report.
*/
def filterModulesFromUpdate(
updateReport: UpdateReport,
modules: Seq[ModuleID],
log: sbt.util.Logger,
shouldContainAll: Boolean = false
): Seq[File] = {
val distinctModules = modules.distinct
def shouldFilterModule(module: ModuleID): Boolean = {
distinctModules.exists(m =>
m.organization == module.organization &&
m.name == module.name &&
m.revision == module.revision
)
}
val foundFiles = updateReport.select(
module = shouldFilterModule
)
if (shouldContainAll) {
if (foundFiles.size < distinctModules.size) {
log.error("Not all modules from update were found")
log.error(s"Returned (${foundFiles.size}): $foundFiles")
log.error(s"Expected: (${distinctModules.size}): $distinctModules")
}
}
foundFiles
}
def filterTruffleAndGraalArtifacts( def filterTruffleAndGraalArtifacts(
classPath: Def.Classpath classPath: Def.Classpath
): Def.Classpath = { ): Def.Classpath = {
@ -130,17 +169,23 @@ object JPMSUtils {
* Note that sbt is not able to correctly handle `module-info.java` files when * Note that sbt is not able to correctly handle `module-info.java` files when
* compilation order is defined to mixed order. * compilation order is defined to mixed order.
* *
* Compilation of `module-info.java` is skipped iff none of all the classes from all the dependencies
* changed and if the `module-info.java` itself have not changed.
*
* @param copyDepsFilter The filter of scopes of the projects from which the class files are first * @param copyDepsFilter The filter of scopes of the projects from which the class files are first
* copied into the `target` directory before `module-info.java` is compiled. * copied into the `target` directory before `module-info.java` is compiled.
* @param modulePath IDs of dependencies that should be put on the module path. The modules * @param modulePath IDs of dependencies that should be put on the module path. The modules
* put into `modulePath` are filtered away from class-path, so that module-path * put into `modulePath` are filtered away from class-path, so that module-path
* and class-path passed to the `javac` are exclusive. * and class-path passed to the `javac` are exclusive.
* @param modulePathExtra More directories that should be added on `--module-path`. This parameter is of
* type [[File]], because this is how inter project dependencies are gathered.
* *
* @see https://users.scala-lang.org/t/scala-jdk-11-and-jpms/6102/19 * @see https://users.scala-lang.org/t/scala-jdk-11-and-jpms/6102/19
*/ */
def compileModuleInfo( def compileModuleInfo(
copyDepsFilter: ScopeFilter, copyDepsFilter: ScopeFilter,
modulePath: Seq[ModuleID] = Seq() modulePath: Seq[ModuleID] = Seq(),
modulePathExtra: Seq[File] = Seq()
): Def.Initialize[Task[Unit]] = ): Def.Initialize[Task[Unit]] =
Def Def
.task { .task {
@ -153,79 +198,153 @@ object JPMSUtils {
// copied to. // copied to.
val outputPath: Path = output.getSingleOutputAsPath val outputPath: Path = output.getSingleOutputAsPath
.get() .get()
// Class directories of all the dependencies.
val sourceProducts =
productDirectories.all(copyDepsFilter).value.flatten
/** Copy classes into the target directory from all the dependencies */ val moduleName = javaModuleName.value
log.debug(s"Copying classes to $output") val cacheStore = streams.value.cacheStoreFactory
val sourceProducts = products.all(copyDepsFilter).value.flatten val repoRootDir = (LocalProject("enso") / baseDirectory).value
var someDepChanged = false
if (!(outputPath.toFile.exists())) { sourceProducts.foreach(sourceProduct => {
Files.createDirectory(outputPath) if (!sourceProduct.exists()) {
} log.error(s"Source product ${sourceProduct} does not exist")
log.error(
val outputLangProvider = "This means that the Compile/compile task was probably not run in " +
outputPath / "META-INF" / "services" / "com.oracle.truffle.api.provider.TruffleLanguageProvider" "the corresponding project"
sourceProducts.foreach { sourceProduct =>
log.debug(s"Copying ${sourceProduct} to ${output}")
val sourceLangProvider =
sourceProduct / "META-INF" / "services" / "com.oracle.truffle.api.provider.TruffleLanguageProvider"
if (
outputLangProvider.toFile.exists() && sourceLangProvider.exists()
) {
log.debug(
s"Merging ${sourceLangProvider} into ${outputLangProvider}"
) )
val sourceLines = IO.readLines(sourceLangProvider) log.error("Run Compile/compile before this task")
val destLines = IO.readLines(outputLangProvider.toFile) }
val outLines = (sourceLines ++ destLines).distinct val relPath = repoRootDir.toPath.relativize(sourceProduct.toPath)
IO.writeLines(outputLangProvider.toFile, outLines) val cache = cacheStore.make("cache-" + relPath.toString)
val depChanged = copyClasses(sourceProduct, output, cache, log)
if (depChanged) {
someDepChanged = true
} }
// Copy the rest of the directory - don't override META-INF.
IO.copyDirectory(
sourceProduct,
outputPath.toFile,
CopyOptions(
overwrite = false,
preserveLastModified = true,
preserveExecutable = true
)
)
}
log.info("Compiling module-info.java with javac")
val baseJavacOpts = (Compile / javacOptions).value
val fullCp = (Compile / fullClasspath).value
val (mp, cp) = fullCp.partition(file => {
val moduleID =
file.metadata.get(AttributeKey[ModuleID]("moduleID")).get
modulePath.exists(mod => {
mod.organization == moduleID.organization &&
mod.name == moduleID.name &&
mod.revision == moduleID.revision
})
}) })
val allOpts = baseJavacOpts ++ Seq( val baseJavacOpts = (Compile / javacOptions).value
"--class-path", val fullCp = (Compile / fullClasspath).value
cp.map(_.data.getAbsolutePath).mkString(File.pathSeparator),
"--module-path",
mp.map(_.data.getAbsolutePath).mkString(File.pathSeparator),
"-d",
outputPath.toAbsolutePath().toString()
)
val javaCompiler = val javaCompiler =
(Compile / compile / compilers).value.javaTools.javac() (Compile / compile / compilers).value.javaTools.javac()
val succ = javaCompiler.run(
Array(PlainVirtualFile(moduleInfo.toPath)), // Skip module-info.java compilation if the source have not changed
allOpts.toArray, // Force the compilation if some class file from one of the dependencies changed,
output, // just to be sure that we don't cause any weird compilation errors.
incToolOpts, val moduleInfoCache = cacheStore.make("cache-module-info-" + moduleName)
reporter, Tracked.diffInputs(moduleInfoCache, FileInfo.lastModified)(
log Set(moduleInfo)
) ) { changeReport =>
if (!succ) { if (
sys.error(s"Compilation of ${moduleInfo} failed") someDepChanged || changeReport.modified.nonEmpty || changeReport.added.nonEmpty
) {
log.info(s"Compiling $moduleInfo with javac")
val (mp, cp) = fullCp.partition(file => {
val moduleID =
file.metadata.get(AttributeKey[ModuleID]("moduleID")).get
modulePath.exists(mod => {
mod.organization == moduleID.organization &&
mod.name == moduleID.name &&
mod.revision == moduleID.revision
})
})
val allDirsOnMp = mp.map(_.data) ++ modulePathExtra
val allOpts = baseJavacOpts ++ Seq(
"--class-path",
cp.map(_.data.getAbsolutePath).mkString(File.pathSeparator),
"--module-path",
allDirsOnMp.map(_.getAbsolutePath).mkString(File.pathSeparator),
"-d",
outputPath.toAbsolutePath.toString
)
log.debug(s"javac options: $allOpts")
val succ = javaCompiler.run(
Array(PlainVirtualFile(moduleInfo.toPath)),
allOpts.toArray,
output,
incToolOpts,
reporter,
log
)
if (!succ) {
log.error(s"Compilation of ${moduleInfo} failed")
}
}
} }
} }
.dependsOn(Compile / compile)
/** Copies all classes from all the dependencies `classes` directories into the target directory.
* @param sourceClassesDir Directory from where the classes will be copied.
* @param output Target directory where all the classes from all the dependencies
* will be copied to.
* @param log
* @return True iff some of the dependencies changed, i.e., if there is a modified class file, or
* some class file was added, or removed
*/
private def copyClasses(
sourceClassesDir: File,
output: xsbti.compile.Output,
cache: CacheStore,
log: Logger
): Boolean = {
require(sourceClassesDir.isDirectory)
val outputPath: Path = output.getSingleOutputAsPath.get()
val outputDir = outputPath.toFile
val filesToCopy = mutable.HashSet.empty[File]
val fileVisitor = new SimpleFileVisitor[Path] {
override def visitFile(
path: Path,
attrs: BasicFileAttributes
): FileVisitResult = {
if (!path.toFile.isDirectory) {
filesToCopy.add(path.toFile)
}
FileVisitResult.CONTINUE
}
override def preVisitDirectory(
dir: Path,
attrs: BasicFileAttributes
): FileVisitResult = {
// We do not care about files in META-INF directory. Everything should be described
// in the `module-info.java`.
if (dir.getFileName.toString == "META-INF") {
FileVisitResult.SKIP_SUBTREE
} else {
FileVisitResult.CONTINUE
}
}
}
Files.walkFileTree(sourceClassesDir.toPath, fileVisitor)
if (!outputDir.exists()) {
IO.createDirectory(outputDir)
}
var someDependencyChanged = false
Tracked.diffInputs(cache, FileInfo.lastModified)(filesToCopy.toSet) {
changeReport =>
for (f <- changeReport.removed) {
val relPath = sourceClassesDir.toPath.relativize(f.toPath)
val dest = outputDir.toPath.resolve(relPath)
IO.delete(dest.toFile)
someDependencyChanged = true
}
for (f <- changeReport.modified -- changeReport.removed) {
val relPath = sourceClassesDir.toPath.relativize(f.toPath)
val dest = outputDir.toPath.resolve(relPath)
IO.copyFile(f, dest.toFile)
someDependencyChanged = true
}
for (f <- changeReport.unmodified) {
val relPath = sourceClassesDir.toPath.relativize(f.toPath)
val dest = outputDir.toPath.resolve(relPath)
if (!dest.toFile.exists()) {
IO.copyFile(f, dest.toFile)
someDependencyChanged = true
}
}
}
someDependencyChanged
}
} }

View File

@ -61,11 +61,13 @@ object WithDebugCommand {
val (debugFlags, prefixedRunArgs) = args.span(_ != argSeparator) val (debugFlags, prefixedRunArgs) = args.span(_ != argSeparator)
val runArgs = " " + prefixedRunArgs.drop(1).mkString(" ") val runArgs = " " + prefixedRunArgs.drop(1).mkString(" ")
val taskKey = val taskKeyOpt =
if (debugFlags.contains(benchOnlyCommandName)) BenchTasks.benchOnly if (debugFlags.contains(benchOnlyCommandName))
else if (debugFlags.contains(runCommandName)) Compile / Keys.run Some(BenchTasks.benchOnly)
else if (debugFlags.contains(testOnlyCommandName)) Test / Keys.testOnly else if (debugFlags.contains(runCommandName)) Some(Compile / Keys.run)
else throw new IllegalArgumentException("Invalid command name.") else if (debugFlags.contains(testOnlyCommandName))
Some(Test / Keys.testOnly)
else None
val dumpGraphsOpts = val dumpGraphsOpts =
if (debugFlags.contains(dumpGraphsOption)) truffleDumpGraphsOptions if (debugFlags.contains(dumpGraphsOption)) truffleDumpGraphsOptions
@ -90,14 +92,22 @@ object WithDebugCommand {
debuggerOpts debuggerOpts
).flatten ).flatten
val extracted = Project.extract(state) taskKeyOpt match {
val withJavaOpts = extracted.appendWithoutSession( case None =>
Seq(Compile / Keys.javaOptions ++= javaOpts), state.log.error(
state s"Invalid command name. Expected one of $benchOnlyCommandName, $runCommandName, or $testOnlyCommandName"
) )
Project state.fail
.extract(withJavaOpts) case Some(taskKey) =>
.runInputTask(taskKey, runArgs, withJavaOpts) val extracted = Project.extract(state)
state val withJavaOpts = extracted.appendWithoutSession(
Seq(Compile / Keys.javaOptions ++= javaOpts),
state
)
Project
.extract(withJavaOpts)
.runInputTask(taskKey, runArgs, withJavaOpts)
state
}
} }
} }

View File

@ -1,9 +1,8 @@
addSbtPlugin("com.sandinh" % "sbt-java-module-info" % "0.4.0") addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "2.1.3")
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "2.1.3") addSbtPlugin("ch.epfl.scala" % "sbt-bloop" % "1.5.6")
addSbtPlugin("ch.epfl.scala" % "sbt-bloop" % "1.5.6") addSbtPlugin("com.github.sbt" % "sbt-license-report" % "1.5.0")
addSbtPlugin("com.github.sbt" % "sbt-license-report" % "1.5.0") addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.5.0")
addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.5.0") addSbtPlugin("com.simplytyped" % "sbt-antlr4" % "0.8.3")
addSbtPlugin("com.simplytyped" % "sbt-antlr4" % "0.8.3")
libraryDependencies += "io.circe" %% "circe-yaml" % "0.14.2" libraryDependencies += "io.circe" %% "circe-yaml" % "0.14.2"
libraryDependencies += "commons-io" % "commons-io" % "2.12.0" libraryDependencies += "commons-io" % "commons-io" % "2.12.0"