Skip to content

Commit

Permalink
Add ResultFailureException and T.fail convenience API
Browse files Browse the repository at this point in the history
Removed `Exception` from `Result.Failing` hierarchy.
It was added after the last release, so this should be
considered a binary compatible change.

The `T.fail` shortcut is an experiment. Pease comment WDYT.

This change was discussed in #3406
  • Loading branch information
lefou committed Aug 26, 2024
1 parent a34932e commit bf86cc7
Show file tree
Hide file tree
Showing 9 changed files with 64 additions and 16 deletions.
1 change: 0 additions & 1 deletion build.sc
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import com.github.lolgab.mill.mima.Mima
import coursier.maven.MavenRepository
import de.tobiasroeser.mill.vcs.version.VcsVersion
import com.goyeau.mill.scalafix.ScalafixModule
import example.millSourcePath
import mill._
import mill.api.JarManifest
import mill.define.NamedTask
Expand Down
15 changes: 14 additions & 1 deletion main/api/src/mill/api/Ctx.scala
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,18 @@ object Ctx {
def workspace: os.Path
}

/**
* Provide some convenience API to fail a task.
*/
trait Fail {

/**
* Fail the current task. This behaves equivalent to `return Result.Failure(msg)`.
* @param msg The failure message
*/
def fail(msg: String): Nothing = throw new ResultFailureException(msg)
}

def defaultHome: os.Path = os.home / ".mill" / "ammonite"

/**
Expand All @@ -123,7 +135,8 @@ class Ctx(
with Ctx.Args
with Ctx.Home
with Ctx.Env
with Ctx.Workspace {
with Ctx.Workspace
with Ctx.Fail {

def dest: os.Path = dest0()
def arg[T](index: Int): T = {
Expand Down
17 changes: 16 additions & 1 deletion main/api/src/mill/api/MillException.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,26 @@ package mill.api
* This exception is specifically handled in [[mill.runner.MillMain]] and [[mill.runner.MillServerMain]]. You can use it, if you need to exit Mill with a nice error message.
* @param msg The error message, to be displayed to the user.
*/
class MillException(msg: String) extends Exception(msg)
class MillException protected[api] (msg: String, cause: Throwable = null)
extends Exception(msg, cause) {
def this(msg: String) = this(msg, null)
}

class BuildScriptException(msg: String, script: Option[String])
extends MillException(
script.map(_ + ": ").getOrElse("") + "Build script contains errors:\n" + msg
) {
def this(msg: String) = this(msg, None)
}

/**
* This exception is meant to specifically exit a task evaluation with a [[Result.Failure]].
* The actual catch and lift logic need to be applied in the evaluator.
* @param msg The error message, to be displayed to the user.
* @param cause A potential cause or `null`
*/
class ResultFailureException(msg: String, cause: Throwable = null)
extends MillException(msg, cause)
// {
// def toFailure[T]: Result.Failure[T] = Result.Failure(msg)
//}
11 changes: 9 additions & 2 deletions main/api/src/mill/api/Result.scala
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,21 @@ sealed trait Result[+T] {
def asFailing: Option[Result.Failing[T]] = None
def getOrThrow: T = this match {
case Result.Success(v) => v
case f: Result.Failing[_] => throw f
case Result.Failure(msg, _) =>
// TODO: once, we have an original exception, reuse it
throw new ResultFailureException(msg)
case Result.Exception(e, _) =>
throw e
}
}

