8000 Graal scala3 by romanowski · Pull Request #830 · VirtusLab/scala-cli · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Graal scala3 #830

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Apr 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 60 additions & 4 deletions build.sc
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import $file.project.settings, settings.{
PublishLocalNoFluff,
ScalaCliCrossSbtModule,
ScalaCliScalafixModule,
ScalaCliCompile,
localRepoResourcePath,
platformExecutableJarExtension,
workspaceDirName
Expand All @@ -35,7 +36,7 @@ implicit def millModuleBasePath: define.BasePath =

object cli extends Cli
// remove once migrate to Scala 3
object cli3 extends Cli { override def myScalaVersion = Scala.scala3 }
object cli3 extends Cli3
object `cli-options` extends CliOptions
object `build-macros` extends Cross[BuildMacros](Scala.defaultInternal, Scala.scala3)
object options extends Cross[Options](Scala.defaultInternal, Scala.scala3)
Expand All @@ -47,6 +48,12 @@ object runner extends Cross[Runner](Scala.all: _*)
object `test-runner` extends Cross[TestRunner](Scala.all: _*)
object `bloop-rifle` extends Cross[BloopRifle](Scala.all: _*)
object `tasty-lib` extends Cross[TastyLib](Scala.all: _*)
// Runtime classes used within native image on Scala 3 replacing runtime from Scala
object `scala3-runtime` extends Scala3Runtime
// Logic to process classes that is shared between build and the scala-cli itself
object `scala3-graal` extends Cross[Scala3Graal](Scala.defaultInternal, Scala.scala3)
// Main app used to process classpath within build itself
object `scala3-graal-processor` extends Scala3GraalProcessor

object stubs extends JavaModule with ScalaCliPublishModule {
def javacOptions = T {
Expand Down Expand Up @@ -412,11 +419,41 @@ class Options(val crossScalaVersion: String) extends BuildLikeModule {
}
}

trait ScalaParse extends SbtModule with ScalaCliPublishModule with settings.ScalaCliCompile {
trait ScalaParse extends SbtModule with ScalaCliPublishModule with ScalaCliCompile {
def ivyDeps = super.ivyDeps() ++ Agg(Deps.scalaparse)
def scalaVersion = Scala.defaultInternal
}

trait Scala3Runtime extends SbtModule with ScalaCliPublishModule with ScalaCliCompile {
def ivyDeps = super.ivyDeps()
def scalaVersion = Scala.scala3
}

class Scala3Graal(val crossScalaVersion: String) extends BuildLikeModule {
def ivyDeps = super.ivyDeps() ++ Agg(
Deps.asm,
Deps.osLib
)

def resources = T.sources {
val extraResourceDir = T.dest / "extra"
// scala3RuntimeFixes.jar is also used within
// resource-config.json and BytecodeProcessor.scala
os.copy.over(
`scala3-runtime`.jar().path,
extraResourceDir / "scala3RuntimeFixes.jar",
createFolders = true
)
super.resources() ++ Seq(mill.PathRef(extraResourceDir))
}
}

trait Scala3GraalProcessor extends ScalaModule {
def moduleDeps = Seq(`scala3-graal`(Scala.scala3))
def scalaVersion = Scala.scala3
def finalMainClass = "scala.cli.graal.CoursierCacheProcessor"
}

class Build(val crossScalaVersion: String) extends BuildLikeModule {
def moduleDeps = Seq(
`options`(),
Expand Down Expand Up @@ -481,7 +518,7 @@ class Build(val crossScalaVersion: String) extends BuildLikeModule {
}
}

trait CliOptions extends SbtModule with ScalaCliPublishModule with settings.ScalaCliCompile {
trait CliOptions extends SbtModule with ScalaCliPublishModule with ScalaCliCompile {
def ivyDeps = super.ivyDeps() ++ Agg(
Deps.caseApp,
Deps.jsoniterCore,
Expand Down Expand Up @@ -511,7 +548,8 @@ trait Cli extends SbtModule with ProtoBuildModule with CliLaunchers
def moduleDeps = Seq(
build(myScalaVersion),
`cli-options`,
`test-runner`(myScalaVersion)
`test-runner`(myScalaVersion),
`scala3-graal`(myScalaVersion)
)

def repositories = super.repositories ++ customRepositories
Expand Down Expand Up @@ -542,6 +580,24 @@ trait Cli extends SbtModule with ProtoBuildModule with CliLaunchers
}
}

trait Cli3 extends Cli {
override def myScalaVersion = Scala.scala3

override def nativeImageClassPath = T {
val classpath = super.nativeImageClassPath().map(_.path).mkString(File.pathSeparator)
val cache = T.dest / "native-cp"
// `scala3-graal-processor`.run() do not give me output and I cannot pass dynamically computed values like classpath
val res = mill.modules.Jvm.callSubprocess(
mainClass = `scala3-graal-processor`.finalMainClass(),
classPath = `scala3-graal-processor`.runClasspath().map(_.path),
mainArgs = Seq(cache.toNIO.toString, classpath),
workingDir = os.pwd
)
val cp = res.out.text
cp.split(File.pathSeparator).toSeq.map(p => mill.PathRef(os.Path(p)))
}
}

trait CliIntegrationBase extends SbtModule with ScalaCliPublishModule with HasTests
with ScalaCliScalafixModule {
def scalaVersion = sv
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@
},
{
"pattern": "\\Qcommons-logging.properties\\E"
},
{
"pattern": ".*scala3RuntimeFixes.jar$"
}
]
},
Expand Down
96 changes: 63 additions & 33 deletions modules/cli/src/main/scala/scala/cli/packaging/NativeImage.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import scala.annotation.tailrec
import scala.build.internal.{NativeBuilderHelper, Runner}
import scala.build.{Build, Logger}
import scala.cli.errors.GraalVMNativeImageError
import scala.cli.graal.{BytecodeProcessor, TempCache}
import scala.util.Properties

object NativeImage {
Expand Down Expand Up @@ -231,46 +232,75 @@ object NativeImage {
maybeWithManifestClassPath(
createManifest = Properties.isWin,
classPath = originalClasspath.map(os.Path(_, os.pwd))
) { classPath =>
val args = extraOptions ++ Seq(
s"-H:Path=${dest / os.up}",
s"-H:Name=${dest.last.stripSuffix(".exe")}", // FIXME Case-insensitive strip suffix?
"-cp",
classPath.map(_.toString).mkString(File.pathSeparator),
mainClass
)
) { processedClassPath =>
val (classPath, toClean, scala3extraOptions) =
if (!build.scalaParams.scalaBinaryVersion.startsWith("3"))
(processedClassPath, Seq[os.Path](), Seq[String]())
else {
val cpString = processedClassPath.mkString(File.pathSeparator)
val processed = BytecodeProcessor.processClassPath(cpString, TempCache).toSeq
val nativeConfigFile = os.temp(suffix = ".json")
os.write.over(
nativeConfigFile,
"""[
| {
| "name": "sun.misc.Unsafe",
| "allDeclaredConstructors": true,
| "allPublicConstructors": true,
| "allDeclaredMethods": true,
| "allDeclaredFields": true
| }
|]
|""".stripMargin
)
val cp = processed.map(_.path)
val options = Seq(s"-H:ReflectionConfigurationFiles=$nativeConfigFile")

maybeWithShorterGraalvmHome(javaHome.javaHome, logger) { graalVMHome =>
(cp, nativeConfigFile +: BytecodeProcessor.toClean(processed), options)
}

val nativeImageCommand = ensureHasNativeImageCommand(graalVMHome, logger)
val command = nativeImageCommand.toString +: args
try {
val args = extraOptions ++ scala3extraOptions ++ Seq(
s"-H:Path=${dest / os.up}",
s"-H:Name=${dest.last.stripSuffix(".exe")}", // FIXME Case-insensitive strip suffix?
"-cp",
classPath.map(_.toString).mkString(File.pathSeparator),
mainClass
)

val exitCode =
if (Properties.isWin)
vcvarsOpt match {
case Some(vcvars) =>
runFromVcvarsBat(command, vcvars, nativeImageWorkDir, logger)
case None =>
Runner.run("unused", command, logger).waitFor()
}
else
Runner.run("unused", command, logger).waitFor()
if (exitCode == 0) {
val actualDest =
maybeWithShorterGraalvmHome(javaHome.javaHome, logger) { graalVMHome =>

val nativeImageCommand = ensureHasNativeImageCommand(graalVMHome, logger)
val command = nativeImageCommand.toString +: args

val exitCode =
if (Properties.isWin)
if (dest.last.endsWith(".exe")) dest
else dest / os.up / s"${dest.last}.exe"
vcvarsOpt match {
case Some(vcvars) =>
runFromVcvarsBat(command, vcvars, nativeImageWorkDir, logger)
case None =>
Runner.run("unused", command, logger).waitFor()
}
else
dest
NativeBuilderHelper.updateProjectAndOutputSha(
actualDest,
nativeImageWorkDir,
cacheData.projectSha
)
Runner.run("unused", command, logger).waitFor()
if (exitCode == 0) {
val actualDest =
if (Properties.isWin)
if (dest.last.endsWith(".exe")) dest
else dest / os.up / s"${dest.last}.exe"
else
dest
NativeBuilderHelper.updateProjectAndOutputSha(
actualDest,
nativeImageWorkDir,
cacheData.projectSha
)
}
else
throw new GraalVMNativeImageError
}
else
throw new GraalVMNativeImageError
}
finally util.Try(toClean.foreach(os.remove.all))
}
}
else
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package scala.cli.integration

import com.eed3si9n.expecty.Expecty.expect

import scala.util.Properties

class TestNativeImageOnScala3 extends munit.FunSuite {

def runTest(args: String*)(expectedLines: String*)(code: String): Unit = {
val dest =
if (Properties.isWin) "testApp.exe"
else "testApp"

val inputs = TestInputs(Seq(os.rel / "Hello.scala" -> code))
inputs.fromRoot { root =>
os.proc(
TestUtil.cli,
"package",
".",
"--native-image",
"-o",
dest,
"--",
"--no-fallback"
).call(
cwd = root,
stdin = os.Inherit,
stdout = os.Inherit
)

expect(os.isFile(root / dest))

// FIXME Check that dest is indeed a binary?

val res = os.proc(root / dest, args).call(cwd = root)
val outputLines = res.out.text().trim.linesIterator.to(Seq)
expect(expectedLines == outputLines)
}
}

test("lazy vals") {
runTest("1")("2") {
"""//> using scala "3.1.1"
|class A(a: String) { lazy val b = a.toInt + 1 }
|@main def add1(i: String) = println(A(i).b)
|""".stripMargin
}
}

test("lazy vals and enums with default scala version") {
runTest("1")("2", "A") {
"""class A(a: String) { lazy val b = a.toInt + 1 }
|enum Ala:
| case A
| case B
|@main def add1(i: String) =
| println(A(i).b)
| println(Ala.valueOf("A"))
|""".stripMargin
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package scala.cli.graal

import java.io.File
import java.nio.channels.FileChannel

object CoursierCacheProcessor {
def main(args: Array[String]) = {
val List(cacheDir, classpath) = args.toList
val cache = DirCache(os.Path(cacheDir, os.pwd))

val newCp = BytecodeProcessor.processClassPath(classpath, cache).map(_.nioPath)

println(newCp.mkString(File.pathSeparator))
}
}
Loading
0