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

POC : static computation of source roots for code-generating tasks #4621

Draft
wants to merge 4 commits into
base: 0.12.x
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
1 change: 1 addition & 0 deletions bsp/worker/src/mill/bsp/worker/MillBuildServer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@ private class MillBuildServer(
targetIds = _ => sourcesParams.getTargets.asScala.toSeq,
tasks = {
case module: MillBuildRootModule =>
// TODO
Task.Anon {
module.sources().map(p => sourceItem(p.path, false)) ++
module.generatedSources().map(p => sourceItem(p.path, true))
Expand Down
13 changes: 9 additions & 4 deletions contrib/bloop/src/mill/contrib/bloop/BloopImpl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -116,15 +116,19 @@ class BloopImpl(evs: () => Seq[Evaluator], wd: os.Path) extends ExternalModule {
JavaModuleUtils.transitiveModules(mod, accept)
}

protected def computeModules: Seq[JavaModule] = {
private def modulesToOutFolderMap: Map[JavaModule, os.Path] = {
val evals = evs()
evals.flatMap { eval =>
if (eval != null)
JavaModuleUtils.transitiveModules(eval.rootModule, accept)
.collect { case jm: JavaModule => jm }
.collect { case jm: JavaModule => (jm, eval.outPath) }
else
Seq.empty
}
}.toMap
}

protected def computeModules: Seq[JavaModule] = {
modulesToOutFolderMap.keys.toSeq
}

// class-based pattern matching against path-dependant types doesn't seem to work.
Expand All @@ -140,7 +144,8 @@ class BloopImpl(evs: () => Seq[Evaluator], wd: os.Path) extends ExternalModule {
*/
def moduleSourceMap = Task.Input {
val sources = Task.traverse(computeModules) { m =>
m.allSources.map { paths =>
// We're not using `allSources` here, one purpose. See https://github.com/com-lihaoyi/mill/discussions/4530
m.ideSources.map { paths =>
name(m) -> paths.map(_.path)
}
}()
Expand Down
37 changes: 31 additions & 6 deletions main/define/src/mill/define/Task.scala
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@ abstract class Task[+T] extends Task.Ops[T] with Applyable[Task, T] {
*/
def flushDest: Boolean = true

/**
* A list of paths under the `Task.dest` folder that should be considered root source directories
* when generating configuration for IDEs.
*/
def generatedSourceRoots: Seq[os.SubPath] = Seq.empty

def asTarget: Option[Target[T]] = None
def asCommand: Option[Command[T]] = None
def asWorker: Option[Worker[T]] = None
Expand Down Expand Up @@ -189,9 +195,13 @@ object Task extends TaskBase {
*/
def apply(
t: NamedParameterOnlyDummy = new NamedParameterOnlyDummy,
persistent: Boolean = false
): ApplyFactory = new ApplyFactory(persistent)
class ApplyFactory private[mill] (val persistent: Boolean) extends TaskBase.TraverseCtxHolder {
persistent: Boolean = false,
generatedSourceRoots: Seq[os.SubPath] = Seq.empty
): ApplyFactory = new ApplyFactory(persistent, generatedSourceRoots)
class ApplyFactory private[mill] (
val persistent: Boolean,
val generatedSourceRoots: Seq[os.SubPath]
) extends TaskBase.TraverseCtxHolder {
def apply[T](t: Result[T])(implicit
rw: RW[T],
ctx: mill.define.Ctx
Expand Down Expand Up @@ -808,8 +818,18 @@ 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 generatedSourceRoots: Seq[os.SubPath]
) extends Target[T] {

// Added for bincompat
def this(
t: Task[T],
ctx0: mill.define.Ctx,
readWriter: RW[_],
isPrivate: Option[Boolean]
) = this(t, ctx0, readWriter, isPrivate, Seq.empty)

override def asTarget: Option[Target[T]] = Some(this)
// FIXME: deprecated return type: Change to Option
override def readWriterOpt: Some[RW[_]] = Some(readWriter)
Expand All @@ -819,8 +839,13 @@ class PersistentImpl[+T](
t: Task[T],
ctx0: mill.define.Ctx,
readWriter: RW[_],
isPrivate: Option[Boolean]
) extends TargetImpl[T](t, ctx0, readWriter, isPrivate) {
isPrivate: Option[Boolean],
generatedSourceRoots: Seq[os.SubPath]
) extends TargetImpl[T](t, ctx0, readWriter, isPrivate, generatedSourceRoots) {
// Added for bincompat
def this(t: Task[T], ctx0: mill.define.Ctx, readWriter: RW[_], isPrivate: Option[Boolean]) =
this(t, ctx0, readWriter, isPrivate, Seq.empty)

override def flushDest = false
}

Expand Down
53 changes: 51 additions & 2 deletions scalalib/src/mill/scalalib/JavaModule.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import coursier.{Repository, Type}
import mainargs.{Flag, arg}
import mill.Agg
import mill.api.{Ctx, JarManifest, MillException, PathRef, Result, internal}
import mill.define.{Command, ModuleRef, Segment, Task, TaskModule}
import mill.define.{Command, ModuleRef, NamedTask, Segment, Task, TaskModule}
import mill.scalalib.internal.ModuleUtils
import mill.scalalib.api.CompilationResult
import mill.scalalib.bsp.{BspBuildTarget, BspModule, BspUri, JvmBuildTarget}
Expand Down Expand Up @@ -847,10 +847,59 @@ trait JavaModule
*/
def generatedSources: T[Seq[PathRef]] = Task { Seq.empty[PathRef] }

/**
* A set of tasks producing generated code. The difference between this and
* [[generatedSources]] lies in the fact that IDE-related tasks
* (BSP/Bloop/GenIdea) will not run the tasks, but will use them to statically
* determine the location of the source code they will produce by looking up their
* [[Task#generatedSourceRoots]], knowing that these roots will be present under
* the dest directories once the task is run.
*/
def deferredSourceGenerators: Seq[NamedTask[Unit]] = Seq.empty

private def deferredSourceRoots(task: NamedTask[Unit])(workspace: os.Path): Seq[os.Path] = {
val dest = mill
.eval
.EvaluatorPaths
.resolveDestPaths(workspace / "out", task)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't know whether there's a better way to do it, to be honest

.dest
val explicitSourceRoots = task.generatedSourceRoots
if (explicitSourceRoots.isEmpty) Seq(dest)
else explicitSourceRoots.map(subPath => dest / subPath)
}

/**
* Computes the list of source roots that will be produced by [[deferredSourceGenerators]] without
* actually running the generators in question.
*/
def allDeferredSourceRoots: T[Seq[PathRef]] = T {
val ws = T.workspace
deferredSourceGenerators.flatMap(t => deferredSourceRoots(t)(ws)).map(PathRef(_))
}

/**
* Runs the lazy source generators, returning references to the expanded generated source roots
* they are supposed to have written code in.
*/
final def deferredGeneratedSources: T[Seq[PathRef]] = T {
val ws = T.workspace
T.sequence {
deferredSourceGenerators.map { t => t.map((_: Unit) => deferredSourceRoots(t)) }
}().flatMap(_.apply(ws)).map(PathRef(_))
}

/**
* Task that IDE-configuration tasks should rely on, as they avoid eagerly
* running source generators referenced by [[deferredSourceGenerators]]
*/
def ideSources: T[Seq[PathRef]] =
Task { sources() ++ generatedSources() ++ allDeferredSourceRoots() }

/**
* The folders containing all source files fed into the compiler
*/
def allSources: T[Seq[PathRef]] = Task { sources() ++ generatedSources() }
def allSources: T[Seq[PathRef]] =
Task { sources() ++ generatedSources() ++ deferredGeneratedSources() }
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keeps the current (0.12.x) behaviour of generatedSources to avoid breaking the ecosystem


/**
* All individual source files fed into the Java compiler
Expand Down