diff --git a/example/extending/evaluator/1-depmapper/build.mill b/example/extending/evaluator/1-depmapper/build.mill
new file mode 100644
index 000000000000..91622564ea62
--- /dev/null
+++ b/example/extending/evaluator/1-depmapper/build.mill
@@ -0,0 +1,79 @@
+package build
+import mill.*, scalalib.*
+
+import mill.api._
+import mill.api.daemon.SelectMode
+
+object `package` extends ScalaModule {
+ def scalaVersion = "2.13.11"
+ def mvnDeps = Seq(
+ mvn"com.lihaoyi::scalatags:0.13.1",
+ mvn"com.lihaoyi::mainargs:0.6.2"
+ )
+
+ object test extends ScalaTests {
+ def mvnDeps = Seq(mvn"com.lihaoyi::utest:0.8.5")
+ def testFramework = "utest.runner.Framework"
+ }
+
+ def depMapper(evaluator: Evaluator) = Task.Command(exclusive = true) {
+ val tasks = Seq("mvnDeps", "test.mvnDeps", "allSourceFiles")
+ val resolvedTasks = evaluator.resolveTasks(tasks, SelectMode.Multi).get
+ val executeResult = evaluator.execute(resolvedTasks)
+
+ executeResult.values match {
+ case mill.api.Result.Success(values) =>
+ val mainDeps = values(0).asInstanceOf[Seq[mill.javalib.Dep]]
+ val testDeps = values(1).asInstanceOf[Seq[mill.javalib.Dep]]
+ val sourceFiles = values(2).asInstanceOf[Seq[mill.api.PathRef]].map(_.path)
+
+ println("--- Dependency Users Report ---")
+ (mainDeps ++ testDeps).foreach { dep =>
+ val depName = s"${dep.organization}:${dep.name}:${dep.version}"
+ val usageInfo = findDependencyUsage(dep, sourceFiles)
+ if (usageInfo.nonEmpty) {
+ println(s"Dependency: $depName\nUsed By Files:")
+ usageInfo.foreach { case (file, imports) =>
+ println(s" - ${file.relativeTo(os.pwd)} (via: ${imports.mkString(", ")})")
+ }
+ println()
+ }
+ }
+ println("--- End Report ---")
+ case failure =>
+ println(s"Task execution failed: $failure")
+ }
+ }
+
+ def findDependencyUsage(
+ dep: mill.javalib.Dep,
+ sourceFiles: Seq[os.Path]
+ ): Seq[(os.Path, Seq[String])] = {
+ sourceFiles.collect {
+ case file =>
+ val imports = extractImportsForDep(os.read(file), dep)
+ if (imports.nonEmpty) Some((file, imports)) else None
+ }.flatten
+ }
+
+ def extractImportsForDep(content: String, dep: mill.javalib.Dep): Seq[String] = {
+ val importRegex = """import\s+([^\s\n;]+)""".r
+
+ importRegex.findAllMatchIn(content)
+ .map(_.group(1))
+ .filter(_.startsWith(dep.name))
+ .toSeq
+ .distinct
+ }
+
+}
+
+// This command generates a report of dependency usage in source files.
+// It uses resolveTasks and execute to gather information about dependencies and their usage in source files
+
+/** Usage
+
+> ./mill depMapper
+--- Dependency Users Report ---
+
+*/
diff --git a/example/extending/evaluator/1-depmapper/foo/src/Foo.scala b/example/extending/evaluator/1-depmapper/foo/src/Foo.scala
new file mode 100644
index 000000000000..2de577a0280f
--- /dev/null
+++ b/example/extending/evaluator/1-depmapper/foo/src/Foo.scala
@@ -0,0 +1,16 @@
+package foo
+import scalatags.Text.all._
+import mainargs.{main, ParserForMethods}
+
+object Foo {
+ def generateHtml(text: String) = {
+ h1(text).toString
+ }
+
+ @main
+ def main(text: String) = {
+ println(generateHtml(text))
+ }
+
+ def main(args: Array[String]): Unit = ParserForMethods(this).runOrExit(args)
+}
diff --git a/example/extending/evaluator/1-depmapper/foo/test/src/FooTests.scala b/example/extending/evaluator/1-depmapper/foo/test/src/FooTests.scala
new file mode 100644
index 000000000000..9dcd8bf4e040
--- /dev/null
+++ b/example/extending/evaluator/1-depmapper/foo/test/src/FooTests.scala
@@ -0,0 +1,18 @@
+package foo
+
+import utest._
+
+object FooTests extends TestSuite {
+ def tests = Tests {
+ test("simple") {
+ val result = Foo.generateHtml("hello")
+ assert(result == "
hello
")
+ result
+ }
+ test("escaping") {
+ val result = Foo.generateHtml("")
+ assert(result == "<hello>
")
+ result
+ }
+ }
+}
diff --git a/example/extending/evaluator/2-unreferencedfiles/bar/src/Foo.scala b/example/extending/evaluator/2-unreferencedfiles/bar/src/Foo.scala
new file mode 100644
index 000000000000..2de577a0280f
--- /dev/null
+++ b/example/extending/evaluator/2-unreferencedfiles/bar/src/Foo.scala
@@ -0,0 +1,16 @@
+package foo
+import scalatags.Text.all._
+import mainargs.{main, ParserForMethods}
+
+object Foo {
+ def generateHtml(text: String) = {
+ h1(text).toString
+ }
+
+ @main
+ def main(text: String) = {
+ println(generateHtml(text))
+ }
+
+ def main(args: Array[String]): Unit = ParserForMethods(this).runOrExit(args)
+}
diff --git a/example/extending/evaluator/2-unreferencedfiles/bar/test/src/FooTests.scala b/example/extending/evaluator/2-unreferencedfiles/bar/test/src/FooTests.scala
new file mode 100644
index 000000000000..9dcd8bf4e040
--- /dev/null
+++ b/example/extending/evaluator/2-unreferencedfiles/bar/test/src/FooTests.scala
@@ -0,0 +1,18 @@
+package foo
+
+import utest._
+
+object FooTests extends TestSuite {
+ def tests = Tests {
+ test("simple") {
+ val result = Foo.generateHtml("hello")
+ assert(result == "hello
")
+ result
+ }
+ test("escaping") {
+ val result = Foo.generateHtml("")
+ assert(result == "<hello>
")
+ result
+ }
+ }
+}
diff --git a/example/extending/evaluator/2-unreferencedfiles/build.mill b/example/extending/evaluator/2-unreferencedfiles/build.mill
new file mode 100644
index 000000000000..5f0699579414
--- /dev/null
+++ b/example/extending/evaluator/2-unreferencedfiles/build.mill
@@ -0,0 +1,90 @@
+package build
+import mill.*, scalalib.*
+
+import mill.api._
+import mill.api.daemon.SelectMode
+
+object `package` extends ScalaModule {
+ def scalaVersion = "2.13.11"
+ def mvnDeps = Seq(
+ mvn"com.lihaoyi::scalatags:0.13.1",
+ mvn"com.lihaoyi::mainargs:0.6.2"
+ )
+
+ object test extends ScalaTests {
+ def mvnDeps = Seq(mvn"com.lihaoyi::utest:0.8.5")
+ def testFramework = "utest.runner.Framework"
+ }
+
+ def unreferencedFiles(evaluator: Evaluator) = Task.Command(exclusive = true) {
+ val sourceFiles = Seq("allSourceFiles")
+ val segmentResult = evaluator.resolveSegments(sourceFiles, SelectMode.Multi).get
+ segmentResult.foreach { segment =>
+ println(s"Planning segment: ${segment.render}")
+ }
+
+ val resolveResult = evaluator.resolveTasks(sourceFiles, SelectMode.Multi).get
+ val plan = evaluator.plan(resolveResult)
+ .sortedGroups
+ .keys()
+ .map(_.toString)
+ .toIndexedSeq
+ plan.foreach(task => println(s"Planned task: $task"))
+
+ val executeResult = evaluator.evaluate(plan, SelectMode.Multi).get
+
+ val knownSources = executeResult.values match {
+ case mill.api.Result.Success(resultVector) =>
+ val allPaths = for {
+ resultList <- resultVector.asInstanceOf[Vector[List[Any]]]
+ item <- resultList
+ pathRef <- item match {
+ case p: mill.api.PathRef => Some(p.path)
+ case _ => None
+ }
+ } yield pathRef
+
+ println(s"Extracted ${allPaths.size} known source paths")
+ allPaths.toSet
+
+ case mill.api.Result.Failure(msg) =>
+ println(s"Task execution failed: $msg")
+ Set.empty[os.Path]
+ }
+
+ // Find all source files on disk
+ val projectRoot = os.pwd
+ val sourceExtensions = Set(".scala")
+ val diskSources = os.walk(projectRoot)
+ .filter(p => sourceExtensions.exists(p.toString.endsWith))
+ .filter(!_.segments.contains(".git"))
+ .filter(!_.segments.contains("out"))
+ .toSet
+
+ // Find unreferenced files
+ val unreferenced = diskSources -- knownSources
+ if (unreferenced.nonEmpty) {
+ println("--- Unreferenced Source Files ---")
+ unreferenced.toSeq.sorted.foreach { file =>
+ println(s" - ${file.relativeTo(projectRoot)}")
+ }
+ println(s"\nTotal: ${unreferenced.size} unreferenced files")
+ } else {
+ println("No unreferenced source files found!")
+ }
+ }
+
+}
+
+// This command finds source files that are not referenced by any module in the Mill build.
+// It uses resolveSegments, resolveTasks, plan and evaluate to gather information about source files
+// and their dependencies.
+// It also excludes files in the .git directory and Mill's output directory.
+// It prints a report of unreferenced files.
+
+/** Usage
+
+> ./mill unreferencedFiles
+Extracted 2 known source paths
+
+*/
diff --git a/example/package.mill b/example/package.mill
index 34caed3efcbe..07cd9c31823c 100644
--- a/example/package.mill
+++ b/example/package.mill
@@ -110,6 +110,7 @@ object `package` extends Module {
object jvmcode extends Cross[ExampleCrossModule](build.listCross)
object python extends Cross[ExampleCrossModule](build.listCross)
object typescript extends Cross[ExampleCrossModule](build.listCross)
+ object evaluator extends Cross[ExampleCrossModule](build.listCross)
}
trait ExampleCrossModuleKotlin extends ExampleCrossModuleJava {
diff --git a/website/docs/modules/ROOT/nav.adoc b/website/docs/modules/ROOT/nav.adoc
index 60c77f748603..860a0fa93f31 100644
--- a/website/docs/modules/ROOT/nav.adoc
+++ b/website/docs/modules/ROOT/nav.adoc
@@ -104,6 +104,7 @@
** xref:extending/meta-build.adoc[]
** xref:extending/example-typescript-support.adoc[]
** xref:extending/example-python-support.adoc[]
+** xref:extending/evaluator.adoc[]
* xref:large/large.adoc[]
** xref:large/selective-execution.adoc[]
** xref:large/multi-file-builds.adoc[]
diff --git a/website/docs/modules/ROOT/pages/extending/evaluator.adoc b/website/docs/modules/ROOT/pages/extending/evaluator.adoc
new file mode 100644
index 000000000000..0cb3bea64942
--- /dev/null
+++ b/website/docs/modules/ROOT/pages/extending/evaluator.adoc
@@ -0,0 +1,32 @@
+= Example: Evaluator API Commands
+
+The Mill `Evaluator` API provides programmatic access to Mill's core functionalities, allowing you to resolve, plan, and execute build tasks directly.
+This API is essential for extending Mill built-in features through interaction and control of the build process.
+
+== Dependency Mapper
+
+In this example, the `depMapper` task demonstrates how to use the `Evaluator` API to resolve and execute multiple build tasks,
+then analyze and report on dependency usage within your source files using both `resolveTasks` and `execute`.
+
+This task:
+
+* Resolves the main and test dependencies, as well as all source files using `resolveTasks`.
+* Executes these tasks to gather the actual dependencies values and source file paths with `execute`.
+* Scans each source file for import statements that match the resolved dependencies with helper methods.
+* Prints a report showing which dependencies are used by which source files, including the specific import statements.
+
+include::partial$example/extending/evaluator/1-depMapper.adoc[]
+
+== Orphaned Source Files
+In this example, the `unreferencedFiles` task shows how to use the `Evaluator` API to find source files in your project that are not referenced by any module.
+
+This task:
+
+* Resolves all known source files using `resolveSegments` and `resolveTasks`.
+* Plans and evaluates these tasks to collect the set of referenced source file paths using `plan` and `evaluate`.
+* Walks the project directory to find all `.scala` source files, excluding `.git` and Mill's output directories.
+* Compares the discovered files on disk with the referenced files to identify unreferenced (orphaned) source files.
+* Prints a report listing all orphaned files, or a message if none are found.
+
+include::partial$example/extending/evaluator/2-unreferencedfiles.adoc[]
+