object Result {
implicit def create[T](t: => T): Result[T] = {
try Success(t)
catch {
case e: ResultFailureException =>
// TODO: add cause when Result.Failure supports it
Result.Failure(e.getMessage, None)
case e: Throwable =>
Exception(e, new OuterStack(new java.lang.Exception().getStackTrace().toIndexedSeq))
}
Expand Down Expand Up @@ -58,7 +65,7 @@ object Result {
* A failed task execution.
* @tparam T The result type of the computed task.
*/
sealed trait Failing[+T] extends java.lang.Exception with Result[T] {
sealed trait Failing[+T] extends Result[T] {
def map[V](f: T => V): Failing[V]
def flatMap[V](f: T => Result[V]): Failing[V]
override def asFailing: Option[Result.Failing[T]] = Some(this)
Expand Down
6 changes: 6 additions & 0 deletions main/define/src/mill/define/Task.scala
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,12 @@ object Target extends Applicative.Applyer[Task, Task, Result, mill.api.Ctx] {
*/
def workspace(implicit ctx: mill.api.Ctx): os.Path = ctx.workspace

/**
* Fail the current task. This behaves equivalent to `return Result.Failure(msg)`.
* @param msg The failure message
*/
def fail(msg: String)(implicit ctx: mill.api.Ctx): Nothing = ctx.fail(msg)

/**
* A target is the most common [[Task]] a user would encounter, commonly
* defined using the `def foo = T{...}` syntax. [[TargetImpl]]s require that their
Expand Down
11 changes: 9 additions & 2 deletions main/eval/src/mill/eval/Evaluator.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
package mill.eval

import mill.api.{CompileProblemReporter, DummyTestReporter, Result, TestReporter, Val}
import mill.api.{
CompileProblemReporter,
DummyTestReporter,
Result,
ResultFailureException,
TestReporter,
Val
}
import mill.api.Strict.Agg
import mill.define.{BaseModule, BaseModuleTree, Segments, Task}
import mill.eval.Evaluator.{Results, formatFailing}
Expand Down Expand Up @@ -82,7 +89,7 @@ object Evaluator {
yield {
val fss = fs.map {
case Result.Failure(t, _) => t
case Result.Exception(Result.Failure(t, _), _) => t
case Result.Exception(e: ResultFailureException, _) => e.getMessage
case ex: Result.Exception => ex.toString
}
s"${k.render} ${fss.iterator.mkString(", ")}"
Expand Down
7 changes: 4 additions & 3 deletions scalalib/api/src/mill/scalalib/api/ZincWorkerUtil.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package mill.scalalib.api

import mill.api.Loose.Agg
import mill.api.PathRef
import mill.api.{PathRef, ResultFailureException}

import scala.util.matching.Regex

trait ZincWorkerUtil {
Expand Down Expand Up @@ -68,7 +69,7 @@ trait ZincWorkerUtil {

def scalaJSBinaryVersion(scalaJSVersion: String): String = scalaJSVersion match {
case _ if scalaJSVersion.startsWith("0.6.") =>
throw new Exception("Scala.js 0.6 is not supported")
throw new ResultFailureException("Scala.js 0.6 is not supported")
case ScalaJSFullVersion(major, minor, patch, suffix) =>
if (suffix != null && minor == "0" && patch == "0")
s"$major.$minor$suffix"
Expand All @@ -78,7 +79,7 @@ trait ZincWorkerUtil {

def scalaJSWorkerVersion(scalaJSVersion: String): String = scalaJSVersion match {
case _ if scalaJSVersion.startsWith("0.6.") =>
throw new Exception("Scala.js 0.6 is not supported")
throw new ResultFailureException("Scala.js 0.6 is not supported")
case ScalaJSFullVersion(major, _, _, _) =>
major
}
Expand Down
4 changes: 2 additions & 2 deletions scalalib/src/mill/scalalib/PublishModule.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package mill
package scalalib

import mill.define.{Command, ExternalModule, Target, Task}
import mill.api.{JarManifest, PathRef, Result}
import mill.api.{BuildScriptException, JarManifest, PathRef, Result}
import mill.scalalib.PublishModule.checkSonatypeCreds
import mill.scalalib.publish.SonatypeHelpers.{
PASSWORD_ENV_VARIABLE_NAME,
Expand All @@ -20,7 +20,7 @@ trait PublishModule extends JavaModule { outer =>
override def moduleDeps: Seq[PublishModule] = super.moduleDeps.map {
case m: PublishModule => m
case other =>
throw new Exception(
throw new BuildScriptException(
s"PublishModule moduleDeps need to be also PublishModules. $other is not a PublishModule"
)
}
Expand Down
8 changes: 4 additions & 4 deletions scalalib/src/mill/scalalib/ZincWorkerModule.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ import coursier.Repository
import mainargs.Flag
import mill.Agg
import mill._
import mill.api.{Ctx, FixSizedCache, KeyedLockedCache, PathRef, Result}
import mill.define.{ExternalModule, Discover}
import mill.api.{Ctx, FixSizedCache, KeyedLockedCache, PathRef, Result, ResultFailureException}
import mill.define.{Discover, ExternalModule}
import mill.scalalib.Lib.resolveDependencies
import mill.scalalib.api.ZincWorkerUtil.{isBinaryBridgeAvailable, isDotty, isDottyOrScala3}
import mill.scalalib.api.{ZincWorkerApi, ZincWorkerUtil, Versions}
import mill.scalalib.api.{Versions, ZincWorkerApi, ZincWorkerUtil}
import mill.util.Util.millProjectModule

/**
Expand Down Expand Up @@ -76,7 +76,7 @@ trait ZincWorkerModule extends mill.Module with OfflineSupportModule { self: Cou
scalaCompilerBridgeJar(x, y, repositoriesTask())
.asSuccess
.getOrElse(
throw new Exception(s"Failed to load compiler bridge for $x $y")
throw new ResultFailureException(s"Failed to load compiler bridge for $x $y")
)
.value
)),
Expand Down

0 comments on commit bf86cc7

Please sign in to comment.