From c38a112b7aacb65b470738efa7dd40320c7dad9b Mon Sep 17 00:00:00 2001 From: Jan Chyb Date: Tue, 14 Dec 2021 17:14:28 +0100 Subject: [PATCH 1/4] Add option for NIR file deserialization to scala-native-p --- .../scala/scalanative/cli/ScalaNativeP.scala | 80 ++++++++++++++++++- .../scalanative/cli/options/POptions.scala | 4 + 2 files changed, 80 insertions(+), 4 deletions(-) diff --git a/cli/src/main/scala/scala/scalanative/cli/ScalaNativeP.scala b/cli/src/main/scala/scala/scalanative/cli/ScalaNativeP.scala index 00b633a..77b83e5 100644 --- a/cli/src/main/scala/scala/scalanative/cli/ScalaNativeP.scala +++ b/cli/src/main/scala/scala/scalanative/cli/ScalaNativeP.scala @@ -9,6 +9,10 @@ import scala.scalanative.cli.options._ import scala.scalanative.nir.Global import scala.scalanative.build.Config import scala.scalanative.linker.ClassLoader +import java.nio.file.Path +import scala.scalanative.io.VirtualDirectory +import scala.scalanative.nir.serialization.deserializeBinary +import java.nio.file.Files object ScalaNativeP extends CaseApp[POptions] { @@ -19,7 +23,10 @@ object ScalaNativeP extends CaseApp[POptions] { } if (args.all.isEmpty) { - System.err.println("Required NIR file not specified.") + if (options.nirFiles) + System.err.println("Required NIR file not specified.") + else + System.err.println("Required class/object not specified.") exit(1) } @@ -32,6 +39,14 @@ object ScalaNativeP extends CaseApp[POptions] { System.err.println(s"Ignoring non existing path: $path") } + if (options.nirFiles) deserializeFiles(classpath, args.all) + else deserializeClassesOrObjects(classpath, args.all) + } + + private def deserializeClassesOrObjects( + classpath: List[Path], + args: Seq[String] + ): Unit = { Scope { implicit scope => val classLoader = ClassLoader.fromDisk { @@ -39,9 +54,9 @@ object ScalaNativeP extends CaseApp[POptions] { } for { - fileName <- args.all + className <- args } { - classLoader.load(Global.Top(fileName)) match { + classLoader.load(Global.Top(className)) match { case Some(defns) => defns .sortBy(_.name.mangle) @@ -49,7 +64,64 @@ object ScalaNativeP extends CaseApp[POptions] { println(d.show) println() } - case None => fail(s"Not found class/object with name `${fileName}`") + case None => fail(s"Not found class/object with name `${className}`") + } + } + } + } + + private def deserializeFiles( + classpath: List[Path], + args: Seq[String] + ): Unit = { + + case class VirtDirFile(file: Path, virtDir: VirtualDirectory) + + // Paths obtained from VirtualDirectory.files() are decoded as absolute, + // but actually are relative to the classpath. + // Users are expected to pass relative paths, like in javap and scalajsp. + // This is a workaround to standardize the two to allow comparisons. + // It is worth noting that it's impossible to pass actual absolute directory here + // and lose that data as it should be impossible for a child of a directory to be root. + def normalizedVirtDirPathString(path: Path) = { + val fileString = path.toString() + if (path.isAbsolute()) fileString.substring(1) + else fileString + } + + Scope { implicit scope => + val allVirtDirFiles = + for { + path <- classpath + virtDir = VirtualDirectory.real(path) + file <- virtDir.files + } yield VirtDirFile(file, virtDir) + + for { + fileName <- args + } { + + val foundFileMaybe = allVirtDirFiles.find { + case VirtDirFile(file, virtDir) => + val compString = normalizedVirtDirPathString(file) + !Files.isDirectory(file) && compString == fileName + } + + foundFileMaybe match { + case Some(VirtDirFile(file, virtDir)) => + // Used for an internal NIR compatibility assertion message + val bufferName = file.getFileName().toString() + + val fileByteBuffer = virtDir.read(file) + val defns = deserializeBinary(fileByteBuffer, bufferName) + + defns + .sortBy(_.name.mangle) + .foreach { d => + println(d.show) + println() + } + case None => fail(s"Not found file with name `${fileName}`") } } } diff --git a/cli/src/main/scala/scala/scalanative/cli/options/POptions.scala b/cli/src/main/scala/scala/scalanative/cli/options/POptions.scala index 1036331..0f53f7a 100644 --- a/cli/src/main/scala/scala/scalanative/cli/options/POptions.scala +++ b/cli/src/main/scala/scala/scalanative/cli/options/POptions.scala @@ -9,6 +9,10 @@ case class POptions( @ExtraName("cp") @ValueDescription("") classpath: List[String] = "." :: Nil, + @HelpMessage( + "Instead of deserializing classes/objects, deserialize NIR files. Requires passing NIR files as arguments" + ) + nirFiles: Boolean = false, @Recurse misc: MiscOptions ) From c4be24b482e38878d6ee2451f853d31a6854d359 Mon Sep 17 00:00:00 2001 From: Jan Chyb Date: Tue, 14 Dec 2021 17:15:39 +0100 Subject: [PATCH 2/4] Add simple test for file deserialization --- cli/src/sbt-test/integration/standalone/test | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/cli/src/sbt-test/integration/standalone/test b/cli/src/sbt-test/integration/standalone/test index 71cf49a..f182e12 100755 --- a/cli/src/sbt-test/integration/standalone/test +++ b/cli/src/sbt-test/integration/standalone/test @@ -9,6 +9,13 @@ $ exists Foo$.nir -> runScript scala-native-p not.existing.Class +> runScript scala-native-p --nir-files Foo.nir +-> runScript scala-native-p --nir-files notExisting.nir + +> runExec jar cf inside.jar Foo.class Foo.nir Foo$.class Foo$.nir +> runScript scala-native-p --cp inside.jar --nir-files Foo.nir Foo$.nir +-> runScript scala-native-p --cp inside.jar --nir-files Main$.nir + > runScript scala-native-ld --main Main . -o scala-native-out.exe -v -v $ exists scala-native-out.exe > runExec ./scala-native-out.exe From b72ff734b9135137e9dd65d3e915289d6d8f802c Mon Sep 17 00:00:00 2001 From: Jan Chyb Date: Thu, 16 Dec 2021 10:41:52 +0100 Subject: [PATCH 3/4] Address review comments --- .../scala/scalanative/cli/ScalaNativeP.scala | 99 ++++++++----------- .../scalanative/cli/options/POptions.scala | 4 +- cli/src/sbt-test/integration/standalone/test | 8 +- 3 files changed, 48 insertions(+), 63 deletions(-) diff --git a/cli/src/main/scala/scala/scalanative/cli/ScalaNativeP.scala b/cli/src/main/scala/scala/scalanative/cli/ScalaNativeP.scala index 77b83e5..3279bc4 100644 --- a/cli/src/main/scala/scala/scalanative/cli/ScalaNativeP.scala +++ b/cli/src/main/scala/scala/scalanative/cli/ScalaNativeP.scala @@ -12,7 +12,7 @@ import scala.scalanative.linker.ClassLoader import java.nio.file.Path import scala.scalanative.io.VirtualDirectory import scala.scalanative.nir.serialization.deserializeBinary -import java.nio.file.Files +import scala.scalanative.nir.Defn object ScalaNativeP extends CaseApp[POptions] { @@ -23,7 +23,7 @@ object ScalaNativeP extends CaseApp[POptions] { } if (args.all.isEmpty) { - if (options.nirFiles) + if (options.fromPath) System.err.println("Required NIR file not specified.") else System.err.println("Required class/object not specified.") @@ -39,11 +39,11 @@ object ScalaNativeP extends CaseApp[POptions] { System.err.println(s"Ignoring non existing path: $path") } - if (options.nirFiles) deserializeFiles(classpath, args.all) - else deserializeClassesOrObjects(classpath, args.all) + if (options.fromPath) printFromFiles(classpath, args.all) + else printFromNames(classpath, args.all) } - private def deserializeClassesOrObjects( + private def printFromNames( classpath: List[Path], args: Seq[String] ): Unit = { @@ -57,76 +57,61 @@ object ScalaNativeP extends CaseApp[POptions] { className <- args } { classLoader.load(Global.Top(className)) match { - case Some(defns) => - defns - .sortBy(_.name.mangle) - .foreach { d => - println(d.show) - println() - } + case Some(defns) => printNIR(defns) case None => fail(s"Not found class/object with name `${className}`") } } } } - private def deserializeFiles( + private def printFromFiles( classpath: List[Path], args: Seq[String] ): Unit = { - case class VirtDirFile(file: Path, virtDir: VirtualDirectory) - - // Paths obtained from VirtualDirectory.files() are decoded as absolute, - // but actually are relative to the classpath. - // Users are expected to pass relative paths, like in javap and scalajsp. - // This is a workaround to standardize the two to allow comparisons. - // It is worth noting that it's impossible to pass actual absolute directory here - // and lose that data as it should be impossible for a child of a directory to be root. - def normalizedVirtDirPathString(path: Path) = { - val fileString = path.toString() - if (path.isAbsolute()) fileString.substring(1) - else fileString - } - Scope { implicit scope => - val allVirtDirFiles = - for { - path <- classpath - virtDir = VirtualDirectory.real(path) - file <- virtDir.files - } yield VirtDirFile(file, virtDir) - + val cp = classpath.map(VirtualDirectory.real(_)) + def isVirtualDirPathEqual( + virtualPath: Path, + regularPath: Path + ): Boolean = { + // Paths in jars are always using Unix path denotation + val relativeInJar = virtualPath.toString().stripPrefix("/") + relativeInJar == regularPath.toString() + } + def virtualDirHasPath(dir: VirtualDirectory, path: Path): Boolean = { + dir.files.exists(isVirtualDirPathEqual(_, path)) + } for { fileName <- args - } { - - val foundFileMaybe = allVirtDirFiles.find { - case VirtDirFile(file, virtDir) => - val compString = normalizedVirtDirPathString(file) - !Files.isDirectory(file) && compString == fileName - } - - foundFileMaybe match { - case Some(VirtDirFile(file, virtDir)) => - // Used for an internal NIR compatibility assertion message - val bufferName = file.getFileName().toString() - - val fileByteBuffer = virtDir.read(file) - val defns = deserializeBinary(fileByteBuffer, bufferName) - - defns - .sortBy(_.name.mangle) - .foreach { d => - println(d.show) - println() + path = Paths.get(fileName).normalize() + content <- cp + .collectFirst { + case d if virtualDirHasPath(d, path) => + // Paths in VirtualDirectories have a seperate, incomparable FileSystem + // Correct path has to be chosen from the read ones + val correctedPath = d.files.find { p => + val relativeInJar = p.toString().stripPrefix("/") + relativeInJar == path.toString() } - case None => fail(s"Not found file with name `${fileName}`") - } + d.read(correctedPath.get) + } + .orElse(fail(s"Not found file with name `${fileName}`")) + } { + val defns = deserializeBinary(content, fileName) + printNIR(defns) } } } + private def printNIR(defns: Seq[Defn]) = + defns + .sortBy(_.name.mangle) + .foreach { d => + println(d.show) + println() + } + private def fail(msg: String): Nothing = { Console.err.println(msg) exit(1) diff --git a/cli/src/main/scala/scala/scalanative/cli/options/POptions.scala b/cli/src/main/scala/scala/scalanative/cli/options/POptions.scala index 0f53f7a..079b89b 100644 --- a/cli/src/main/scala/scala/scalanative/cli/options/POptions.scala +++ b/cli/src/main/scala/scala/scalanative/cli/options/POptions.scala @@ -10,9 +10,9 @@ case class POptions( @ValueDescription("") classpath: List[String] = "." :: Nil, @HelpMessage( - "Instead of deserializing classes/objects, deserialize NIR files. Requires passing NIR files as arguments" + "Instead of passing class/object names, pass NIR file paths." ) - nirFiles: Boolean = false, + fromPath: Boolean = false, @Recurse misc: MiscOptions ) diff --git a/cli/src/sbt-test/integration/standalone/test b/cli/src/sbt-test/integration/standalone/test index f182e12..175abc3 100755 --- a/cli/src/sbt-test/integration/standalone/test +++ b/cli/src/sbt-test/integration/standalone/test @@ -9,12 +9,12 @@ $ exists Foo$.nir -> runScript scala-native-p not.existing.Class -> runScript scala-native-p --nir-files Foo.nir --> runScript scala-native-p --nir-files notExisting.nir +> runScript scala-native-p --from-path Foo.nir +-> runScript scala-native-p --from-path notExisting.nir > runExec jar cf inside.jar Foo.class Foo.nir Foo$.class Foo$.nir -> runScript scala-native-p --cp inside.jar --nir-files Foo.nir Foo$.nir --> runScript scala-native-p --cp inside.jar --nir-files Main$.nir +> runScript scala-native-p --cp inside.jar --from-path Foo.nir Foo$.nir +-> runScript scala-native-p --cp inside.jar --from-path Main$.nir > runScript scala-native-ld --main Main . -o scala-native-out.exe -v -v $ exists scala-native-out.exe From d4b96062868976346b4279a985426ee8ce322052 Mon Sep 17 00:00:00 2001 From: Jan Chyb Date: Thu, 16 Dec 2021 12:06:44 +0100 Subject: [PATCH 4/4] Remove repeated iterations --- .../scala/scalanative/cli/ScalaNativeP.scala | 34 +++++++++++-------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/cli/src/main/scala/scala/scalanative/cli/ScalaNativeP.scala b/cli/src/main/scala/scala/scalanative/cli/ScalaNativeP.scala index 3279bc4..b779ac4 100644 --- a/cli/src/main/scala/scala/scalanative/cli/ScalaNativeP.scala +++ b/cli/src/main/scala/scala/scalanative/cli/ScalaNativeP.scala @@ -13,6 +13,8 @@ import java.nio.file.Path import scala.scalanative.io.VirtualDirectory import scala.scalanative.nir.serialization.deserializeBinary import scala.scalanative.nir.Defn +import scala.annotation.tailrec +import java.nio.ByteBuffer object ScalaNativeP extends CaseApp[POptions] { @@ -70,8 +72,8 @@ object ScalaNativeP extends CaseApp[POptions] { ): Unit = { Scope { implicit scope => - val cp = classpath.map(VirtualDirectory.real(_)) - def isVirtualDirPathEqual( + val cp = classpath.toStream.map(VirtualDirectory.real(_)) + def virtualDirPathMatches( virtualPath: Path, regularPath: Path ): Boolean = { @@ -79,23 +81,25 @@ object ScalaNativeP extends CaseApp[POptions] { val relativeInJar = virtualPath.toString().stripPrefix("/") relativeInJar == regularPath.toString() } - def virtualDirHasPath(dir: VirtualDirectory, path: Path): Boolean = { - dir.files.exists(isVirtualDirPathEqual(_, path)) + @tailrec + def findAndRead( + classpath: Stream[VirtualDirectory], + path: Path + ): Option[ByteBuffer] = { + classpath match { + case Stream.Empty => None + case dir #:: tail => + val found = dir.files + .find(virtualDirPathMatches(_, path)) + .map(dir.read(_)) + if (found.isEmpty) findAndRead(tail, path) + else found + } } for { fileName <- args path = Paths.get(fileName).normalize() - content <- cp - .collectFirst { - case d if virtualDirHasPath(d, path) => - // Paths in VirtualDirectories have a seperate, incomparable FileSystem - // Correct path has to be chosen from the read ones - val correctedPath = d.files.find { p => - val relativeInJar = p.toString().stripPrefix("/") - relativeInJar == path.toString() - } - d.read(correctedPath.get) - } + content <- findAndRead(cp, path) .orElse(fail(s"Not found file with name `${fileName}`")) } { val defns = deserializeBinary(content, fileName)