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

Add Task.nonSelective #4148

Draft
wants to merge 1 commit 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
2 changes: 1 addition & 1 deletion build.mill
Original file line number Diff line number Diff line change
Expand Up @@ -646,7 +646,7 @@ def formatDep(dep: Dep) = {
def listIn(path: os.Path) = interp.watchValue(os.list(path).map(_.last))

object idea extends MillPublishScalaModule {
def moduleDeps = Seq(build.scalalib, build.runner)
def moduleDeps = Seq(build.scalalib, build.scalajslib, build.scalanativelib, build.runner)
}


Expand Down
22 changes: 20 additions & 2 deletions dist/package.mill
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,20 @@ import mill.api.JarManifest
import de.tobiasroeser.mill.vcs.version.VcsVersion
import $file.ci.upload

object `package` extends RootModule with build.MillPublishJavaModule {

object `package` extends RootModule with DistModule{

object kotlinlib extends DistModule{
def moduleDeps = super.moduleDeps ++ Seq(build.kotlinlib)
}
object javascriptlib extends DistModule{
def moduleDeps = super.moduleDeps ++ Seq(build.javascriptlib)
}
object pythonlib extends DistModule{
def moduleDeps = super.moduleDeps ++ Seq(build.pythonlib)
}
}
trait DistModule extends build.MillPublishJavaModule {

/**
* Version of [[dist]] meant for local integration testing within the Mill
Expand All @@ -15,7 +28,12 @@ object `package` extends RootModule with build.MillPublishJavaModule {
object dist0 extends build.MillPublishJavaModule {
// disable scalafix here because it crashes when a module has no sources
def fix(args: String*): Command[Unit] = Task.Command {}
def moduleDeps = Seq(build.runner, build.idea)
def moduleDeps = Seq(
build.runner,
build.idea,
build.scalajslib,
build.scalanativelib
)

def testTransitiveDeps = build.runner.testTransitiveDeps() ++ Seq(
build.main.graphviz.testDep(),
Expand Down
11 changes: 10 additions & 1 deletion docs/modules/ROOT/pages/large/selective-execution.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,13 @@ def myProjectVersion: T[String] = Task.Input {
* Look at xref:fundamentals/out-dir.adoc#_mill_invalidation_tree_json[out/mill-invalidation-tree.json],
whether on disk locally or printing it out (e.g via `cat`) on your CI machines to diagnose issues
there. This would give you a richer view of what source tasks or inputs are the ones actually
triggered the invalidation, and what tasks were just invalidated due to being downstream of them.
triggered the invalidation, and what tasks were just invalidated due to being downstream of them.


== Non-Selective Tasks


include::partial$example/large/selective/10-non-selective.adoc[]



49 changes: 49 additions & 0 deletions example/large/selective/10-non-selective/build.mill
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// In some circumstances, you may want to disable selective execution for
// certain task dependencies. For example, you may have a task that contains its own
// test suite, and so exercising all downstream test suites during selective
// execution would be redundant. You can make a task dependency not considered
// during selective execution via `Task.NonSelective`:

package build
import mill._

object qux extends mill.define.Module {
def quxSource = Task.Source(millSourcePath / "qux.txt")

def quxNonSelective = Task.nonSelective(quxSource)

def quxSelective = Task { quxSource() }

def quxCommandNonSelective() = Task.Command {
System.out.println("Computing quxCommandNonSelective")
quxNonSelective().path
}
def quxCommandSelective() = Task.Command {
System.out.println("Computing quxCommandSelective")
quxSelective().path
}
}

// In this example, `quxNonSelective` is an anonymous `Task.NonSelective`, meaning that
// dependencies on `quxNonSelective` are not considered during selective execution. Thus,
// if you make a change to `qux/qux.txt`, only `quxCommandSelective` is run, while
// `quxCommandNonSelective` is ignored:

/** Usage

> ./mill selective.prepare qux._

> echo "" >> qux/qux.txt

> ./mill selective.resolve qux._ # does not resolve `quxCommandNonSelective`
qux.quxSource
qux.quxSelective
qux.quxCommandSelective

> ./mill selective.run qux._ # does not run `quxCommandNonSelective`
Computing quxCommandSelective

*/

// `Task.NonSelective` can be used to wrap any task reference to ignore it during selective
// execution.
Empty file.
44 changes: 28 additions & 16 deletions example/package.mill
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,18 @@ object `package` extends RootModule with Module {
object publishing extends Cross[ExampleCrossModuleKotlin](build.listIn(millSourcePath / "publishing"))
object web extends Cross[ExampleCrossModuleKotlin](build.listIn(millSourcePath / "web"))
}

trait ExampleCrossModuleKotlin extends ExampleCrossModuleJava {

override def localLauncher = build.dist.kotlinlib.launcher()
override def lineTransform(line: String) = this.millModuleSegments.parts.last match {
case "1-test-suite" => line
.replace("mill bar.test bar.BarTests.hello", "kotest_filter_tests='hello' kotest_filter_specs='bar.BarTests' ./mill bar.test")
.replace("compiling 1 ... source...", "Compiling 1 ... source...")
case _ => line
}
}

object scalalib extends Module {
object basic extends Cross[ExampleCrossModule](build.listIn(millSourcePath / "basic"))
object module extends Cross[ExampleCrossModule](build.listIn(millSourcePath / "module"))
Expand All @@ -61,15 +73,25 @@ object `package` extends RootModule with Module {
object web extends Cross[ExampleCrossModule](build.listIn(millSourcePath / "web"))
object native extends Cross[ExampleCrossModule](build.listIn(millSourcePath / "native"))
}

object javascriptlib extends Module {
object basic extends Cross[ExampleCrossModule](build.listIn(millSourcePath / "basic"))
object testing extends Cross[ExampleCrossModule](build.listIn(millSourcePath / "testing"))
object basic extends Cross[ExampleCrossModuleJavascript](build.listIn(millSourcePath / "basic"))
object testing extends Cross[ExampleCrossModuleJavascript](build.listIn(millSourcePath / "testing"))
}

trait ExampleCrossModuleJavascript extends ExampleCrossModule{
override def localLauncher = build.dist.javascriptlib.launcher()
}

object pythonlib extends Module {
object basic extends Cross[ExampleCrossModule](build.listIn(millSourcePath / "basic"))
object dependencies extends Cross[ExampleCrossModule](build.listIn(millSourcePath / "dependencies"))
object publishing extends Cross[ExampleCrossModule](build.listIn(millSourcePath / "publishing"))
object module extends Cross[ExampleCrossModule](build.listIn(millSourcePath / "module"))
object basic extends Cross[ExampleCrossModulePython](build.listIn(millSourcePath / "basic"))
object dependencies extends Cross[ExampleCrossModulePython](build.listIn(millSourcePath / "dependencies"))
object publishing extends Cross[ExampleCrossModulePython](build.listIn(millSourcePath / "publishing"))
object module extends Cross[ExampleCrossModulePython](build.listIn(millSourcePath / "module"))
}

trait ExampleCrossModulePython extends ExampleCrossModule{
override def localLauncher = build.dist.pythonlib.launcher()
}

object cli extends Module{
Expand Down Expand Up @@ -107,16 +129,6 @@ object `package` extends RootModule with Module {
object typescript extends Cross[ExampleCrossModule](build.listIn(millSourcePath / "typescript"))
}

trait ExampleCrossModuleKotlin extends ExampleCrossModuleJava {

override def lineTransform(line: String) = this.millModuleSegments.parts.last match {
case "1-test-suite" => line
.replace("mill bar.test bar.BarTests.hello", "kotest_filter_tests='hello' kotest_filter_specs='bar.BarTests' ./mill bar.test")
.replace("compiling 1 ... source...", "Compiling 1 ... source...")
case _ => line
}
}

trait ExampleCrossModuleJava extends ExampleCrossModule {

def upstreamCross(s: String) = s match {
Expand Down
16 changes: 16 additions & 0 deletions integration/invalidation/selective-execution/resources/build.mill
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,20 @@ object bar extends mill.define.TaskModule {
}

def defaultCommandName(): String = "barCommand"
}
object qux extends mill.define.Module {
def quxTask = Task.Source(millSourcePath / "qux.txt")

def quxNonSelective = Task.nonSelective(quxTask)
def quxSelective = Task{ quxTask() }

def quxCommandNonSelective() = Task.Command {
System.out.println("Computing quxCommandNonSelective")
quxNonSelective().path
}
def quxCommandSelective() = Task.Command {
System.out.println("Computing quxCommandSelective")
quxSelective().path
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Qux Contents
Original file line number Diff line number Diff line change
Expand Up @@ -266,5 +266,18 @@ object SelectiveExecutionTests extends UtestIntegrationTestSuite {
Seq("bar.barCommandRenamed", "foo.fooCommand", "foo.fooTaskRenamed")
)
}
test("non-selective") - integrationTest { tester =>
import tester._
eval(("selective.prepare", "qux._"), check = true)

modifyFile(workspacePath / "qux" / "qux.txt", _ + "!")

val cached = eval(("selective.resolve", "qux._"))

assert(
cached.out.linesIterator.toSet ==
Set("qux.quxTask", "qux.quxSelective", "qux.quxCommandSelective")
)
}
}
}
5 changes: 4 additions & 1 deletion integration/package.mill
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ object `package` extends RootModule {
def runClasspath: T[Seq[PathRef]]
def localRunClasspath: T[Seq[PathRef]]
def forkEnv: T[Map[String, String]]

def localLauncher = Task{ build.dist.launcher() }
trait ModeModule extends build.MillBaseTestsModule {

def mode: String = millModuleSegments.parts.last
Expand All @@ -54,9 +56,10 @@ object `package` extends RootModule {

def forkArgs = Task { super.forkArgs() ++ build.dist.forkArgs() }


def testReleaseEnv =
if (mode == "local")
T { Map("MILL_INTEGRATION_LAUNCHER" -> build.dist.launcher().path.toString()) }
T { Map("MILL_INTEGRATION_LAUNCHER" -> localLauncher().path.toString()) }
else T { Map("MILL_INTEGRATION_LAUNCHER" -> testMill().path.toString()) }

def resources = IntegrationTestModule.this.resources()
Expand Down
19 changes: 17 additions & 2 deletions main/define/src/mill/define/Task.scala
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,18 @@ abstract class Task[+T] extends Task.Ops[T] with Applyable[Task, T] {
case c: Command[_] if c.exclusive => true
case _ => false
}

def selective: Boolean = true
}

object Task extends TaskBase {

private class NonSelectiveTask[+T](t: Task[T]) extends Task[T] {
val inputs: Seq[Task[_]] = Seq(t)
def evaluate(args: mill.api.Ctx): Result[T] = Result.Success(args.args(0).asInstanceOf[T])
override def selective = false
}

/**
* A specialization of [[InputImpl]] defined via `Task.Sources`, [[SourcesImpl]]
* uses [[PathRef]]s to compute a signature for a set of source files and
Expand Down Expand Up @@ -155,6 +163,7 @@ object Task extends TaskBase {
*/
def Anon[T](t: Result[T]): Task[T] = macro Applicative.impl[Task, T, mill.api.Ctx]

def nonSelective[T](t: Task[T]): Task[T] = new NonSelectiveTask(t)
@deprecated(
"Creating a target from a task is deprecated. You most likely forgot a parenthesis pair `()`",
"Mill after 0.12.0-RC1"
Expand Down Expand Up @@ -758,8 +767,11 @@ class TargetImpl[+T](
val t: Task[T],
val ctx0: mill.define.Ctx,
val readWriter: RW[_],
val isPrivate: Option[Boolean]
val isPrivate: Option[Boolean],
override val selective: Boolean = true
) extends Target[T] {
def this(t: Task[T], ctx0: mill.define.Ctx, readWriter: RW[_], isPrivate: Option[Boolean]) =
this(t, ctx0, readWriter, isPrivate, true)
override def asTarget: Option[Target[T]] = Some(this)
// FIXME: deprecated return type: Change to Option
override def readWriterOpt: Some[RW[_]] = Some(readWriter)
Expand All @@ -769,8 +781,11 @@ class PersistentImpl[+T](
t: Task[T],
ctx0: mill.define.Ctx,
readWriter: RW[_],
isPrivate: Option[Boolean]
isPrivate: Option[Boolean],
override val selective: Boolean = true
) extends TargetImpl[T](t, ctx0, readWriter, isPrivate) {
def this(t: Task[T], ctx0: mill.define.Ctx, readWriter: RW[_], isPrivate: Option[Boolean]) =
this(t, ctx0, readWriter, isPrivate, true)
override def flushDest = false
}

Expand Down
2 changes: 2 additions & 0 deletions main/src/mill/main/SelectiveExecution.scala
Original file line number Diff line number Diff line change
Expand Up @@ -109,10 +109,12 @@ private[mill] object SelectiveExecution {

val changedRootTasks = (changedInputNames ++ changedCodeNames)
.flatMap(namesToTasks.get(_): Option[Task[_]])
.filter(_.selective)

val allNodes = breadthFirst(terminals.map(_.task: Task[_]))(_.inputs)
val downstreamEdgeMap = allNodes
.flatMap(t => t.inputs.map(_ -> t))
.filter { t => t._1.selective && t._2.selective }
.groupMap(_._1)(_._2)

breadthFirst(changedRootTasks)(downstreamEdgeMap.getOrElse(_, Nil))
Expand Down
12 changes: 6 additions & 6 deletions runner/package.mill
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,17 @@ object `package` extends RootModule with build.MillPublishScalaModule {
def moduleDeps = Seq(build.main.client)
}

def ivyDeps = Agg(
build.Deps.scalaparse
)
def moduleDeps = Seq(
build.scalalib,
build.kotlinlib,
build.scalajslib,
build.scalanativelib,
build.javascriptlib,
build.pythonlib,

build.bsp,
linenumbers,
build.main.codesig,
build.main.server,
build.main,
build.scalalib,
client
)
def skipPreviousVersions: T[Seq[String]] = Seq("0.11.0-M7")
Expand Down
Loading