Skip to content

Scalac preset option in directive #3615

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

Draft
wants to merge 7 commits into
base: main
Choose a base branch
from
Draft
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
150 changes: 147 additions & 3 deletions modules/build/src/main/scala/scala/build/CrossSources.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package scala.build

import java.io.File

import scala.build.CollectionOps.*
import scala.build.EitherCps.{either, value}
import scala.build.Ops.*
Expand All @@ -17,11 +16,14 @@ import scala.build.input.ElementsUtils.*
import scala.build.input.*
import scala.build.internal.Constants
import scala.build.internal.util.{RegexUtils, WarningMessages}
import scala.build.options.ScalacOpt.PresetOption
import scala.build.options.{
BuildOptions,
BuildRequirements,
MaybeScalaVersion,
ScalacOpt,
Scope,
ShadowingSeq,
SuppressWarningOptions,
WithBuildRequirements
}
Expand Down Expand Up @@ -261,15 +263,70 @@ object CrossSources {
else fromDirectives
}

def resolveScalacOptionsForPreset(
mayBeScalaVersion: Option[MaybeScalaVersion],
presetOption: PresetOption
): Seq[Positioned[ScalacOpt]] = {
// todo: I don't think this is the right approach
val scalaVersion = mayBeScalaVersion.flatMap(_.versionOpt) match {
case Some(version) => version
case None => Constants.defaultScalaVersion
}

presetOption match {
case PresetOption.Suggested =>
presetOptionsSuggested(scalaVersion).map(Positioned.none)

case PresetOption.CI =>
presetOptionsCI(scalaVersion).map(Positioned.none)

case PresetOption.Strict =>
presetOptionsStrict(scalaVersion).map(Positioned.none)

}

}

def buildOptionWithPreset(opts: BuildOptions): BuildOptions = {
val scalaVersion = opts.scalaOptions.scalaVersion
val scalacOpts = opts.scalaOptions.scalacOptions
val scalacPresetOpt = opts.scalaOptions.scalacPresetOption
if (scalacPresetOpt.isDefined && scalacOpts.keys.nonEmpty) {
val scalacMsg = WarningMessages.conflictingScalacOptions(
scalacPresetOpt.map(
_.preset
).get, // safe to do get here since we have check before
scalacOpts.keys.map(_.value.value)
)
logger.diagnostic(
scalacMsg,
Severity.Warning,
Nil // todo: fix this
)
opts
}
else if (scalacPresetOpt.isDefined && scalacOpts.keys.isEmpty)
// resolve the preset options and set into BuildOptions
val resolvedOpts =
resolveScalacOptionsForPreset(opts.scalaOptions.scalaVersion, scalacPresetOpt.get)
opts.copy(
scalaOptions = opts.scalaOptions.copy(scalacOptions = ShadowingSeq.from(resolvedOpts))
)
else
opts

}

val buildOptions: Seq[WithBuildRequirements[BuildOptions]] = (for {
preprocessedSource <- preprocessedSources
opts <- preprocessedSource.options.toSeq
if opts != BuildOptions() || preprocessedSource.optionsWithTargetRequirements.nonEmpty
optsWithPreset = buildOptionWithPreset(opts)
if optsWithPreset != BuildOptions() || preprocessedSource.optionsWithTargetRequirements.nonEmpty
} yield {
val baseReqs0 = baseReqs(preprocessedSource.scopePath)
preprocessedSource.optionsWithTargetRequirements :+ WithBuildRequirements(
preprocessedSource.requirements.fold(baseReqs0)(_ orElse baseReqs0),
opts
optsWithPreset
)
}).flatten

Expand Down Expand Up @@ -497,4 +554,91 @@ object CrossSources {
Severity.Warning,
transitiveAdditionalSource.positions
)

private def presetOptionsSuggested(scalaVersion: String) =
scalaVersion match {
case v if v.startsWith("2.12") =>
List(
"-encoding",
"utf8",
"-deprecation",
"-feature",
"-unchecked"
).map(ScalacOpt.apply)
case v if v.startsWith("2.13") =>
List(
"-encoding",
"utf8",
"-deprecation",
"-feature",
"-unchecked"
).map(ScalacOpt.apply)

case v if v.startsWith("3.0") =>
List(
"-encoding",
"utf8",
"-deprecation",
"-feature",
"-unchecked"
).map(ScalacOpt.apply)
}

private def presetOptionsCI(scalaVersion: String) =
scalaVersion match {
case v if v.startsWith("2.12") =>
List(
"-encoding",
"utf8",
"-deprecation",
"-feature",
"-unchecked"
).map(ScalacOpt.apply)
case v if v.startsWith("2.13") =>
List(
"-encoding",
"utf8",
"-deprecation",
"-feature",
"-unchecked"
).map(ScalacOpt.apply)

case v if v.startsWith("3.0") =>
List(
"-encoding",
"utf8",
"-deprecation",
"-feature",
"-unchecked"
).map(ScalacOpt.apply)
}

