From f77f458df10792a99c3f9fa4b4c56cdaf254e62d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Sun, 3 Dec 2017 16:17:42 +0100 Subject: [PATCH] Initial implementation, ported from the core repository. Compared to the code from the core repository, the sbt build is in charge of more operations than was `assemble-cli.sh`. In particular, it has taken over the tasks of: * finding the jars to include in `lib/` (most of them are now downloaded through ivy resolution, which a .sh can hardly do), * processing the shell scripts to replace the version numbers, and * laying out the contents of the archives. The script `assemble-cli.sh` is only responsible of invoking the `cliPack` task in sbt, and create the zip and tgz archives. --- .gitignore | 1 + .travis.yml | 18 ++ build.sbt | 196 ++++++++++++++++++ project/build.properties | 1 + project/plugins.sbt | 1 + scripts/assemble-cli.sh | 52 +++++ src/main/resources/scalajsc | 16 ++ src/main/resources/scalajsc.bat | 14 ++ src/main/resources/scalajsld | 10 + src/main/resources/scalajsld.bat | 8 + src/main/resources/scalajsp | 9 + src/main/resources/scalajsp.bat | 7 + .../scala/org/scalajs/cli/Scalajsld.scala | 185 +++++++++++++++++ src/main/scala/org/scalajs/cli/Scalajsp.scala | 125 +++++++++++ 14 files changed, 643 insertions(+) create mode 100644 .travis.yml create mode 100644 build.sbt create mode 100644 project/build.properties create mode 100644 project/plugins.sbt create mode 100755 scripts/assemble-cli.sh create mode 100755 src/main/resources/scalajsc create mode 100644 src/main/resources/scalajsc.bat create mode 100755 src/main/resources/scalajsld create mode 100644 src/main/resources/scalajsld.bat create mode 100755 src/main/resources/scalajsp create mode 100644 src/main/resources/scalajsp.bat create mode 100644 src/main/scala/org/scalajs/cli/Scalajsld.scala create mode 100644 src/main/scala/org/scalajs/cli/Scalajsp.scala diff --git a/.gitignore b/.gitignore index 2f7896d..402069d 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ target/ +/pack/ diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..929c43e --- /dev/null +++ b/.travis.yml @@ -0,0 +1,18 @@ +sudo: false +language: scala +scala: + - 2.11.12 + - 2.12.4 +jdk: + - oraclejdk8 +env: + - SCALAJS_VERSION=1.0.0-M2 +script: + - ./scripts/assemble-cli.sh $SCALAJS_VERSION $TRAVIS_SCALA_VERSION +cache: + directories: + - $HOME/.ivy2/cache + - $HOME/.sbt +before_cache: + - find $HOME/.ivy2/cache -name "ivydata-*.properties" -print -delete + - find $HOME/.sbt -name "*.lock" -print -delete diff --git a/build.sbt b/build.sbt new file mode 100644 index 0000000..08fd12e --- /dev/null +++ b/build.sbt @@ -0,0 +1,196 @@ +// Configurable settings +val scalaJSVersion = + settingKey[String]("Version of Scala.js for which to build to CLI") +val scalaJSScalaVersions = + settingKey[Seq[String]]("All the minor versions of Scala for which to build the CLI") + +// Computed settings +val scalaJSBinaryVersion = + settingKey[String]("Binary version of Scala.js") + +// Custom tasks +val cliLibJars = + taskKey[Seq[File]]("All the .jars that must go to the lib/ directory of the CLI") +val cliPack = + taskKey[File]("Pack the CLI for the current configuration") + +// Duplicated from the Scala.js sbt plugin +def binaryScalaJSVersion(full: String): String = { + val ReleaseVersion = raw"""(\d+)\.(\d+)\.(\d+)""".r + val MinorSnapshotVersion = raw"""(\d+)\.(\d+)\.([1-9]\d*)-SNAPSHOT""".r + full match { + case ReleaseVersion(major, _, _) => major + case MinorSnapshotVersion(major, _, _) => major + case _ => full + } +} + +inThisBuild(Def.settings( + version := "1.0.0-SNAPSHOT", + organization := "org.scala-js", + + crossScalaVersions := Seq("2.12.4", "2.11.12"), + scalaVersion := crossScalaVersions.value.head, + scalacOptions ++= Seq("-deprecation", "-feature", "-Xfatal-warnings"), + + scalaJSVersion := "1.0.0-M2", + scalaJSBinaryVersion := binaryScalaJSVersion(scalaJSVersion.value), + + scalaJSScalaVersions := Seq( + "2.11.0", + "2.11.1", + "2.11.2", + "2.11.4", + "2.11.5", + "2.11.6", + "2.11.7", + "2.11.8", + "2.11.11", + "2.11.12", + "2.12.1", + "2.12.2", + "2.12.3", + "2.12.4", + ), + + homepage := Some(url("https://www.scala-js.org/")), + licenses += ("BSD New", + url("https://github.com/scala-js/scala-js-env-cli/blob/master/LICENSE")), + scmInfo := Some(ScmInfo( + url("https://github.com/scala-js/scala-js-cli"), + "scm:git:git@github.com:scala-js/scala-js-cli.git", + Some("scm:git:git@github.com:scala-js/scala-js-cli.git"))), +)) + +val commonSettings = Def.settings( + publishMavenStyle := true, + publishTo := { + val nexus = "https://oss.sonatype.org/" + if (isSnapshot.value) + Some("snapshots" at nexus + "content/repositories/snapshots") + else + Some("releases" at nexus + "service/local/staging/deploy/maven2") + }, + pomExtra := ( + + + sjrd + Sébastien Doeraene + https://github.com/sjrd/ + + + gzm0 + Tobias Schlatter + https://github.com/gzm0/ + + + nicolasstucki + Nicolas Stucki + https://github.com/nicolasstucki/ + + + ), + pomIncludeRepository := { _ => false }, +) + +lazy val `scalajs-cli`: Project = project.in(file(".")). + settings( + commonSettings, + + libraryDependencies ++= Seq( + "org.scala-js" %% "scalajs-tools" % scalaJSVersion.value, + "com.github.scopt" %% "scopt" % "3.5.0", + ), + + // assembly options + mainClass in assembly := None, // don't want an executable JAR + assemblyOption in assembly ~= { _.copy(includeScala = false) }, + assemblyJarName in assembly := + s"${normalizedName.value}-assembly_${scalaBinaryVersion.value}-${scalaJSVersion.value}.jar", + + cliLibJars := { + val s = streams.value + val log = s.log + + val sjsOrg = organization.value + val scalaBinVer = scalaBinaryVersion.value + val sjsVer = scalaJSVersion.value + + val scalaFullVers = scalaJSScalaVersions.value.filter { full => + CrossVersion.binaryScalaVersion(full) == scalaBinVer + } + + val cliAssemblyJar = assembly.value + + val stdLibModuleID = + sjsOrg % s"scalajs-library_$scalaBinVer" % sjsVer + val compilerPluginModuleIDs = + scalaFullVers.map(v => sjsOrg % s"scalajs-compiler_$v" % sjsVer) + val allModuleIDs = (stdLibModuleID +: compilerPluginModuleIDs).toVector + val allModuleIDsIntransitive = allModuleIDs.map(_.intransitive()) + + val resolvedLibJars = { + val retrieveDir = s.cacheDirectory / "cli-lib-jars" + val lm = { + import sbt.librarymanagement.ivy._ + val ivyConfig = InlineIvyConfiguration().withLog(log) + IvyDependencyResolution(ivyConfig) + } + val dummyModuleName = + s"clilibjars-$sjsVer-$scalaBinVer-" + scalaFullVers.mkString("-") + val dummyModuleID = sjsOrg % dummyModuleName % version.value + val descriptor = + lm.moduleDescriptor(dummyModuleID, allModuleIDsIntransitive, scalaModuleInfo = None) + val maybeFiles = lm.retrieve(descriptor, retrieveDir, log) + maybeFiles.fold({ unresolvedWarn => + throw unresolvedWarn.resolveException + }, { files => + files + }).distinct + } + + cliAssemblyJar +: resolvedLibJars + }, + + target in cliPack := baseDirectory.value / "pack", + moduleName in cliPack := + s"scalajs_${scalaBinaryVersion.value}-${scalaJSVersion.value}", + crossTarget in cliPack := + (target in cliPack).value / (moduleName in cliPack).value, + + cliPack := { + val scalaBinVer = scalaBinaryVersion.value + val sjsVer = scalaJSVersion.value + + val trg = (crossTarget in cliPack).value + val trgLib = trg / "lib" + val trgBin = trg / "bin" + + if (trg.exists) + IO.delete(trg) + + IO.createDirectory(trgLib) + val libJars = cliLibJars.value + for (libJar <- libJars) { + IO.copyFile(libJar, trgLib / libJar.getName) + } + + IO.createDirectory(trgBin) + val scriptDir = (resourceDirectory in Compile).value + for { + scriptFile <- IO.listFiles(scriptDir) + if !scriptFile.getPath.endsWith("~") + } { + val content = IO.read(scriptFile) + val processedContent = content + .replaceAllLiterally("@SCALA_BIN_VER@", scalaBinVer) + .replaceAllLiterally("@SCALAJS_VER@", sjsVer) + val dest = trgBin / scriptFile.getName + IO.write(dest, processedContent) + if (scriptFile.canExecute) + dest.setExecutable(/* executable = */ true, /* ownerOnly = */ false) + } + + trg + }, + ) diff --git a/project/build.properties b/project/build.properties new file mode 100644 index 0000000..394cb75 --- /dev/null +++ b/project/build.properties @@ -0,0 +1 @@ +sbt.version=1.0.4 diff --git a/project/plugins.sbt b/project/plugins.sbt new file mode 100644 index 0000000..15a88b0 --- /dev/null +++ b/project/plugins.sbt @@ -0,0 +1 @@ +addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.5") diff --git a/scripts/assemble-cli.sh b/scripts/assemble-cli.sh new file mode 100755 index 0000000..2a5a4d1 --- /dev/null +++ b/scripts/assemble-cli.sh @@ -0,0 +1,52 @@ +#! /bin/sh + +set -e + +# Assembles the CLI tools for a given Scala binary version. + +if [ $# -lt 2 ]; then + echo "Usage: $(basename $0) " >&2 + exit 1 +fi + +SCALAJS_VER=$1 + +BASEVER=$2 +case $BASEVER in + 2.11.*) + BINVER="2.11" + ;; + 2.12.*) + BINVER="2.12" + ;; + *) + echo "Invalid Scala version $BINVER" >&2 + exit 2 +esac + +# Build and lay out the contents of the archives +sbt \ + "clean" \ + "++$BASEVER!" \ + "set scalaJSVersion in ThisBuild := \"$SCALAJS_VER\"" \ + "cliPack" \ + || exit $? + +# Base Scala.js project directory. +BASE="$(dirname $0)/.." + +# Aritfact name (no extension). +NAME=scalajs_$BINVER-$SCALAJS_VER + +# Target directories +TRG_BASE="$BASE/pack" +TRG_VER="$TRG_BASE/$NAME" + +# Tar and zip the whole thing up +( + cd $TRG_BASE + tar cfz $NAME.tgz $NAME + + if [ -f $NAME.zip ]; then rm $NAME.zip; fi + zip -r $NAME.zip -r $NAME +) diff --git a/src/main/resources/scalajsc b/src/main/resources/scalajsc new file mode 100755 index 0000000..7fd1100 --- /dev/null +++ b/src/main/resources/scalajsc @@ -0,0 +1,16 @@ +#! /bin/sh + +SCALA_BIN_VER="@SCALA_BIN_VER@" +SCALAJS_VER="@SCALAJS_VER@" +SCALA_VER=$(scalac -version 2>&1 | grep -o '[0-9]\.[0-9][0-9]\.[0-9]') + +if [ "$(echo $SCALA_VER | cut -b 1-4)" != "$SCALA_BIN_VER" ]; then + echo "This bundle of Scala.js CLI is for $SCALA_BIN_VER. Your scala version is $SCALA_VER!" >&2 + exit 1 +fi + +BASE="$(dirname $0)/.." +PLUGIN="$BASE/lib/scalajs-compiler_$SCALA_VER-$SCALAJS_VER.jar" +JSLIB="$BASE/lib/scalajs-library_$SCALA_BIN_VER-$SCALAJS_VER.jar" + +scalac -classpath "$JSLIB" "-Xplugin:$PLUGIN" "$@" diff --git a/src/main/resources/scalajsc.bat b/src/main/resources/scalajsc.bat new file mode 100644 index 0000000..767c5df --- /dev/null +++ b/src/main/resources/scalajsc.bat @@ -0,0 +1,14 @@ +@ECHO OFF +set SCALA_BIN_VER=@SCALA_BIN_VER@ +set SCALAJS_VER=@SCALAJS_VER@ + +for /F "tokens=5" %%i in (' scala -version 2^>^&1 1^>nul ') do set SCALA_VER=%%i + +if NOT "%SCALA_VER:~0,4%" == "%SCALA_BIN_VER%" ( + echo "This bundle of Scala.js CLI is for %SCALA_BIN_VER%. Your scala version is %SCALA_VER%!" 1>&2 +) else ( + set PLUGIN=%~dp0\..\lib\scalajs-compiler_%SCALA_VER%-%SCALAJS_VER%.jar + set JSLIB=%~dp0\..\lib\scalajs-library_%SCALA_BIN_VER%-%SCALAJS_VER%.jar + + scalac -classpath "%JSLIB%" "-Xplugin:%PLUGIN%" %* +) diff --git a/src/main/resources/scalajsld b/src/main/resources/scalajsld new file mode 100755 index 0000000..b194859 --- /dev/null +++ b/src/main/resources/scalajsld @@ -0,0 +1,10 @@ +#! /bin/sh + +SCALA_BIN_VER="@SCALA_BIN_VER@" +SCALAJS_VER="@SCALAJS_VER@" + +BASE="$(dirname $0)/.." +CLILIB="$BASE/lib/scalajs-cli-assembly_$SCALA_BIN_VER-$SCALAJS_VER.jar" +JSLIB="$BASE/lib/scalajs-library_$SCALA_BIN_VER-$SCALAJS_VER.jar" + +scala -classpath "$CLILIB" org.scalajs.cli.Scalajsld --stdlib "$JSLIB" "$@" diff --git a/src/main/resources/scalajsld.bat b/src/main/resources/scalajsld.bat new file mode 100644 index 0000000..0308d55 --- /dev/null +++ b/src/main/resources/scalajsld.bat @@ -0,0 +1,8 @@ +@ECHO OFF +set SCALA_BIN_VER=@SCALA_BIN_VER@ +set SCALAJS_VER=@SCALAJS_VER@ + +set CLILIB="%~dp0\..\lib\scalajs-cli-assembly_%SCALA_BIN_VER%-%SCALAJS_VER%.jar" +set JSLIB="%~dp0\..\lib\scalajs-library_%SCALA_BIN_VER%-%SCALAJS_VER%.jar" + +scala -classpath %CLILIB% org.scalajs.cli.Scalajsld --stdlib %JSLIB% %* diff --git a/src/main/resources/scalajsp b/src/main/resources/scalajsp new file mode 100755 index 0000000..f98f259 --- /dev/null +++ b/src/main/resources/scalajsp @@ -0,0 +1,9 @@ +#! /bin/sh + +SCALA_BIN_VER="@SCALA_BIN_VER@" +SCALAJS_VER="@SCALAJS_VER@" + +BASE="$(dirname $0)/.." +CLILIB="$BASE/lib/scalajs-cli-assembly_$SCALA_BIN_VER-$SCALAJS_VER.jar" + +scala -classpath "$CLILIB" org.scalajs.cli.Scalajsp "$@" diff --git a/src/main/resources/scalajsp.bat b/src/main/resources/scalajsp.bat new file mode 100644 index 0000000..1fc4ad6 --- /dev/null +++ b/src/main/resources/scalajsp.bat @@ -0,0 +1,7 @@ +@ECHO OFF +set SCALA_BIN_VER=@SCALA_BIN_VER@ +set SCALAJS_VER=@SCALAJS_VER@ + +set CLILIB="%~dp0\..\lib\scalajs-cli-assembly_%SCALA_BIN_VER%-%SCALAJS_VER%.jar" + +scala -classpath %CLILIB% org.scalajs.cli.Scalajsp %* diff --git a/src/main/scala/org/scalajs/cli/Scalajsld.scala b/src/main/scala/org/scalajs/cli/Scalajsld.scala new file mode 100644 index 0000000..ef53b80 --- /dev/null +++ b/src/main/scala/org/scalajs/cli/Scalajsld.scala @@ -0,0 +1,185 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js CLI ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013-2014, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package org.scalajs.cli + +import org.scalajs.core.ir.ScalaJSVersions + +import org.scalajs.core.tools.io._ +import org.scalajs.core.tools.logging._ + +import org.scalajs.core.tools.linker._ +import org.scalajs.core.tools.linker.standard._ + +import CheckedBehavior.Compliant + +import scala.collection.immutable.Seq + +import java.io.File +import java.net.URI + +object Scalajsld { + + private case class Options( + cp: Seq[File] = Seq.empty, + moduleInitializers: Seq[ModuleInitializer] = Seq.empty, + output: File = null, + semantics: Semantics = Semantics.Defaults, + outputMode: OutputMode = OutputMode.ECMAScript51Isolated, + moduleKind: ModuleKind = ModuleKind.NoModule, + noOpt: Boolean = false, + fullOpt: Boolean = false, + prettyPrint: Boolean = false, + sourceMap: Boolean = false, + relativizeSourceMap: Option[URI] = None, + checkIR: Boolean = false, + stdLib: Option[File] = None, + logLevel: Level = Level.Info + ) + + private implicit object MainMethodRead extends scopt.Read[ModuleInitializer] { + val arity = 1 + val reads = { (s: String) => + val lastDot = s.lastIndexOf('.') + if (lastDot < 0) + throw new IllegalArgumentException(s"$s is not a valid main method") + ModuleInitializer.mainMethodWithArgs(s.substring(0, lastDot), + s.substring(lastDot + 1)) + } + } + + private implicit object OutputModeRead extends scopt.Read[OutputMode] { + val arity = 1 + val reads = { (s: String) => + OutputMode.All.find(_.toString() == s).getOrElse( + throw new IllegalArgumentException(s"$s is not a valid output mode")) + } + } + + private implicit object ModuleKindRead extends scopt.Read[ModuleKind] { + val arity = 1 + val reads = { (s: String) => + ModuleKind.All.find(_.toString() == s).getOrElse( + throw new IllegalArgumentException(s"$s is not a valid module kind")) + } + } + + def main(args: Array[String]): Unit = { + val parser = new scopt.OptionParser[Options]("scalajsld") { + head("scalajsld", ScalaJSVersions.current) + arg[File](" ...") + .unbounded() + .action { (x, c) => c.copy(cp = c.cp :+ x) } + .text("Entries of Scala.js classpath to link") + opt[ModuleInitializer]("mainMethod") + .valueName("") + .abbr("mm") + .unbounded() + .action { (x, c) => c.copy(moduleInitializers = c.moduleInitializers :+ x) } + .text("Execute the specified main(Array[String]) method on startup") + opt[File]('o', "output") + .valueName("") + .required() + .action { (x, c) => c.copy(output = x) } + .text("Output file of linker (required)") + opt[Unit]('f', "fastOpt") + .action { (_, c) => c.copy(noOpt = false, fullOpt = false) } + .text("Optimize code (this is the default)") + opt[Unit]('n', "noOpt") + .action { (_, c) => c.copy(noOpt = true, fullOpt = false) } + .text("Don't optimize code") + opt[Unit]('u', "fullOpt") + .action { (_, c) => c.copy(noOpt = false, fullOpt = true) } + .text("Fully optimize code (uses Google Closure Compiler)") + opt[Unit]('p', "prettyPrint") + .action { (_, c) => c.copy(prettyPrint = true) } + .text("Pretty print full opted code (meaningful with -u)") + opt[Unit]('s', "sourceMap") + .action { (_, c) => c.copy(sourceMap = true) } + .text("Produce a source map for the produced code") + opt[Unit]("compliantAsInstanceOfs") + .action { (_, c) => c.copy(semantics = + c.semantics.withAsInstanceOfs(Compliant)) + } + .text("Use compliant asInstanceOfs") + opt[OutputMode]('m', "outputMode") + .action { (mode, c) => c.copy(outputMode = mode) } + .text("Output mode " + OutputMode.All.mkString("(", ", ", ")")) + opt[ModuleKind]('k', "moduleKind") + .action { (kind, c) => c.copy(moduleKind = kind) } + .text("Module kind " + ModuleKind.All.mkString("(", ", ", ")")) + opt[Unit]('c', "checkIR") + .action { (_, c) => c.copy(checkIR = true) } + .text("Check IR before optimizing") + opt[File]('r', "relativizeSourceMap") + .valueName("") + .action { (x, c) => c.copy(relativizeSourceMap = Some(x.toURI)) } + .text("Relativize source map with respect to given path (meaningful with -s)") + opt[Unit]("noStdlib") + .action { (_, c) => c.copy(stdLib = None) } + .text("Don't automatically include Scala.js standard library") + opt[File]("stdlib") + .valueName("") + .hidden() + .action { (x, c) => c.copy(stdLib = Some(x)) } + .text("Location of Scala.js standard libarary. This is set by the " + + "runner script and automatically prepended to the classpath. " + + "Use -n to not include it.") + opt[Unit]('d', "debug") + .action { (_, c) => c.copy(logLevel = Level.Debug) } + .text("Debug mode: Show full log") + opt[Unit]('q', "quiet") + .action { (_, c) => c.copy(logLevel = Level.Warn) } + .text("Only show warnings & errors") + opt[Unit]("really-quiet") + .abbr("qq") + .action { (_, c) => c.copy(logLevel = Level.Error) } + .text("Only show errors") + version("version") + .abbr("v") + .text("Show scalajsld version") + help("help") + .abbr("h") + .text("prints this usage text") + + override def showUsageOnError = true + } + + for (options <- parser.parse(args, Options())) { + val classpath = options.stdLib.toList ++ options.cp + val irContainers = FileScalaJSIRContainer.fromClasspath(classpath) + val moduleInitializers = options.moduleInitializers + + val semantics = + if (options.fullOpt) options.semantics.optimized + else options.semantics + + val config = StandardLinker.Config() + .withSemantics(semantics) + .withModuleKind(options.moduleKind) + .withOutputMode(options.outputMode) + .withCheckIR(options.checkIR) + .withOptimizer(!options.noOpt) + .withParallel(true) + .withSourceMap(options.sourceMap) + .withRelativizeSourceMapBase(options.relativizeSourceMap) + .withClosureCompiler(options.fullOpt) + .withPrettyPrint(options.prettyPrint) + .withBatchMode(true) + + val linker = StandardLinker(config) + val logger = new ScalaConsoleLogger(options.logLevel) + val outFile = WritableFileVirtualJSFile(options.output) + val cache = (new IRFileCache).newCache + + linker.link(cache.cached(irContainers), moduleInitializers, outFile, + logger) + } + } +} diff --git a/src/main/scala/org/scalajs/cli/Scalajsp.scala b/src/main/scala/org/scalajs/cli/Scalajsp.scala new file mode 100644 index 0000000..e63b4a8 --- /dev/null +++ b/src/main/scala/org/scalajs/cli/Scalajsp.scala @@ -0,0 +1,125 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js CLI ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013-2014, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package org.scalajs.cli + +import org.scalajs.core.ir +import ir.ScalaJSVersions +import ir.Trees.{Tree, ClassDef} +import ir.Printers.IRTreePrinter + +import org.scalajs.core.tools.io._ +import scala.collection.immutable.Seq + +import java.io.{Console => _, _} +import java.util.zip.{ZipFile, ZipEntry} + +object Scalajsp { + + private case class Options( + jar: Option[File] = None, + fileNames: Seq[String] = Seq.empty + ) + + def main(args: Array[String]): Unit = { + val parser = new scopt.OptionParser[Options]("scalajsp") { + head("scalajsp", ScalaJSVersions.current) + arg[String](" ...") + .unbounded() + .action { (x, c) => c.copy(fileNames = c.fileNames :+ x) } + .text("*.sjsir file to display content of") + opt[File]('j', "jar") + .valueName("") + .action { (x, c) => c.copy(jar = Some(x)) } + .text("Read *.sjsir file(s) from the given JAR.") + opt[Unit]('s', "supported") + .action { (_,_) => printSupported(); exit(0) } + .text("Show supported Scala.js IR versions") + version("version") + .abbr("v") + .text("Show scalajsp version") + help("help") + .abbr("h") + .text("prints this usage text") + + override def showUsageOnError = true + } + + for { + options <- parser.parse(args, Options()) + fileName <- options.fileNames + } { + val vfile = options.jar.map { jar => + readFromJar(jar, fileName) + }.getOrElse { + readFromFile(fileName) + } + + displayFileContent(vfile, options) + } + } + + private def printSupported(): Unit = { + import ScalaJSVersions._ + println(s"Emitted Scala.js IR version is: $binaryEmitted") + println("Supported Scala.js IR versions are") + binarySupported.foreach(v => println(s"* $v")) + } + + private def displayFileContent(vfile: VirtualScalaJSIRFile, + opts: Options): Unit = { + new IRTreePrinter(stdout).print(vfile.tree) + stdout.write('\n') + stdout.flush() + } + + private def fail(msg: String): Nothing = { + Console.err.println(msg) + exit(1) + } + + private def exit(code: Int): Nothing = { + System.exit(code) + throw new AssertionError("unreachable") + } + + private def readFromFile(fileName: String) = { + val file = new File(fileName) + + if (!file.exists) + fail(s"No such file: $fileName") + else if (!file.canRead) + fail(s"Unable to read file: $fileName") + else + FileVirtualScalaJSIRFile(file) + } + + private def readFromJar(jar: File, name: String) = { + val jarFile = + try { new ZipFile(jar) } + catch { case _: FileNotFoundException => fail(s"No such JAR: $jar") } + try { + val entry = jarFile.getEntry(name) + if (entry == null) { + fail(s"No such file in jar: $name") + } else { + val name = jarFile.getName + "#" + entry.getName + val content = + IO.readInputStreamToByteArray(jarFile.getInputStream(entry)) + new MemVirtualSerializedScalaJSIRFile(name).withContent(content) + } + } finally { + jarFile.close() + } + } + + private val stdout = + new BufferedWriter(new OutputStreamWriter(Console.out, "UTF-8")) + +}