From 742beb6c7e2cb9463d677a899c7baf04d30f77d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Wro=C5=84ski?= Date: Tue, 30 Nov 2021 15:16:23 +0100 Subject: [PATCH] Allow shebangs and using directives in scala files --- .../preprocessing/ScalaPreprocessor.scala | 9 +++++--- .../preprocessing/ScriptPreprocessor.scala | 20 +--------------- .../scala/build/preprocessing/SheBang.scala | 23 +++++++++++++++++++ .../scala/build/tests/SourcesTests.scala | 6 ++--- .../cli/integration/RunTestDefinitions.scala | 21 ++++++++++++++++- 5 files changed, 53 insertions(+), 26 deletions(-) create mode 100644 modules/build/src/main/scala/scala/build/preprocessing/SheBang.scala diff --git a/modules/build/src/main/scala/scala/build/preprocessing/ScalaPreprocessor.scala b/modules/build/src/main/scala/scala/build/preprocessing/ScalaPreprocessor.scala index 1e482cf3a3..8a1d74c9f2 100644 --- a/modules/build/src/main/scala/scala/build/preprocessing/ScalaPreprocessor.scala +++ b/modules/build/src/main/scala/scala/build/preprocessing/ScalaPreprocessor.scala @@ -127,14 +127,16 @@ case object ScalaPreprocessor extends Preprocessor { Option[String] )]] = either { - val afterStrictUsing = value(processStrictUsing(content, scopeRoot)) + val (content0, isSheBang) = SheBang.ignoreSheBangLines(content) + + val afterStrictUsing = value(processStrictUsing(content0, scopeRoot)) val afterUsing = value { - processUsing(path, afterStrictUsing.map(_._2).getOrElse(content), scopeRoot) + processUsing(path, afterStrictUsing.map(_._2).getOrElse(content0), scopeRoot) .sequence } val afterProcessImports = value { processSpecialImports( - afterUsing.flatMap(_._4).orElse(afterStrictUsing.map(_._2)).getOrElse(content), + afterUsing.flatMap(_._4).orElse(afterStrictUsing.map(_._2)).getOrElse(content0), path ) } @@ -151,6 +153,7 @@ case object ScalaPreprocessor extends Preprocessor { .map(_._3) .orElse(afterUsing.flatMap(_._4)) .orElse(afterStrictUsing.map(_._2)) + .orElse(if (isSheBang) Some(content0) else None) val scopedRequirements = afterUsing.map(_._2).getOrElse(Nil) Some((summedRequirements, scopedRequirements, summedOptions, lastContentOpt)) } diff --git a/modules/build/src/main/scala/scala/build/preprocessing/ScriptPreprocessor.scala b/modules/build/src/main/scala/scala/build/preprocessing/ScriptPreprocessor.scala index 10cf6b7e10..5ef1ad7e23 100644 --- a/modules/build/src/main/scala/scala/build/preprocessing/ScriptPreprocessor.scala +++ b/modules/build/src/main/scala/scala/build/preprocessing/ScriptPreprocessor.scala @@ -7,7 +7,6 @@ import scala.build.Inputs import scala.build.errors.BuildException import scala.build.internal.{AmmUtil, CodeWrapper, CustomCodeWrapper, Name} import scala.build.options.{BuildOptions, BuildRequirements} -import scala.util.matching.Regex final case class ScriptPreprocessor(codeWrapper: CodeWrapper) extends Preprocessor { def preprocess(input: Inputs.SingleElement) @@ -54,23 +53,6 @@ final case class ScriptPreprocessor(codeWrapper: CodeWrapper) extends Preprocess object ScriptPreprocessor { - private val sheBangRegex: Regex = s"""(^(#!.*(\\r\\n?|\\n)?)+(\\s*!#.*)?)""".r - - private def ignoreSheBangLines(content: String): String = - if (content.startsWith("#!")) { - val regexMatch = sheBangRegex.findFirstMatchIn(content) - regexMatch match { - case Some(firstMatch) => - content.replace( - firstMatch.toString(), - System.lineSeparator() * firstMatch.toString().split(System.lineSeparator()).length - ) - case None => content - } - } - else - content - private def preprocess( reportingPath: Either[String, os.Path], content: String, @@ -79,7 +61,7 @@ object ScriptPreprocessor { scopePath: ScopePath ): Either[BuildException, List[PreprocessedSource.InMemory]] = either { - val contentIgnoredSheBangLines = ignoreSheBangLines(content) + val (contentIgnoredSheBangLines, _) = SheBang.ignoreSheBangLines(content) val (pkg, wrapper) = AmmUtil.pathToPackageWrapper(subPath) diff --git a/modules/build/src/main/scala/scala/build/preprocessing/SheBang.scala b/modules/build/src/main/scala/scala/build/preprocessing/SheBang.scala new file mode 100644 index 0000000000..5d47cf783d --- /dev/null +++ b/modules/build/src/main/scala/scala/build/preprocessing/SheBang.scala @@ -0,0 +1,23 @@ +package scala.build.preprocessing + +import scala.util.matching.Regex + +object SheBang { + private val sheBangRegex: Regex = s"""(^(#!.*(\\r\\n?|\\n)?)+(\\s*!#.*)?)""".r + + def ignoreSheBangLines(content: String): (String, Boolean) = + if (content.startsWith("#!")) { + val regexMatch = sheBangRegex.findFirstMatchIn(content) + regexMatch match { + case Some(firstMatch) => + content.replace( + firstMatch.toString(), + System.lineSeparator() * firstMatch.toString().split(System.lineSeparator()).length + ) -> true + case None => (content, false) + } + } + else + (content, false) + +} diff --git a/modules/build/src/test/scala/scala/build/tests/SourcesTests.scala b/modules/build/src/test/scala/scala/build/tests/SourcesTests.scala index 82818c3bee..e315ca8441 100644 --- a/modules/build/src/test/scala/scala/build/tests/SourcesTests.scala +++ b/modules/build/src/test/scala/scala/build/tests/SourcesTests.scala @@ -212,7 +212,7 @@ class SourcesTests extends munit.FunSuite { } } - test("should skip SheBang in .sc") { + test("should skip SheBang in .sc and .scala") { val testInputs = TestInputs( os.rel / "something1.sc" -> """#!/usr/bin/env scala-cli @@ -227,14 +227,14 @@ class SourcesTests extends munit.FunSuite { |#! nix-shell -i scala-cli | |println("Hello World")""".stripMargin, - os.rel / "something4.sc" -> + os.rel / "something4.scala" -> """#!/usr/bin/scala-cli |#! nix-shell -i scala-cli | |!# | |println("Hello World")""".stripMargin, - os.rel / "something5.sc" -> + os.rel / "something5.scala" -> """#!/usr/bin/scala-cli | |println("Hello World #!")""".stripMargin diff --git a/modules/integration/src/test/scala/scala/cli/integration/RunTestDefinitions.scala b/modules/integration/src/test/scala/scala/cli/integration/RunTestDefinitions.scala index 70d7154d6b..adde7ce0e8 100644 --- a/modules/integration/src/test/scala/scala/cli/integration/RunTestDefinitions.scala +++ b/modules/integration/src/test/scala/scala/cli/integration/RunTestDefinitions.scala @@ -1207,7 +1207,7 @@ abstract class RunTestDefinitions(val scalaVersionOpt: Option[String]) } } - if (!Properties.isWin) + if (!Properties.isWin) { test("CLI args passed to shebang script") { val inputs = TestInputs( Seq( @@ -1222,6 +1222,25 @@ abstract class RunTestDefinitions(val scalaVersionOpt: Option[String]) expect(p.out.text().trim == "List(1, 2, 3, -v)") } } + test("CLI args passed to shebang in Scala file") { + val inputs = TestInputs( + Seq( + os.rel / "f.scala" -> s"""|#!/usr/bin/env -S ${TestUtil.cli.mkString(" ")} shebang + |object Hello { + | def main(args: Array[String]) = { + | println(args.toList) + | } + |} + |""".stripMargin + ) + ) + inputs.fromRoot { root => + os.perms.set(root / "f.scala", os.PermSet.fromString("rwx------")) + val p = os.proc("./f.scala", "1", "2", "3", "-v").call(cwd = root) + expect(p.out.text().trim == "List(1, 2, 3, -v)") + } + } + } test("Runs with JVM 8") { val inputs = TestInputs(