private def presetOptionsStrict(scalaVersion: String) =
scalaVersion match {
case v if v.startsWith("2.12") =>
List(
"-encoding",
"utf8",
"-deprecation",
"-feature",
"-unchecked"
).map(ScalacOpt.apply)
case v if v.startsWith("2.13") =>
List(
"-encoding",
"utf8",
"-deprecation",
"-feature",
"-unchecked"
).map(ScalacOpt.apply)

case v if v.startsWith("3.0") =>
List(
"-encoding",
"utf8",
"-deprecation",
"-feature",
"-unchecked"
).map(ScalacOpt.apply)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -139,4 +139,7 @@ object WarningMessages {
else
s"""Using 'latest' for toolkit is deprecated, use 'default' to get more stable behaviour:
| $updatedValue""".stripMargin

def conflictingScalacOptions(preset: String, scalacOptions: Seq[String]) =
s"The scalacPreset $preset is used, but explicit scalac options ${scalacOptions.mkString(",")} are provided. Preset option is ignored."
}
19 changes: 19 additions & 0 deletions modules/build/src/test/scala/scala/build/tests/BuildTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -778,6 +778,25 @@ abstract class BuildTests(server: Boolean) extends TestUtil.ScalaCliBuildSuite {
}
}

test("use scalac preset options") {
val inputs = TestInputs(
os.rel / "foo.scala" ->
"""//> using scala 2.13
|//> using options.preset suggested
|
|def foo = "bar"
|""".stripMargin
)

inputs.withBuild(defaultOptions, buildThreads, bloopConfigOpt) { (_, _, maybeBuild) =>
val expectedOptions =
Seq("-Xfatal-warnings", "-deprecation", "-unchecked")
val scalacOptions =
maybeBuild.toOption.get.options.scalaOptions.scalacOptions.toSeq.map(_.value.value)
expect(scalacOptions == expectedOptions)
}
}

test("multiple times scalac options with -Xplugin prefix") {
val inputs = TestInputs(
os.rel / "foo.scala" ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package scala.build.preprocessing.directives

import scala.build.Positioned
import scala.build.directives.*
import scala.build.errors.BuildException
import scala.build.errors.{BuildException, DirectiveErrors, InputsException}
import scala.build.options.WithBuildRequirements.*
import scala.build.options.{
BuildOptions,
Expand All @@ -19,6 +19,7 @@ import scala.cli.commands.SpecificationLevel
@DirectiveExamples("//> using options -Xasync -Xfatal-warnings")
@DirectiveExamples("//> using test.option -Xasync")
@DirectiveExamples("//> using test.options -Xasync -Xfatal-warnings")
@DirectiveExamples("//> using options.preset suggested")
@DirectiveUsage(
"using option _option_ | using options _option1_ _option2_ …",
"""`//> using scalacOption` _option_
Expand All @@ -37,6 +38,9 @@ import scala.cli.commands.SpecificationLevel
|
|`//> using test.options` _option1_ _option2_ …
|
|`//> using options` _option1_ _option2_ …
|
|`//> using options.preset` _suggested_ | _ci_ | _strict_
|""".stripMargin
)
@DirectiveDescription("Add Scala compiler options")
Expand All @@ -50,12 +54,16 @@ final case class ScalacOptions(
@DirectiveName("test.options")
@DirectiveName("test.scalacOption")
@DirectiveName("test.scalacOptions")
testOptions: List[Positioned[String]] = Nil
testOptions: List[Positioned[String]] = Nil,
@DirectiveName("option.preset")
@DirectiveName("options.preset")
presetOptions: Option[String] = None
) extends HasBuildOptionsWithRequirements {
def buildOptionsList: List[Either[BuildException, WithBuildRequirements[BuildOptions]]] = List(
ScalacOptions.buildOptions(options).map(_.withEmptyRequirements),
ScalacOptions.buildOptions(testOptions).map(_.withScopeRequirement(Scope.Test))
)
def buildOptionsList: List[Either[BuildException, WithBuildRequirements[BuildOptions]]] =
List(
ScalacOptions.buildOptions(options).map(_.withEmptyRequirements),
ScalacOptions.buildOptions(testOptions).map(_.withScopeRequirement(Scope.Test))
)
}

object ScalacOptions {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ object HashedType {
pf => pf.repr
}

implicit val presetOption: HashedType[ScalacOpt.PresetOption] = {
opt => opt.preset
}

implicit val unit: HashedType[Unit] = {
_ => ""
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import dependency.AnyDependency

import scala.build.Positioned
import scala.build.internal.Constants
import scala.build.options.ScalacOpt.PresetOption

final case class ScalaOptions(
scalaVersion: Option[MaybeScalaVersion] = None,
Expand All @@ -12,6 +13,7 @@ final case class ScalaOptions(
addScalaCompiler: Option[Boolean] = None,
semanticDbOptions: SemanticDbOptions = SemanticDbOptions(),
scalacOptions: ShadowingSeq[Positioned[ScalacOpt]] = ShadowingSeq.empty,
scalacPresetOption: Option[PresetOption] = None,
extraScalaVersions: Set[String] = Set.empty,
compilerPlugins: Seq[Positioned[AnyDependency]] = Nil,
platform: Option[Positioned[Platform]] = None,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,11 @@ object ScalacOpt {
def filterScalacOptionKeys(f: String => Boolean): ShadowingSeq[ScalacOpt] =
opts.filterKeys(_.key.exists(f))
}

sealed abstract class PresetOption(val preset: String) extends Product with Serializable
object PresetOption {
case object Suggested extends PresetOption("suggested")
case object CI extends PresetOption("ci")
case object Strict extends PresetOption("strict")
}
}
Loading