Skip to content
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

Options validation #406

Merged
merged 14 commits into from
Nov 26, 2021
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
27 changes: 22 additions & 5 deletions modules/build/src/main/scala/scala/build/Build.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import scala.build.bloop.BloopServer
import scala.build.blooprifle.{BloopRifleConfig, VersionUtil}
import scala.build.errors._
import scala.build.internal.{Constants, CustomCodeWrapper, MainClass, Util}
import scala.build.options.validation.ValidationException
import scala.build.options.{BuildOptions, ClassPathOptions, Platform, Scope}
import scala.build.postprocessing._
import scala.collection.mutable.ListBuffer
Expand Down Expand Up @@ -81,7 +82,7 @@ object Build {
CrossKey(
BuildOptions.CrossKey(
scalaParams.scalaVersion,
options.platform
options.platform.value
),
scope
)
Expand Down Expand Up @@ -373,6 +374,18 @@ object Build {
crossBuilds = crossBuilds
)

def validate(
logger: Logger,
options: BuildOptions
): Either[BuildException, Unit] = {
val (errors, otherDiagnostics) = options.validate.toSeq.partition(_.severity == Severity.Error)
logger.log(otherDiagnostics)
if (errors.nonEmpty)
Left(CompositeBuildException(errors.map(new ValidationException(_))))
else
Right(())
}

def watch(
inputs: Inputs,
options: BuildOptions,
Expand Down Expand Up @@ -508,7 +521,8 @@ object Build {
else Seq("-sourceroot", inputs.workspace.toString)

val scalaJsScalacOptions =
if (options.platform == Platform.JS && !params.scalaVersion.startsWith("2.")) Seq("-scalajs")
if (options.platform.value == Platform.JS && !params.scalaVersion.startsWith("2."))
Seq("-scalajs")
else Nil

val bloopJvmRelease = for {
Expand Down Expand Up @@ -541,15 +555,18 @@ object Build {
List(classesDir(inputs.workspace, inputs.projectName, Scope.Main).toNIO)
else Nil

value(validate(logger, options))

val project = Project(
workspace = inputs.workspace / ".scala",
classesDir = classesDir0,
scalaCompiler = scalaCompiler,
scalaJsOptions =
if (options.platform == Platform.JS) Some(options.scalaJsOptions.config)
if (options.platform.value == Platform.JS) Some(options.scalaJsOptions.config)
else None,
scalaNativeOptions =
if (options.platform == Platform.Native) Some(options.scalaNativeOptions.bloopConfig())
if (options.platform.value == Platform.Native)
Some(options.scalaNativeOptions.bloopConfig())
else None,
projectName = inputs.scopeProjectName(scope),
classPath = artifacts.compileClassPath ++ mainClassesPath,
Expand Down Expand Up @@ -592,7 +609,7 @@ object Build {
buildClient: BloopBuildClient,
bloopServer: bloop.BloopServer
): Either[BuildException, Build] = either {
if (options.platform == Platform.Native && !value(scalaNativeSupported(options, inputs)))
if (options.platform.value == Platform.Native && !value(scalaNativeSupported(options, inputs)))
value(Left(new ScalaNativeCompatibilityError()))
else
value(Right(0))
Expand Down
8 changes: 4 additions & 4 deletions modules/build/src/main/scala/scala/build/CrossSources.scala
Original file line number Diff line number Diff line change
Expand Up @@ -43,20 +43,20 @@ final case class CrossSources(
ScopedSources(
paths
.flatMap(_.withScalaVersion(retainedScalaVersion).toSeq)
.flatMap(_.withPlatform(platform).toSeq)
.flatMap(_.withPlatform(platform.value).toSeq)
.map(_.scopedValue(defaultScope)),
inMemory
.flatMap(_.withScalaVersion(retainedScalaVersion).toSeq)
.flatMap(_.withPlatform(platform).toSeq)
.flatMap(_.withPlatform(platform.value).toSeq)
.map(_.scopedValue(defaultScope)),
mainClass,
resourceDirs
.flatMap(_.withScalaVersion(retainedScalaVersion).toSeq)
.flatMap(_.withPlatform(platform).toSeq)
.flatMap(_.withPlatform(platform.value).toSeq)
.map(_.scopedValue(defaultScope)),
buildOptions
.flatMap(_.withScalaVersion(retainedScalaVersion).toSeq)
.flatMap(_.withPlatform(platform).toSeq)
.flatMap(_.withPlatform(platform.value).toSeq)
.map(_.scopedValue(defaultScope))
)
}
Expand Down
6 changes: 4 additions & 2 deletions modules/build/src/main/scala/scala/build/Logger.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package scala.build
import java.io.{OutputStream, PrintStream}

import scala.build.blooprifle.BloopRifleLogger
import scala.build.errors.BuildException
import scala.build.errors.{BuildException, Diagnostic}
import scala.scalanative.{build => sn}

trait Logger {
Expand All @@ -13,6 +13,7 @@ trait Logger {
def log(s: => String, debug: => String): Unit
def debug(s: => String): Unit

def log(diagnostics: Seq[Diagnostic]): Unit
def log(ex: BuildException): Unit
def exit(ex: BuildException): Nothing

Expand All @@ -30,7 +31,8 @@ object Logger {
def log(s: => String, debug: => String): Unit = ()
def debug(s: => String): Unit = ()

def log(ex: BuildException): Unit = ()
def log(diagnostics: Seq[Diagnostic]): Unit = ()
def log(ex: BuildException): Unit = ()
def exit(ex: BuildException): Nothing =
throw new Exception(ex)

Expand Down
7 changes: 7 additions & 0 deletions modules/build/src/main/scala/scala/build/Position.scala
Original file line number Diff line number Diff line change
Expand Up @@ -114,5 +114,12 @@ object Position {
File(path, startPos, endPos)
}
}
final case class CommandLine() extends Position {
def render(cwd: os.Path, sep: String): String = "COMMAND_LINE"
}

final case class Custom(msg: String) extends Position {
def render(cwd: os.Path, sep: String): String = msg
}

}
6 changes: 6 additions & 0 deletions modules/build/src/main/scala/scala/build/Positioned.scala
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ object Positioned {
def none[T](value: T): Positioned[T] =
Positioned(Nil, value)

def commandLine[T](value: T): Positioned[T] =
Positioned(List(Position.CommandLine()), value)

def sequence[T](seq: Seq[Positioned[T]]): Positioned[Seq[T]] = {
val allPositions = seq.flatMap(_.positions)
val value = seq.map(_.value)
Expand All @@ -42,4 +45,7 @@ object Positioned {
(a, b) =>
Positioned(a.positions ++ b.positions, underlying.orElse(a.value, b.value))
}

implicit def ordering[T](implicit underlying: Ordering[T]): Ordering[Positioned[T]] =
Ordering.by(_.value)
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@ package scala.build.errors

import scala.build.Position

case class Diagnostic(
message: String,
severity: Severity,
positions: Seq[Position] = Nil
)

abstract class BuildException(
val message: String,
val positions: Seq[Position] = Nil,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package scala.build.errors

sealed abstract class Severity extends Product with Serializable

object Severity {
case object Error extends Severity
case object Warning extends Severity
}
44 changes: 24 additions & 20 deletions modules/build/src/main/scala/scala/build/options/BuildOptions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@ import java.nio.file.Path
import java.security.MessageDigest

import scala.build.EitherCps.{either, value}
import scala.build.errors.{BuildException, InvalidBinaryScalaVersionError}
import scala.build.errors.{BuildException, Diagnostic, InvalidBinaryScalaVersionError}
import scala.build.internal.Constants._
import scala.build.internal.{Constants, OsLibc, Util}
import scala.build.{Artifacts, Logger, Os, Positioned}
import scala.build.options.validation.BuildOptionsRule
import scala.build.{Artifacts, Logger, Os, Position, Positioned}
import scala.util.Properties

final case class BuildOptions(
Expand All @@ -32,11 +33,11 @@ final case class BuildOptions(
replOptions: ReplOptions = ReplOptions()
) {

lazy val platform: Platform =
scalaOptions.platform.getOrElse(Platform.JVM)
lazy val platform: Positioned[Platform] =
scalaOptions.platform.getOrElse(Positioned(List(Position.Custom("DEFAULT")), Platform.JVM))

lazy val projectParams: Either[BuildException, Seq[String]] = either {
val platform0 = platform match {
val platform0 = platform.value match {
case Platform.JVM => "JVM"
case Platform.JS => "Scala.JS"
case Platform.Native => "Scala Native"
Expand All @@ -46,7 +47,7 @@ final case class BuildOptions(

def addRunnerDependency: Option[Boolean] =
internalDependencies.addRunnerDependencyOpt
.orElse(if (platform == Platform.JVM) None else Some(false))
.orElse(if (platform.value == Platform.JVM) None else Some(false))

private def scalaLibraryDependencies: Either[BuildException, Seq[AnyDependency]] = either {
if (scalaOptions.addScalaLibrary.getOrElse(true)) {
Expand All @@ -62,11 +63,12 @@ final case class BuildOptions(
}

private def maybeJsDependencies: Either[BuildException, Seq[AnyDependency]] = either {
if (platform == Platform.JS) scalaJsOptions.jsDependencies(value(scalaParams).scalaVersion)
if (platform.value == Platform.JS)
scalaJsOptions.jsDependencies(value(scalaParams).scalaVersion)
else Nil
}
private def maybeNativeDependencies: Seq[AnyDependency] =
if (platform == Platform.Native) scalaNativeOptions.nativeDependencies
if (platform.value == Platform.Native) scalaNativeOptions.nativeDependencies
else Nil
private def dependencies: Either[BuildException, Seq[Positioned[AnyDependency]]] = either {
value(maybeJsDependencies).map(Positioned.none(_)) ++
Expand All @@ -87,11 +89,12 @@ final case class BuildOptions(
}

private def maybeJsCompilerPlugins: Either[BuildException, Seq[AnyDependency]] = either {
if (platform == Platform.JS) scalaJsOptions.compilerPlugins(value(scalaParams).scalaVersion)
if (platform.value == Platform.JS)
scalaJsOptions.compilerPlugins(value(scalaParams).scalaVersion)
else Nil
}
private def maybeNativeCompilerPlugins: Seq[AnyDependency] =
if (platform == Platform.Native) scalaNativeOptions.compilerPlugins
if (platform.value == Platform.Native) scalaNativeOptions.compilerPlugins
else Nil
def compilerPlugins: Either[BuildException, Seq[Positioned[AnyDependency]]] = either {
value(maybeJsCompilerPlugins).map(Positioned.none(_)) ++
Expand All @@ -108,7 +111,7 @@ final case class BuildOptions(
classPathOptions.extraSourceJars.map(_.toNIO)

private def addJvmTestRunner: Boolean =
platform == Platform.JVM &&
platform.value == Platform.JVM &&
internalDependencies.addTestRunnerDependency
private def addJsTestBridge: Option[String] =
if (internalDependencies.addTestRunnerDependency) Some(scalaJsOptions.finalVersion)
Expand Down Expand Up @@ -234,7 +237,7 @@ final case class BuildOptions(
lazy val scalaParams: Either[BuildException, ScalaParameters] = either {
val (scalaVersion, scalaBinaryVersion) =
value(computeScalaVersions(scalaOptions.scalaVersion, scalaOptions.scalaBinaryVersion))
val maybePlatformSuffix = platform match {
val maybePlatformSuffix = platform.value match {
case Platform.JVM => None
case Platform.JS => Some(scalaJsOptions.platformSuffix)
case Platform.Native => Some(scalaNativeOptions.platformSuffix)
Expand Down Expand Up @@ -265,8 +268,8 @@ final case class BuildOptions(
// FIXME We'll probably need more refined rules if we start to support extra Scala.JS or Scala Native specific types
def packageTypeOpt: Option[PackageType] =
if (packageOptions.isDockerEnabled) Some(PackageType.Docker)
else if (platform == Platform.JS) Some(PackageType.Js)
else if (platform == Platform.Native) Some(PackageType.Native)
else if (platform.value == Platform.JS) Some(PackageType.Js)
else if (platform.value == Platform.Native) Some(PackageType.Native)
else packageOptions.packageTypeOpt

private def allCrossScalaVersionOptions: Seq[BuildOptions] = {
Expand All @@ -293,12 +296,11 @@ final case class BuildOptions(
val sortedExtraPlatforms = scalaOptions0
.extraPlatforms
.toVector
.sorted
this +: sortedExtraPlatforms.map { pf =>
this +: sortedExtraPlatforms.map { case (pf, pos) =>
copy(
scalaOptions = scalaOptions0.copy(
platform = Some(pf),
extraPlatforms = Set.empty
platform = Some(Positioned(pos.positions, pf)),
extraPlatforms = Map.empty
)
)
}
Expand All @@ -319,9 +321,9 @@ final case class BuildOptions(
private def normalize: BuildOptions = {
var opt = this

if (platform != Platform.JS)
if (platform.value != Platform.JS)
opt = opt.clearJsOptions
if (platform != Platform.Native)
if (platform.value != Platform.Native)
opt = opt.clearNativeOptions

opt.copy(
Expand Down Expand Up @@ -357,6 +359,8 @@ final case class BuildOptions(

def orElse(other: BuildOptions): BuildOptions =
BuildOptions.monoid.orElse(this, other)

def validate: Seq[Diagnostic] = BuildOptionsRule.validateAll(this)
}

object BuildOptions {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,23 @@ object ConfigMonoid {
main ++ defaults
}

implicit def map[K, V](implicit valueMonoid: ConfigMonoid[V]): ConfigMonoid[Map[K, V]] =
instance(Map.empty[K, V]) {
(main, defaults) =>
(main.keySet ++ defaults.keySet).map {
key =>
val mainVal = main.getOrElse(key, valueMonoid.zero)
val defaultsVal = defaults.getOrElse(key, valueMonoid.zero)
key -> valueMonoid.orElse(mainVal, defaultsVal)
}.toMap
}

implicit val boolean: ConfigMonoid[Boolean] = instance(false) {
(main, defaults) =>
main || defaults
}

implicit val unit: ConfigMonoid[Unit] = instance(()) {
(_, _) => ()
}
}
11 changes: 11 additions & 0 deletions modules/build/src/main/scala/scala/build/options/HashedField.scala
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,17 @@ object HashedField extends LowPriorityHashedField {
update(s"$name+=${hasher.value.hashedValue(t)}")
}

implicit def map[K, V](
implicit
hasherK: Lazy[HashedType[K]],
hasherV: Lazy[HashedType[V]],
ordering: Ordering[K]
): HashedField[Map[K, V]] = {
(name, map0, update) =>
for ((k, v) <- map0.toVector.sortBy(_._1)(ordering))
update(s"$name+=${hasherK.value.hashedValue(k)}${hasherV.value.hashedValue(v)}")
}

}

abstract class LowPriorityHashedField {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,8 @@ object HashedType {
pf => pf.repr
}

implicit val unit: HashedType[Unit] = {
_ => ""
}

}
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package scala.build.options

import scala.build.Positioned

final case class JavaOptions(
javaHomeOpt: Option[os.Path] = None,
jvmIdOpt: Option[String] = None,
jvmIndexOpt: Option[String] = None,
jvmIndexOs: Option[String] = None,
jvmIndexArch: Option[String] = None,
javaOpts: Seq[String] = Nil
javaOpts: Seq[Positioned[String]] = Nil
)

object JavaOptions {
Expand Down
Loading