From e38d05e5fd983b839f2331e9f62e40fb34401554 Mon Sep 17 00:00:00 2001 From: Tobias Roeser Date: Fri, 3 Nov 2023 08:53:24 +0100 Subject: [PATCH 1/7] Update Scala to 2.12.18 --- polyglot-scala/pom.xml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/polyglot-scala/pom.xml b/polyglot-scala/pom.xml index 7a3ad7dc..ad37b925 100644 --- a/polyglot-scala/pom.xml +++ b/polyglot-scala/pom.xml @@ -20,9 +20,8 @@ Polyglot :: Scala - - 2.11.12 - 2.11 + 2.12.18 + 2.12 From 9d9faea723b23aed815ba8433c31b0b98da789d8 Mon Sep 17 00:00:00 2001 From: Tobias Roeser Date: Fri, 3 Nov 2023 10:11:54 +0100 Subject: [PATCH 2/7] Vendored com.twitter.util.Eval as-is --- polyglot-scala/pom.xml | 20 +- .../main/scala/com/twitter/util/Eval.scala | 620 ++++++++++++++++++ 2 files changed, 639 insertions(+), 1 deletion(-) create mode 100644 polyglot-scala/src/main/scala/com/twitter/util/Eval.scala diff --git a/polyglot-scala/pom.xml b/polyglot-scala/pom.xml index ad37b925..5932151a 100644 --- a/polyglot-scala/pom.xml +++ b/polyglot-scala/pom.xml @@ -44,10 +44,28 @@ com.twitter - util-eval_${scala.bin.version} + util-core_${scala.bin.version} 6.43.0 + + com.twitter + util-function_${scala.bin.version} + 6.43.0 + + + + org.scala-lang.modules + scala-parser-combinators_${scala.bin.version} + 1.0.4 + + + + org.scala-lang.modules + scala-xml_${scala.bin.version} + 1.0.6 + + com.googlecode.kiama kiama_${scala.bin.version} diff --git a/polyglot-scala/src/main/scala/com/twitter/util/Eval.scala b/polyglot-scala/src/main/scala/com/twitter/util/Eval.scala new file mode 100644 index 00000000..45ca8667 --- /dev/null +++ b/polyglot-scala/src/main/scala/com/twitter/util/Eval.scala @@ -0,0 +1,620 @@ +/* + * Copied from https://github.com/twitter/util/blob/84d90c797ac0fb024f9fd6c3bf5fac8353d5cf13/util-eval/src/main/scala/com/twitter/util/Eval.scala + * which was copyrighted as 2010 Twitter, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.twitter.util + +import com.twitter.conversions.string._ +import com.twitter.io.StreamIO +import java.io._ +import java.math.BigInteger +import java.net.URLClassLoader +import java.security.MessageDigest +import java.util.Random +import java.util.jar.JarFile +import scala.collection.mutable +import scala.io.Source +import scala.reflect.internal.util.{BatchSourceFile, Position} +import scala.tools.nsc.interpreter.AbstractFileClassLoader +import scala.tools.nsc.io.{AbstractFile, VirtualDirectory} +import scala.tools.nsc.reporters.{Reporter, AbstractReporter} +import scala.tools.nsc.{Global, Settings} +import scala.util.matching.Regex + + +/** + * Evaluate a file or string and return the result. + */ +@deprecated("use a throw-away instance of Eval instead", "1.8.1") +object Eval extends Eval { + private val jvmId = java.lang.Math.abs(new Random().nextInt()) + val classCleaner: Regex = "\\W".r +} + +/** + * Evaluates files, strings, or input streams as Scala code, and returns the result. + * + * If `target` is `None`, the results are compiled to memory (and are therefore ephemeral). If + * `target` is `Some(path)`, the path must point to a directory, and classes will be saved into + * that directory. + * + * Eval also supports a limited set of preprocessors. Currently, "limited" means "exactly one": + * directives of the form `#include `. + * + * The flow of evaluation is: + * - extract a string of code from the file, string, or input stream + * - run preprocessors on that string + * - wrap processed code in an `apply` method in a generated class + * - compile the class + * - contruct an instance of that class + * - return the result of `apply()` + */ +class Eval(target: Option[File]) { + /** + * empty constructor for backwards compatibility + */ + def this() { + this(None) + } + + import Eval.jvmId + + private lazy val compilerPath = try { + classPathOfClass("scala.tools.nsc.Interpreter") + } catch { + case e: Throwable => + throw new RuntimeException("Unable to load Scala interpreter from classpath (scala-compiler jar is missing?)", e) + } + + private lazy val libPath = try { + classPathOfClass("scala.AnyVal") + } catch { + case e: Throwable => + throw new RuntimeException("Unable to load scala base object from classpath (scala-library jar is missing?)", e) + } + + /** + * Preprocessors to run the code through before it is passed to the Scala compiler. + * if you want to add new resolvers, you can do so with + * new Eval(...) { + * lazy val preprocessors = {...} + * } + */ + protected lazy val preprocessors: Seq[Preprocessor] = + Seq( + new IncludePreprocessor( + Seq( + new ClassScopedResolver(getClass), + new FilesystemResolver(new File(".")), + new FilesystemResolver(new File("." + File.separator + "config")) + ) ++ ( + Option(System.getProperty("com.twitter.util.Eval.includePath")) map { path => + new FilesystemResolver(new File(path)) + } + ) + ) + ) + + // For derived classes to provide an alternate compiler message handler. + protected lazy val compilerMessageHandler: Option[Reporter] = None + + // For derived classes do customize or override the default compiler settings. + protected lazy val compilerSettings: Settings = new EvalSettings(target) + + // Primary encapsulation around native Scala compiler + private[this] lazy val compiler = new StringCompiler(codeWrapperLineOffset, target, compilerSettings, compilerMessageHandler) + + /** + * run preprocessors on our string, returning a String that is the processed source + */ + def sourceForString(code: String): String = { + preprocessors.foldLeft(code) { (acc, p) => + p(acc) + } + } + + /** + * write the current checksum to a file + */ + def writeChecksum(checksum: String, file: File) { + val writer = new FileWriter(file) + writer.write("%s".format(checksum)) + writer.close() + } + + /** + * val i: Int = new Eval()("1 + 1") // => 2 + */ + def apply[T](code: String, resetState: Boolean = true): T = { + val processed = sourceForString(code) + applyProcessed(processed, resetState) + } + + /** + * val i: Int = new Eval()(new File("...")) + */ + def apply[T](files: File*): T = { + if (target.isDefined) { + val targetDir = target.get + val unprocessedSource = files.map { scala.io.Source.fromFile(_).mkString }.mkString("\n") + val processed = sourceForString(unprocessedSource) + val sourceChecksum = uniqueId(processed, None) + val checksumFile = new File(targetDir, "checksum") + val lastChecksum = if (checksumFile.exists) { + Source.fromFile(checksumFile).getLines().take(1).toList.head + } else { + -1 + } + + if (lastChecksum != sourceChecksum) { + compiler.reset() + writeChecksum(sourceChecksum, checksumFile) + } + + // why all this nonsense? Well. + // 1) We want to know which file the eval'd code came from + // 2) But sometimes files have characters that aren't valid in Java/Scala identifiers + // 3) And sometimes files with the same name live in different subdirectories + // so, clean it hash it and slap it on the end of Evaluator + val cleanBaseName = fileToClassName(files(0)) + val className = "Evaluator__%s_%s".format( + cleanBaseName, sourceChecksum) + applyProcessed(className, processed, false) + } else { + apply(files.map { scala.io.Source.fromFile(_).mkString }.mkString("\n"), true) + } + } + + /** + * val i: Int = new Eval()(getClass.getResourceAsStream("...")) + */ + def apply[T](stream: InputStream): T = { + apply(sourceForString(Source.fromInputStream(stream).mkString)) + } + + /** + * same as apply[T], but does not run preprocessors. + * Will generate a classname of the form Evaluater__, + * where unique is computed from the jvmID (a random number) + * and a digest of code + */ + def applyProcessed[T](code: String, resetState: Boolean): T = { + val id = uniqueId(code) + val className = "Evaluator__" + id + applyProcessed(className, code, resetState) + } + + /** + * same as apply[T], but does not run preprocessors. + */ + def applyProcessed[T](className: String, code: String, resetState: Boolean): T = { + val cls = compiler(wrapCodeInClass(className, code), className, resetState) + cls.getConstructor().newInstance().asInstanceOf[() => Any].apply().asInstanceOf[T] + } + + /** + * converts the given file to evaluable source. + * delegates to toSource(code: String) + */ + def toSource(file: File): String = { + toSource(scala.io.Source.fromFile(file).mkString) + } + + /** + * converts the given file to evaluable source. + */ + def toSource(code: String): String = { + sourceForString(code) + } + + /** + * Compile an entire source file into the virtual classloader. + */ + def compile(code: String) { + compiler(sourceForString(code)) + } + + /** + * Like `Eval()`, but doesn't reset the virtual classloader before evaluating. So if you've + * loaded classes with `compile`, they can be referenced/imported in code run by `inPlace`. + */ + def inPlace[T](code: String) = { + apply[T](code, false) + } + + /** + * Check if code is Eval-able. + * @throws CompilerException if not Eval-able. + */ + def check(code: String) { + val id = uniqueId(sourceForString(code)) + val className = "Evaluator__" + id + val wrappedCode = wrapCodeInClass(className, code) + resetReporter() + compile(wrappedCode) // may throw CompilerException + } + + /** + * Check if files are Eval-able. + * @throws CompilerException if not Eval-able. + */ + def check(files: File*) { + val code = files.map { scala.io.Source.fromFile(_).mkString }.mkString("\n") + check(code) + } + + /** + * Check if stream is Eval-able. + * @throws CompilerException if not Eval-able. + */ + def check(stream: InputStream) { + check(scala.io.Source.fromInputStream(stream).mkString) + } + + def findClass(className: String): Class[_] = { + compiler.findClass(className).getOrElse { throw new ClassNotFoundException("no such class: " + className) } + } + + private[util] def resetReporter(): Unit = { + compiler.resetReporter() + } + + private[util] def uniqueId(code: String, idOpt: Option[Int] = Some(jvmId)): String = { + val digest = MessageDigest.getInstance("SHA-1").digest(code.getBytes()) + val sha = new BigInteger(1, digest).toString(16) + idOpt match { + case Some(id) => sha + "_" + jvmId + case _ => sha + } + } + + private[util] def fileToClassName(f: File): String = { + // HOPE YOU'RE HAPPY GUYS!!!! + /* __ + * __/|_/ /_ __ ______ ________/|_ + * | / __ \/ / / / __ `/ ___/ / + * /_ __/ / / / /_/ / /_/ (__ )_ __| + * |/ /_/ /_/\__,_/\__, /____/ |/ + * /____/ + */ + val fileName = f.getName + val baseName = fileName.lastIndexOf('.') match { + case -1 => fileName + case dot => fileName.substring(0, dot) + } + baseName.regexSub(Eval.classCleaner) { m => + "$%02x".format(m.group(0).charAt(0).toInt) + } + } + + /* + * Wraps source code in a new class with an apply method. + * NB: If this method is changed, make sure `codeWrapperLineOffset` is correct. + */ + private[this] def wrapCodeInClass(className: String, code: String) = { + "class " + className + " extends (() => Any) with java.io.Serializable {\n" + + " def apply() = {\n" + + code + "\n" + + " }\n" + + "}\n" + } + + /* + * Defines the number of code lines that proceed evaluated code. + * Used to ensure compile error messages report line numbers aligned with user's code. + * NB: If `wrapCodeInClass(String,String)` is changed, make sure this remains correct. + */ + private[this] val codeWrapperLineOffset = 2 + + /* + * For a given FQ classname, trick the resource finder into telling us the containing jar. + */ + private def classPathOfClass(className: String) = { + val resource = className.split('.').mkString("/", "/", ".class") + val path = getClass.getResource(resource).getPath + if (path.indexOf("file:") >= 0) { + val indexOfFile = path.indexOf("file:") + 5 + val indexOfSeparator = path.lastIndexOf('!') + List(path.substring(indexOfFile, indexOfSeparator)) + } else { + require(path.endsWith(resource)) + List(path.substring(0, path.length - resource.length + 1)) + } + } + + /* + * Try to guess our app's classpath. + * This is probably fragile. + */ + lazy val impliedClassPath: List[String] = { + def getClassPath(cl: ClassLoader, acc: List[List[String]] = List.empty): List[List[String]] = { + val cp = cl match { + case urlClassLoader: URLClassLoader => urlClassLoader.getURLs.filter(_.getProtocol == "file"). + map(u => new File(u.toURI).getPath).toList + case _ => Nil + } + cl.getParent match { + case null => (cp :: acc).reverse + case parent => getClassPath(parent, cp :: acc) + } + } + + val classPath = getClassPath(this.getClass.getClassLoader) + val currentClassPath = classPath.head + + // if there's just one thing in the classpath, and it's a jar, assume an executable jar. + currentClassPath ::: (if (currentClassPath.size == 1 && currentClassPath(0).endsWith(".jar")) { + val jarFile = currentClassPath(0) + val relativeRoot = new File(jarFile).getParentFile() + val nestedClassPath = new JarFile(jarFile).getManifest.getMainAttributes.getValue("Class-Path") + if (nestedClassPath eq null) { + Nil + } else { + nestedClassPath.split(" ").map { f => new File(relativeRoot, f).getAbsolutePath }.toList + } + } else { + Nil + }) ::: classPath.tail.flatten + } + + trait Preprocessor { + def apply(code: String): String + } + + trait Resolver { + def resolvable(path: String): Boolean + def get(path: String): InputStream + } + + class FilesystemResolver(root: File) extends Resolver { + private[this] def file(path: String): File = + new File(root.getAbsolutePath + File.separator + path) + + def resolvable(path: String): Boolean = + file(path).exists + + def get(path: String): InputStream = + new FileInputStream(file(path)) + } + + class ClassScopedResolver(clazz: Class[_]) extends Resolver { + private[this] def quotePath(path: String) = + "/" + path + + def resolvable(path: String): Boolean = + clazz.getResourceAsStream(quotePath(path)) != null + + def get(path: String): InputStream = + clazz.getResourceAsStream(quotePath(path)) + } + + class ResolutionFailedException(message: String) extends Exception + + /* + * This is a preprocesor that can include files by requesting them from the given classloader + * + * Thusly, if you put FS directories on your classpath (e.g. config/ under your app root,) you + * mix in configuration from the filesystem. + * + * @example #include file-name.scala + * + * This is the only directive supported by this preprocessor. + * + * Note that it is *not* recursive. Included files cannot have includes + */ + class IncludePreprocessor(resolvers: Seq[Resolver]) extends Preprocessor { + def maximumRecursionDepth = 100 + + def apply(code: String): String = + apply(code, maximumRecursionDepth) + + def apply(code: String, maxDepth: Int): String = { + val lines = code.lines map { line: String => + val tokens = line.trim.split(' ') + if (tokens.length == 2 && tokens(0).equals("#include")) { + val path = tokens(1) + resolvers find { resolver: Resolver => + resolver.resolvable(path) + } match { + case Some(r: Resolver) => { + // recursively process includes + if (maxDepth == 0) { + throw new IllegalStateException("Exceeded maximum recusion depth") + } else { + apply(StreamIO.buffer(r.get(path)).toString, maxDepth - 1) + } + } + case _ => + throw new IllegalStateException("No resolver could find '%s'".format(path)) + } + } else { + line + } + } + lines.mkString("\n") + } + } + + lazy val compilerOutputDir = target match { + case Some(dir) => AbstractFile.getDirectory(dir) + case None => new VirtualDirectory("(memory)", None) + } + + class EvalSettings(targetDir: Option[File]) extends Settings { + nowarnings.value = true // warnings are exceptions, so disable + outputDirs.setSingleOutput(compilerOutputDir) + private[this] val pathList = compilerPath ::: libPath + bootclasspath.value = pathList.mkString(File.pathSeparator) + classpath.value = (pathList ::: impliedClassPath).mkString(File.pathSeparator) + } + + /** + * Dynamic scala compiler. Lots of (slow) state is created, so it may be advantageous to keep + * around one of these and reuse it. + */ + private class StringCompiler( + lineOffset: Int, targetDir: Option[File], settings: Settings, messageHandler: Option[Reporter]) { + + val cache = new mutable.HashMap[String, Class[_]]() + val target = compilerOutputDir + + trait MessageCollector { + val messages: Seq[List[String]] + } + + val reporter = messageHandler getOrElse new AbstractReporter with MessageCollector { + val settings = StringCompiler.this.settings + val messages = new mutable.ListBuffer[List[String]] + + def display(pos: Position, message: String, severity: Severity) { + severity.count += 1 + val severityName = severity match { + case ERROR => "error: " + case WARNING => "warning: " + case _ => "" + } + // the line number is not always available + val lineMessage = + try { + "line " + (pos.line - lineOffset) + } catch { + case _: Throwable => "" + } + messages += (severityName + lineMessage + ": " + message) :: + (if (pos.isDefined) { + pos.inUltimateSource(pos.source).lineContent.stripLineEnd :: + (" " * (pos.column - 1) + "^") :: + Nil + } else { + Nil + }) + } + + def displayPrompt { + // no. + } + + override def reset { + super.reset + messages.clear() + } + } + + val global = new Global(settings, reporter) + + /* + * Class loader for finding classes compiled by this StringCompiler. + * After each reset, this class loader will not be able to find old compiled classes. + */ + var classLoader = new AbstractFileClassLoader(target, this.getClass.getClassLoader) + + def reset() { + targetDir match { + case None => { + target.asInstanceOf[VirtualDirectory].clear() + } + case Some(t) => { + target.foreach { abstractFile => + if (abstractFile.file == null || abstractFile.file.getName.endsWith(".class")) { + abstractFile.delete() + } + } + } + } + cache.clear() + reporter.reset() + classLoader = new AbstractFileClassLoader(target, this.getClass.getClassLoader) + } + + def resetReporter(): Unit = { + synchronized { + reporter.reset() + } + } + + object Debug { + val enabled = + System.getProperty("eval.debug") != null + + def printWithLineNumbers(code: String) { + printf("Code follows (%d bytes)\n", code.length) + + var numLines = 0 + code.lines foreach { line: String => + numLines += 1 + println(numLines.toString.padTo(5, ' ') + "| " + line) + } + } + } + + def findClass(className: String): Option[Class[_]] = { + synchronized { + cache.get(className).orElse { + try { + val cls = classLoader.loadClass(className) + cache(className) = cls + Some(cls) + } catch { + case e: ClassNotFoundException => None + } + } + } + } + + /** + * Compile scala code. It can be found using the above class loader. + */ + def apply(code: String) { + if (Debug.enabled) + Debug.printWithLineNumbers(code) + + //reset reporter, or will always throw exception after one error while resetState==false + resetReporter() + + // if you're looking for the performance hit, it's 1/2 this line... + val compiler = new global.Run + val sourceFiles = List(new BatchSourceFile("(inline)", code)) + // ...and 1/2 this line: + compiler.compileSources(sourceFiles) + + if (reporter.hasErrors || reporter.WARNING.count > 0) { + val msgs: List[List[String]] = reporter match { + case collector: MessageCollector => + collector.messages.toList + case _ => + List(List(reporter.toString)) + } + throw new CompilerException(msgs) + } + } + + /** + * Compile a new class, load it, and return it. Thread-safe. + */ + def apply(code: String, className: String, resetState: Boolean = true): Class[_] = { + synchronized { + if (resetState) reset() + findClass(className).getOrElse { + apply(code) + findClass(className).get + } + } + } + } + + class CompilerException(val messages: List[List[String]]) extends Exception( + "Compiler exception " + messages.map(_.mkString("\n")).mkString("\n")) +} \ No newline at end of file From a8fa0705c1df677d28f3495b840fcf7da659ddd9 Mon Sep 17 00:00:00 2001 From: Tobias Roeser Date: Fri, 3 Nov 2023 10:19:57 +0100 Subject: [PATCH 3/7] Version bumps --- polyglot-scala/pom.xml | 2 +- .../src/it/java-and-scala-project-MavenModel/pom.scala | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/polyglot-scala/pom.xml b/polyglot-scala/pom.xml index 5932151a..923e08a1 100644 --- a/polyglot-scala/pom.xml +++ b/polyglot-scala/pom.xml @@ -115,7 +115,7 @@ net.alchim31.maven scala-maven-plugin - 4.8.0 + 4.8.1 diff --git a/polyglot-scala/src/it/java-and-scala-project-MavenModel/pom.scala b/polyglot-scala/src/it/java-and-scala-project-MavenModel/pom.scala index 7d43cbbf..2b8d2196 100644 --- a/polyglot-scala/src/it/java-and-scala-project-MavenModel/pom.scala +++ b/polyglot-scala/src/it/java-and-scala-project-MavenModel/pom.scala @@ -6,22 +6,22 @@ Model( name = "Test for Java + Scala compilation", description = "Test for Java + Scala compilation", dependencies = Seq( - "org.scala-lang" % "scala-library" % "2.12.15" + "org.scala-lang" % "scala-library" % "2.12.18" ), build = Build( pluginManagement = PluginManagement( plugins = Seq( Plugin( - "net.alchim31.maven" % "scala-maven-plugin" % "4.3.1" + "net.alchim31.maven" % "scala-maven-plugin" % "4.8.1" ), Plugin( - "org.apache.maven.plugins" % "maven-compiler-plugin" % "2.0.2" + "org.apache.maven.plugins" % "maven-compiler-plugin" % "3.11.0" ) ) ), plugins = Seq( Plugin( - "net.alchim31.maven" % "scala-maven-plugin" % "4.3.1", + "net.alchim31.maven" % "scala-maven-plugin" % "4.8.1", executions = Seq( Execution( id = "scala-compile-first", From e152eab53e27ca84ed570f3f462ed42f997b27d6 Mon Sep 17 00:00:00 2001 From: Tobias Roeser Date: Fri, 3 Nov 2023 10:20:49 +0100 Subject: [PATCH 4/7] Slightly refactored Eval to make it work --- .../main/scala/com/twitter/util/Eval.scala | 72 +++++++++---------- .../polyglot/scala/ScalaModelReader.scala | 2 +- 2 files changed, 36 insertions(+), 38 deletions(-) diff --git a/polyglot-scala/src/main/scala/com/twitter/util/Eval.scala b/polyglot-scala/src/main/scala/com/twitter/util/Eval.scala index 45ca8667..efe3ef6b 100644 --- a/polyglot-scala/src/main/scala/com/twitter/util/Eval.scala +++ b/polyglot-scala/src/main/scala/com/twitter/util/Eval.scala @@ -1,5 +1,5 @@ /* - * Copied from https://github.com/twitter/util/blob/84d90c797ac0fb024f9fd6c3bf5fac8353d5cf13/util-eval/src/main/scala/com/twitter/util/Eval.scala + * Based on a copy from https://github.com/twitter/util/blob/84d90c797ac0fb024f9fd6c3bf5fac8353d5cf13/util-eval/src/main/scala/com/twitter/util/Eval.scala * which was copyrighted as 2010 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); you may @@ -19,29 +19,30 @@ package com.twitter.util import com.twitter.conversions.string._ import com.twitter.io.StreamIO + import java.io._ import java.math.BigInteger import java.net.URLClassLoader import java.security.MessageDigest import java.util.Random +import java.util.concurrent.atomic.AtomicInteger import java.util.jar.JarFile import scala.collection.mutable import scala.io.Source import scala.reflect.internal.util.{BatchSourceFile, Position} -import scala.tools.nsc.interpreter.AbstractFileClassLoader import scala.tools.nsc.io.{AbstractFile, VirtualDirectory} -import scala.tools.nsc.reporters.{Reporter, AbstractReporter} +import scala.tools.nsc.reporters.{FilteringReporter, Reporter} +import scala.tools.nsc.util.AbstractFileClassLoader import scala.tools.nsc.{Global, Settings} import scala.util.matching.Regex - -/** - * Evaluate a file or string and return the result. - */ -@deprecated("use a throw-away instance of Eval instead", "1.8.1") -object Eval extends Eval { +object Eval { private val jvmId = java.lang.Math.abs(new Random().nextInt()) val classCleaner: Regex = "\\W".r + + class CompilerException(val messages: List[List[String]]) extends Exception( + "Compiler exception " + messages.map(_.mkString("\n")).mkString("\n")) + } /** @@ -63,14 +64,7 @@ object Eval extends Eval { * - return the result of `apply()` */ class Eval(target: Option[File]) { - /** - * empty constructor for backwards compatibility - */ - def this() { - this(None) - } - - import Eval.jvmId + import Eval._ private lazy val compilerPath = try { classPathOfClass("scala.tools.nsc.Interpreter") @@ -94,17 +88,17 @@ class Eval(target: Option[File]) { * } */ protected lazy val preprocessors: Seq[Preprocessor] = - Seq( + Seq[Preprocessor]( new IncludePreprocessor( - Seq( + Seq[Resolver]( new ClassScopedResolver(getClass), new FilesystemResolver(new File(".")), new FilesystemResolver(new File("." + File.separator + "config")) - ) ++ ( - Option(System.getProperty("com.twitter.util.Eval.includePath")) map { path => - new FilesystemResolver(new File(path)) - } - ) + ) ++ + Option(System.getProperty("com.twitter.util.Eval.includePath")) + .fold(Seq[Resolver]()){ path => + Seq[Resolver](new FilesystemResolver(new File(path))) + } ) ) @@ -473,14 +467,23 @@ class Eval(target: Option[File]) { trait MessageCollector { val messages: Seq[List[String]] + val counts: Map[_root_.scala.reflect.internal.Reporter.Severity, AtomicInteger] } - val reporter = messageHandler getOrElse new AbstractReporter with MessageCollector { + val reporter = messageHandler getOrElse new FilteringReporter with MessageCollector { val settings = StringCompiler.this.settings val messages = new mutable.ListBuffer[List[String]] + val counts = Map( + ERROR -> new AtomicInteger(0), + WARNING -> new AtomicInteger(0), + INFO -> new AtomicInteger(0) + + ) - def display(pos: Position, message: String, severity: Severity) { - severity.count += 1 + override def hasErrors: Boolean = super.hasErrors || (counts(ERROR).get() > 0) + + override def doReport(pos: Position, msg: String, severity: Severity): Unit = { + counts(severity).intValue() val severityName = severity match { case ERROR => "error: " case WARNING => "warning: " @@ -493,9 +496,9 @@ class Eval(target: Option[File]) { } catch { case _: Throwable => "" } - messages += (severityName + lineMessage + ": " + message) :: + messages += (severityName + lineMessage + ": " + msg) :: (if (pos.isDefined) { - pos.inUltimateSource(pos.source).lineContent.stripLineEnd :: + pos.finalPosition.lineContent.stripLineEnd :: (" " * (pos.column - 1) + "^") :: Nil } else { @@ -503,13 +506,10 @@ class Eval(target: Option[File]) { }) } - def displayPrompt { - // no. - } - override def reset { super.reset messages.clear() + counts.foreach(p => p._2.set(0)) } } @@ -590,14 +590,14 @@ class Eval(target: Option[File]) { // ...and 1/2 this line: compiler.compileSources(sourceFiles) - if (reporter.hasErrors || reporter.WARNING.count > 0) { + if (reporter.hasErrors) { val msgs: List[List[String]] = reporter match { case collector: MessageCollector => collector.messages.toList case _ => List(List(reporter.toString)) } - throw new CompilerException(msgs) + throw new Eval.CompilerException(msgs) } } @@ -615,6 +615,4 @@ class Eval(target: Option[File]) { } } - class CompilerException(val messages: List[List[String]]) extends Exception( - "Compiler exception " + messages.map(_.mkString("\n")).mkString("\n")) } \ No newline at end of file diff --git a/polyglot-scala/src/main/scala/org/sonatype/maven/polyglot/scala/ScalaModelReader.scala b/polyglot-scala/src/main/scala/org/sonatype/maven/polyglot/scala/ScalaModelReader.scala index 15467a3d..cc0dbe21 100644 --- a/polyglot-scala/src/main/scala/org/sonatype/maven/polyglot/scala/ScalaModelReader.scala +++ b/polyglot-scala/src/main/scala/org/sonatype/maven/polyglot/scala/ScalaModelReader.scala @@ -260,7 +260,7 @@ class ScalaModelReader @Inject() (executeManager: ExecuteManager) extends ModelR try { eval.apply[ScalaModel](sourcePomFile) } catch { - case e: eval.CompilerException => + case e: Eval.CompilerException => // ModuleParseException is able to provide exact position (line nr., column nr.), so if later // versions of CompilerException make those information available, we should map them here (instead of zeros). // Currently, the information is only available as text in the exeception message. From c1ea8a187cf6c6da2be657f73794f7d63d888f69 Mon Sep 17 00:00:00 2001 From: Tobias Roeser Date: Fri, 3 Nov 2023 10:41:16 +0100 Subject: [PATCH 5/7] Renamed package --- .../sonatype/maven/polyglot/scala/ScalaModelReader.scala | 2 +- .../sonatype/maven/polyglot/scala/eval}/Eval.scala | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) rename polyglot-scala/src/main/scala/{com/twitter/util => org/sonatype/maven/polyglot/scala/eval}/Eval.scala (98%) diff --git a/polyglot-scala/src/main/scala/org/sonatype/maven/polyglot/scala/ScalaModelReader.scala b/polyglot-scala/src/main/scala/org/sonatype/maven/polyglot/scala/ScalaModelReader.scala index cc0dbe21..a99a31dc 100644 --- a/polyglot-scala/src/main/scala/org/sonatype/maven/polyglot/scala/ScalaModelReader.scala +++ b/polyglot-scala/src/main/scala/org/sonatype/maven/polyglot/scala/ScalaModelReader.scala @@ -11,7 +11,7 @@ import scala.collection.immutable import scala.language.implicitConversions import com.twitter.io.StreamIO -import com.twitter.util.Eval +import org.sonatype.maven.polyglot.scala.eval.Eval import java.io._ import java.util diff --git a/polyglot-scala/src/main/scala/com/twitter/util/Eval.scala b/polyglot-scala/src/main/scala/org/sonatype/maven/polyglot/scala/eval/Eval.scala similarity index 98% rename from polyglot-scala/src/main/scala/com/twitter/util/Eval.scala rename to polyglot-scala/src/main/scala/org/sonatype/maven/polyglot/scala/eval/Eval.scala index efe3ef6b..930cabe9 100644 --- a/polyglot-scala/src/main/scala/com/twitter/util/Eval.scala +++ b/polyglot-scala/src/main/scala/org/sonatype/maven/polyglot/scala/eval/Eval.scala @@ -14,8 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -package com.twitter.util +package org.sonatype.maven.polyglot.scala.eval import com.twitter.conversions.string._ import com.twitter.io.StreamIO @@ -262,11 +261,11 @@ class Eval(target: Option[File]) { compiler.findClass(className).getOrElse { throw new ClassNotFoundException("no such class: " + className) } } - private[util] def resetReporter(): Unit = { + private[scala] def resetReporter(): Unit = { compiler.resetReporter() } - private[util] def uniqueId(code: String, idOpt: Option[Int] = Some(jvmId)): String = { + private[scala] def uniqueId(code: String, idOpt: Option[Int] = Some(jvmId)): String = { val digest = MessageDigest.getInstance("SHA-1").digest(code.getBytes()) val sha = new BigInteger(1, digest).toString(16) idOpt match { @@ -275,7 +274,7 @@ class Eval(target: Option[File]) { } } - private[util] def fileToClassName(f: File): String = { + private[scala] def fileToClassName(f: File): String = { // HOPE YOU'RE HAPPY GUYS!!!! /* __ * __/|_/ /_ __ ______ ________/|_ From f5135c1377148d2d937fd945cad3dc77a131d8cb Mon Sep 17 00:00:00 2001 From: Tobias Roeser Date: Fri, 3 Nov 2023 10:55:11 +0100 Subject: [PATCH 6/7] Cleanup --- polyglot-scala/pom.xml | 6 ------ .../org/sonatype/maven/polyglot/scala/eval/Eval.scala | 7 +++---- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/polyglot-scala/pom.xml b/polyglot-scala/pom.xml index 923e08a1..0e8c47a6 100644 --- a/polyglot-scala/pom.xml +++ b/polyglot-scala/pom.xml @@ -54,12 +54,6 @@ 6.43.0 - - org.scala-lang.modules - scala-parser-combinators_${scala.bin.version} - 1.0.4 - - org.scala-lang.modules scala-xml_${scala.bin.version} diff --git a/polyglot-scala/src/main/scala/org/sonatype/maven/polyglot/scala/eval/Eval.scala b/polyglot-scala/src/main/scala/org/sonatype/maven/polyglot/scala/eval/Eval.scala index 930cabe9..174342cb 100644 --- a/polyglot-scala/src/main/scala/org/sonatype/maven/polyglot/scala/eval/Eval.scala +++ b/polyglot-scala/src/main/scala/org/sonatype/maven/polyglot/scala/eval/Eval.scala @@ -28,10 +28,9 @@ import java.util.concurrent.atomic.AtomicInteger import java.util.jar.JarFile import scala.collection.mutable import scala.io.Source -import scala.reflect.internal.util.{BatchSourceFile, Position} -import scala.tools.nsc.io.{AbstractFile, VirtualDirectory} +import scala.reflect.internal.util.{AbstractFileClassLoader, BatchSourceFile, Position} +import scala.reflect.io.{AbstractFile, VirtualDirectory} import scala.tools.nsc.reporters.{FilteringReporter, Reporter} -import scala.tools.nsc.util.AbstractFileClassLoader import scala.tools.nsc.{Global, Settings} import scala.util.matching.Regex @@ -518,7 +517,7 @@ class Eval(target: Option[File]) { * Class loader for finding classes compiled by this StringCompiler. * After each reset, this class loader will not be able to find old compiled classes. */ - var classLoader = new AbstractFileClassLoader(target, this.getClass.getClassLoader) + private var classLoader = new AbstractFileClassLoader(target, this.getClass.getClassLoader) def reset() { targetDir match { From 21e6cee8b9da0545e7c50ac7560feadadf6bf052 Mon Sep 17 00:00:00 2001 From: Tobias Roeser Date: Fri, 3 Nov 2023 11:06:23 +0100 Subject: [PATCH 7/7] Dependency cleanup --- polyglot-scala/pom.xml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/polyglot-scala/pom.xml b/polyglot-scala/pom.xml index 0e8c47a6..0c817b35 100644 --- a/polyglot-scala/pom.xml +++ b/polyglot-scala/pom.xml @@ -48,12 +48,6 @@ 6.43.0 - - com.twitter - util-function_${scala.bin.version} - 6.43.0 - - org.scala-lang.modules scala-xml_${scala.bin.version}