Skip to content

Misc scalac options fixes #1413

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Oct 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package scala.cli.commands

import caseapp.*
import com.github.plokhotnyuk.jsoniter_scala.core.*
import com.github.plokhotnyuk.jsoniter_scala.macros.*

/** Scala CLI options which aren't strictly scalac options, but directly involve the Scala compiler
* in some way.
*/
// format: off
final case class ScalacExtraOptions(
@Group("Scala")
@HelpMessage("Show help for scalac. This is an alias for --scalac-option -help")
@Name("helpScalac")
scalacHelp: Boolean = false,

@Group("Scala")
@HelpMessage("Turn verbosity on for scalac. This is an alias for --scalac-option -verbose")
@Name("verboseScalac")
scalacVerbose: Boolean = false,
)
// format: on

object ScalacExtraOptions {
lazy val parser: Parser[ScalacExtraOptions] = Parser.derive
implicit lazy val parserAux: Parser.Aux[ScalacExtraOptions, parser.D] = parser
implicit lazy val help: Help[ScalacExtraOptions] = Help.derive
implicit lazy val jsonCodec: JsonValueCodec[ScalacExtraOptions] = JsonCodecMaker.make
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ object ScalacOptions {
Set("-V", "-W", "-X", "-Y")
private val scalacOptionsPrefixes =
Set("-g", "-language", "-opt", "-P", "-target") ++ scalacOptionsPurePrefixes
private val scalacAliasedOptions = // these options don't require being passed after -O and accept an arg
Set("-encoding", "-release", "-color")
private val scalacNoArgAliasedOptions = // these options don't require being passed after -O and don't accept an arg
Set("-nowarn", "-feature", "-deprecation")

/** This includes all the scalac options which disregard inputs and print a help and/or context
* message instead.
Expand Down Expand Up @@ -65,6 +69,15 @@ object ScalacOptions {
args match {
case h :: t if scalacOptionsPrefixes.exists(h.startsWith) =>
Right(Some((Some(h :: acc.getOrElse(Nil)), t)))
case h :: t if scalacNoArgAliasedOptions.contains(h) =>
Right(Some((Some(h :: acc.getOrElse(Nil)), t)))
case h :: t if scalacAliasedOptions.contains(h) =>
// check if the next scalac arg is a different option or a param to the current option
val maybeOptionArg = t.headOption.filter(!_.startsWith("-"))
// if it's a param, it'll be treated as such and considered already parsed
val newTail = maybeOptionArg.map(_ => t.drop(1)).getOrElse(t)
val newHead = List(h) ++ maybeOptionArg
Right(Some((Some(newHead ++ acc.getOrElse(Nil)), newTail)))
case _ => underlying.step(args, index, acc, formatter)
}
def get(acc: Option[List[String]], formatter: Formatter[Name]): Either[Error, List[String]] =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,8 @@ final case class SharedOptions(
@Name("B")
scalaBinaryVersion: Option[String] = None,

@Group("Scala")
@HelpMessage("Show help for scalac. This is an alias for --scalac-option -help")
@Name("helpScalac")
scalacHelp: Boolean = false,
@Recurse
scalacExtra: ScalacExtraOptions = ScalacExtraOptions(),

@Recurse
snippet: SnippetOptions = SnippetOptions(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import scala.build.internal.Constants
import scala.build.options.{BuildOptions, Scope}
import scala.cli.ScalaCli
import scala.cli.commands.util.CommandHelpers
import scala.cli.commands.util.ScalacOptionsUtil.*
import scala.cli.commands.util.SharedOptionsUtil.*
import scala.util.{Properties, Try}

Expand Down Expand Up @@ -133,11 +134,8 @@ abstract class ScalaCommand[T](implicit myParser: Parser[T], help: Help[T])
def maybePrintSimpleScalacOutput(options: T, buildOptions: BuildOptions): Unit =
for {
shared <- sharedOptions(options)
scalacOptions = shared.scalac.scalacOption.toSeq
updatedScalacOptions =
if (shared.scalacHelp && !scalacOptions.contains("-help"))
scalacOptions.appended("-help")
else scalacOptions
scalacOptions = shared.scalac.scalacOption
updatedScalacOptions = scalacOptions.withScalacExtraOptions(shared.scalacExtra)
if updatedScalacOptions.exists(ScalacOptions.ScalacPrintOptions)
logger = shared.logger
artifacts <- buildOptions.artifacts(logger, Scope.Main).toOption
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package scala.cli.commands.util

import scala.build.errors.MainClassError
import scala.build.{Build, Logger, Os}
import scala.cli.commands.util.ScalacOptionsUtil.*
import scala.cli.commands.{ScalaCommand, SharedOptions}

trait BuildCommandHelpers { self: ScalaCommand[_] =>
Expand All @@ -21,7 +22,7 @@ trait BuildCommandHelpers { self: ScalaCommand[_] =>
*/
def copyOutput(sharedOptions: SharedOptions): Unit =
sharedOptions.compilationOutput.filter(_.nonEmpty)
.orElse(sharedOptions.scalac.scalacOption.toScalacOptShadowingSeq.getScalacOption("-d"))
.orElse(sharedOptions.scalac.scalacOption.getScalacOption("-d"))
.filter(_.nonEmpty)
.map(os.Path(_, Os.pwd)).foreach(output =>
os.copy.over(successfulBuild.output, output, createFolders = true)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,37 @@
package scala.cli.commands.util

import scala.build.options.{ScalacOpt, ShadowingSeq}
import scala.cli.commands.ScalacOptions
import scala.cli.commands.{ScalacExtraOptions, ScalacOptions}

object ScalacOptionsUtil {
extension (opts: List[String]) {

def withScalacExtraOptions(scalacExtra: ScalacExtraOptions): List[String] = {
def maybeScalacExtraOption(
get: ScalacExtraOptions => Boolean,
scalacName: String
): Option[String] =
if get(scalacExtra) && !opts.contains(scalacName) then Some(scalacName) else None
val scalacHelp = maybeScalacExtraOption(_.scalacHelp, "-help")
val scalacVerbose = maybeScalacExtraOption(_.scalacVerbose, "-verbose")
opts ++ scalacHelp ++ scalacVerbose
}
def toScalacOptShadowingSeq: ShadowingSeq[ScalacOpt] =
ShadowingSeq.from(opts.filter(_.nonEmpty).map(ScalacOpt(_)))

def getScalacPrefixOption(prefixKey: String): Option[String] =
opts.find(_.startsWith(s"$prefixKey:")).map(_.stripPrefix(s"$prefixKey:"))

def getScalacOption(key: String): Option[String] = opts.toScalacOptShadowingSeq.getOption(key)

}

extension (opts: ShadowingSeq[ScalacOpt]) {
def filterScalacOptionKeys(f: String => Boolean): ShadowingSeq[ScalacOpt] =
opts.filterKeys(_.key.exists(f))
def filterNonRedirected: ShadowingSeq[ScalacOpt] =
opts.filterScalacOptionKeys(!ScalacOptions.ScalaCliRedirectedOptions.contains(_))
def getScalacOption(key: String): Option[String] =
def getOption(key: String): Option[String] =
opts.get(ScalacOpt(key)).headOption.map(_.value)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,16 @@ object SharedOptionsUtil extends CommandHelpers {
jmhVersion: Option[String] = None,
ignoreErrors: Boolean = false
): Either[BuildException, bo.BuildOptions] = either {
val releaseOpt = scalac.scalacOption.getScalacOption("-release")
val targetOpt = scalac.scalacOption.getScalacPrefixOption("-target")
jvm.jvm -> (releaseOpt.toSeq ++ targetOpt) match {
case (Some(j), compilerTargets) if compilerTargets.exists(_ != j) =>
val compilerTargetsString = compilerTargets.distinct.mkString(", ")
logger.error(
s"Warning: different target JVM ($j) and scala compiler target JVM ($compilerTargetsString) were passed."
)
case _ =>
}
val parsedPlatform = platform.map(Platform.normalize).flatMap(Platform.parse)
val platformOpt = value {
(parsedPlatform, js.js, native.native) match {
Expand Down Expand Up @@ -187,6 +197,7 @@ object SharedOptionsUtil extends CommandHelpers {
generateSemanticDbs = semanticDb,
scalacOptions = scalac
.scalacOption
.withScalacExtraOptions(scalacExtra)
.toScalacOptShadowingSeq
.filterNonRedirected
.map(Positioned.commandLine),
Expand Down Expand Up @@ -253,7 +264,7 @@ object SharedOptionsUtil extends CommandHelpers {
}

def extraJarsAndClassPath: List[os.Path] =
(extraJars ++ scalac.scalacOption.toScalacOptShadowingSeq.getScalacOption("-classpath"))
(extraJars ++ scalac.scalacOption.getScalacOption("-classpath"))
.extractedClassPath

def extraCompileOnlyClassPath: List[os.Path] = extraCompileOnlyJars.extractedClassPath
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
package scala.cli.integration

import java.io.{FileOutputStream, IOException}
import java.nio.charset.StandardCharsets
import java.nio.charset.{Charset, StandardCharsets}
import java.security.SecureRandom
import java.util.concurrent.atomic.AtomicInteger
import java.util.zip.{ZipEntry, ZipOutputStream}

import scala.cli.integration.TestInputs.compress
import scala.util.control.NonFatal

final case class TestInputs(files: (os.RelPath, String)*) {
final case class TestInputs(maybeCharset: Option[Charset], files: (os.RelPath, String)*) {
private lazy val charset = maybeCharset.getOrElse(StandardCharsets.UTF_8)
def add(extraFiles: (os.RelPath, String)*): TestInputs = TestInputs((files ++ extraFiles)*)

private def writeIn(dir: os.Path): Unit =
for ((relPath, content) <- files) {
val path = dir / relPath
os.write(path, content.getBytes(StandardCharsets.UTF_8), createFolders = true)
os.write(path, content.getBytes(charset), createFolders = true)
}
def root(): os.Path = {
val tmpDir = TestInputs.tmpDir
Expand All @@ -36,6 +37,13 @@ final case class TestInputs(files: (os.RelPath, String)*) {
}

object TestInputs {
def apply(files: (os.RelPath, String)*): TestInputs = new TestInputs(None, files*)

def apply(charsetName: String, files: (os.RelPath, String)*): TestInputs = {
val charset: Charset = Charset.forName(charsetName)
new TestInputs(Some(charset), files*)
}

def empty: TestInputs = TestInputs()

def compress(zipFilepath: os.Path, files: Seq[(os.RelPath, String)]) = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -257,25 +257,49 @@ abstract class CompileTestDefinitions(val scalaVersionOpt: Option[String])
)
}

test("Scala CLI should not infer scalac's--release if 'O --release' is passed".tag(jvmT)) {
test("Scala CLI should not infer scalac --release if --release is passed".tag(jvmT)) {
scalaJvm11Project.fromRoot { root =>
val res = os.proc(
TestUtil.cli,
"compile",
extraOptions,
"--jvm",
"11",
"-O",
"-release",
"-O",
"8",
"."
).call(cwd = root, check = false, stderr = os.Pipe)
expect(res.exitCode != 0)
expect(res.err.text().contains("isEmpty is not a member"))
val errOutput = res.err.trim()
expect(errOutput.contains("isEmpty is not a member"))
expect(errOutput.contains(
"Warning: different target JVM (11) and scala compiler target JVM (8) were passed."
))
}
}

if (actualScalaVersion.startsWith("2.1"))
test("warn for different target JVMs in --jvm, -target:x and -release".tag(jvmT)) {
scalaJvm8Project.fromRoot { root =>
val res = os.proc(
TestUtil.cli,
"compile",
extraOptions,
"--jvm",
"11",
"-release",
"8",
"-target:8",
"."
).call(cwd = root, check = false, stderr = os.Pipe)
expect(res.exitCode == 0)
val errOutput = res.err.trim()
expect(errOutput.contains(
"Warning: different target JVM (11) and scala compiler target JVM (8) were passed."
))
}
}

def compileToADifferentJvmThanBloops(
bloopJvm: String,
targetJvm: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2520,4 +2520,39 @@ abstract class RunTestDefinitions(val scalaVersionOpt: Option[String])
test("scalapy native") {
scalapyNativeTest()
}

test("scalac verbose") {
val expectedOutput = "Hello"
val mainClass = "Main"
val inputRelPath = os.rel / s"$mainClass.scala"
TestInputs(inputRelPath -> s"""object $mainClass extends App { println("$expectedOutput") }""")
.fromRoot { root =>
val res = os.proc(TestUtil.cli, ".", "--scalac-verbose", extraOptions)
.call(cwd = root, stderr = os.Pipe)
val errLines = res.err.trim.lines.toList.asScala
// there should be a lot of logs, but different stuff is logged depending on the Scala version
expect(errLines.length > 100)
expect(errLines.exists(_.startsWith("[loaded package loader scala")))
expect(errLines.exists(_.contains(s"$mainClass.scala")))
expect(res.out.trim == expectedOutput)
}
}

if (!Properties.isWin)
test("-encoding CP1252 should be handled correctly in .scala files") {
TestInputs(
charsetName = "Windows-1252",
os.rel / "s.scala" -> """object Main extends App { println("€") }"""
)
.fromRoot { root =>
val res = os.proc(
TestUtil.cli,
"s.scala",
"-encoding",
"cp1252",
extraOptions
).call(cwd = root)
expect(res.out.trim == "€")
}
}
}
26 changes: 20 additions & 6 deletions website/docs/reference/cli-options.md
Original file line number Diff line number Diff line change
Expand Up @@ -1196,6 +1196,26 @@ Aliases: `--scala-opt`, `-O`, `--scala-option`

Add a `scalac` option. Note that options starting with `-g`, `-language`, `-opt`, `-P`, `-target`, `-V`, `-W`, `-X`, and `-Y` are assumed to be Scala compiler options and don't require to be passed after `-O` or `--scalac-option`.

## Scalac extra options

Available in commands:

[`bsp`](./commands.md#bsp), [`compile`](./commands.md#compile), [`dependency-update`](./commands.md#dependency-update), [`doc`](./commands.md#doc), [`export`](./commands.md#export), [`fmt` , `format` , `scalafmt`](./commands.md#fmt), [`browse` , `metabrowse`](./commands.md#browse), [`package`](./commands.md#package), [`publish`](./commands.md#publish), [`publish local`](./commands.md#publish-local), [`repl` , `console`](./commands.md#repl), [`run`](./commands.md#run), [`setup-ide`](./commands.md#setup-ide), [`shebang`](./commands.md#shebang), [`test`](./commands.md#test)

<!-- Automatically generated, DO NOT EDIT MANUALLY -->

### `--scalac-help`

Aliases: `--help-scalac`

Show help for scalac. This is an alias for --scalac-option -help

### `--scalac-verbose`

Aliases: `--verbose-scalac`

Turn verbosity on for scalac. This is an alias for --scalac-option -verbose

## Secret options

Available in commands:
Expand Down Expand Up @@ -1250,12 +1270,6 @@ Aliases: `--scala-binary`, `--scala-bin`, `-B`
[Internal]
Set the Scala binary version

### `--scalac-help`

Aliases: `--help-scalac`

Show help for scalac. This is an alias for --scalac-option -help

### `--extra-jars`

Aliases: `--jar`, `--jars`, `--extra-jar`, `--class`, `--extra-class`, `--classes`, `--extra-classes`, `-classpath`, `-cp`, `--classpath`, `--class-path`, `--extra-class-path`
Expand Down
Loading