|
| 1 | +package dotty.tools.scripting |
| 2 | + |
| 3 | +import java.nio.file.{ Files, Path } |
| 4 | +import java.io.File |
| 5 | +import java.net.{ URL, URLClassLoader } |
| 6 | +import java.lang.reflect.{ Modifier, Method } |
| 7 | + |
| 8 | +import scala.jdk.CollectionConverters._ |
| 9 | + |
| 10 | +import dotty.tools.dotc.{ Driver, Compiler } |
| 11 | +import dotty.tools.dotc.core.Contexts, Contexts.{ Context, ContextBase, ctx } |
| 12 | +import dotty.tools.dotc.config.CompilerCommand |
| 13 | +import dotty.tools.io.{ PlainDirectory, Directory } |
| 14 | +import dotty.tools.dotc.reporting.Reporter |
| 15 | +import dotty.tools.dotc.config.Settings.Setting._ |
| 16 | + |
| 17 | +import sys.process._ |
| 18 | + |
| 19 | +class ScriptingDriver(compilerArgs: Array[String], scriptFile: File, scriptArgs: Array[String]) extends Driver: |
| 20 | + def compileAndRun(): Unit = |
| 21 | + val outDir = Files.createTempDirectory("scala3-scripting") |
| 22 | + val (toCompile, rootCtx) = setup(compilerArgs :+ scriptFile.getAbsolutePath, initCtx.fresh) |
| 23 | + given Context = rootCtx.fresh.setSetting(rootCtx.settings.outputDir, |
| 24 | + new PlainDirectory(Directory(outDir))) |
| 25 | + |
| 26 | + if doCompile(newCompiler, toCompile).hasErrors then |
| 27 | + throw ScriptingException("Errors encountered during compilation") |
| 28 | + |
| 29 | + try detectMainMethod(outDir, ctx.settings.classpath.value).invoke(null, scriptArgs) |
| 30 | + catch |
| 31 | + case e: java.lang.reflect.InvocationTargetException => |
| 32 | + throw e.getCause |
| 33 | + |
| 34 | + end compileAndRun |
| 35 | + |
| 36 | + private def detectMainMethod(outDir: Path, classpath: String): Method = |
| 37 | + val outDirURL = outDir.toUri.toURL |
| 38 | + val classpathUrls = classpath.split(":").map(File(_).toURI.toURL) |
| 39 | + val cl = URLClassLoader(classpathUrls :+ outDirURL) |
| 40 | + |
| 41 | + def collectMainMethods(target: File, path: String): List[Method] = |
| 42 | + val nameWithoutExtension = target.getName.takeWhile(_ != '.') |
| 43 | + val targetPath = |
| 44 | + if path.nonEmpty then s"${path}.${nameWithoutExtension}" |
| 45 | + else nameWithoutExtension |
| 46 | + |
| 47 | + if target.isDirectory then |
| 48 | + for |
| 49 | + packageMember <- target.listFiles.toList |
| 50 | + membersMainMethod <- collectMainMethods(packageMember, targetPath) |
| 51 | + yield membersMainMethod |
| 52 | + else if target.getName.endsWith(".class") then |
| 53 | + val cls = cl.loadClass(targetPath) |
| 54 | + val method = |
| 55 | + try cls.getMethod("main", classOf[Array[String]]) |
| 56 | + catch |
| 57 | + case _: java.lang.NoSuchMethodException => null |
| 58 | + |
| 59 | + if method != null && Modifier.isStatic(method.getModifiers) then List(method) |
| 60 | + else Nil |
| 61 | + else Nil |
| 62 | + end collectMainMethods |
| 63 | + |
| 64 | + val candidates = for |
| 65 | + file <- outDir.toFile.listFiles.toList |
| 66 | + method <- collectMainMethods(file, "") |
| 67 | + yield method |
| 68 | + |
| 69 | + candidates match |
| 70 | + case Nil => |
| 71 | + throw ScriptingException("No main methods detected in your script") |
| 72 | + case _ :: _ :: _ => |
| 73 | + throw ScriptingException("More than one main method detected in your script") |
| 74 | + case m :: Nil => m |
| 75 | + end match |
| 76 | + end detectMainMethod |
| 77 | +end ScriptingDriver |
| 78 | + |
| 79 | +case class ScriptingException(msg: String) extends RuntimeException(msg) |
0 commit comments