Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
228 commits
Select commit Hold shift + click to select a range
13e85ee
.
lihaoyi Mar 23, 2026
8f26479
.
lihaoyi Mar 23, 2026
19e74e0
.
lihaoyi Mar 23, 2026
595d785
.
lihaoyi Mar 23, 2026
1202b6e
.
lihaoyi Mar 23, 2026
a1bc444
.
lihaoyi Mar 23, 2026
9a658d2
.
lihaoyi Mar 23, 2026
403ca9e
.
lihaoyi Mar 23, 2026
516704a
.
lihaoyi Mar 23, 2026
4656ea3
.
lihaoyi Mar 23, 2026
8fdd6d4
.
lihaoyi Mar 23, 2026
aaf0703
merge
lihaoyi Apr 17, 2026
b1cefb6
.
lihaoyi Apr 18, 2026
a423265
merge
lihaoyi Apr 18, 2026
d410af6
.
lihaoyi Apr 21, 2026
bc89581
.
lihaoyi Apr 21, 2026
ed85e96
.
lihaoyi Apr 21, 2026
cc12001
.
lihaoyi Apr 21, 2026
a43e392
.
lihaoyi Apr 21, 2026
5d754d1
.
lihaoyi Apr 22, 2026
6b6c0e5
.
lihaoyi Apr 22, 2026
52d4f80
.
lihaoyi Apr 22, 2026
3ec44eb
.
lihaoyi Apr 23, 2026
6972646
.
lihaoyi Apr 23, 2026
f277551
.
lihaoyi Apr 23, 2026
a029596
.
lihaoyi Apr 23, 2026
a13bc1c
.
lihaoyi Apr 23, 2026
2f4ef1f
.
lihaoyi Apr 23, 2026
e9c7eb5
.
lihaoyi Apr 23, 2026
77cb401
.
lihaoyi Apr 23, 2026
9ab09ab
.
lihaoyi Apr 23, 2026
7c6e3b9
.
lihaoyi Apr 23, 2026
788ce10
.
lihaoyi Apr 23, 2026
e1883dc
.
lihaoyi Apr 23, 2026
b1c3b1c
.
lihaoyi Apr 23, 2026
ef48b50
.
lihaoyi Apr 23, 2026
24d5867
.
lihaoyi Apr 23, 2026
3b117cd
.
lihaoyi Apr 23, 2026
14ad707
.
lihaoyi Apr 23, 2026
5b52905
.
lihaoyi Apr 23, 2026
18bba47
.
lihaoyi Apr 23, 2026
1351fd4
.
lihaoyi Apr 23, 2026
a1d9807
.
lihaoyi Apr 24, 2026
eb1f363
.
lihaoyi Apr 24, 2026
23cea8e
.
lihaoyi Apr 24, 2026
9729233
.
lihaoyi Apr 24, 2026
8c76c67
.
lihaoyi Apr 24, 2026
efeea95
.
lihaoyi Apr 24, 2026
7402fc2
.
lihaoyi Apr 24, 2026
0ac17d9
.
lihaoyi Apr 24, 2026
8c44a87
.
lihaoyi Apr 24, 2026
a1df3a7
.
lihaoyi Apr 24, 2026
bc2e5a9
.
lihaoyi Apr 24, 2026
1f76543
.
lihaoyi Apr 24, 2026
16d5b05
.
lihaoyi Apr 24, 2026
dd80fc8
.
lihaoyi Apr 24, 2026
cb94800
.
lihaoyi Apr 24, 2026
c7e3652
.
lihaoyi Apr 24, 2026
f21b0af
.
lihaoyi Apr 24, 2026
80b4ccf
.
lihaoyi Apr 24, 2026
34779a8
.
lihaoyi Apr 24, 2026
f914610
.
lihaoyi Apr 24, 2026
ea31b86
wip
lihaoyi Apr 24, 2026
97648b1
.
lihaoyi Apr 24, 2026
d311578
.
lihaoyi Apr 24, 2026
6fef0fd
.
lihaoyi Apr 24, 2026
a5c8559
.
lihaoyi Apr 24, 2026
53d9068
.
lihaoyi Apr 24, 2026
3e16edf
.
lihaoyi Apr 24, 2026
13597a7
.
lihaoyi Apr 24, 2026
fe31f09
.
lihaoyi Apr 24, 2026
4dd3b44
.
lihaoyi Apr 24, 2026
4f9bb9c
.
lihaoyi Apr 24, 2026
bb4d03e
.
lihaoyi Apr 24, 2026
8c2669c
.
lihaoyi Apr 24, 2026
7c69555
.
lihaoyi Apr 24, 2026
ee26105
.
lihaoyi Apr 24, 2026
a110d35
.
lihaoyi Apr 25, 2026
8b9f79b
.
lihaoyi Apr 25, 2026
65fdf50
.
lihaoyi Apr 25, 2026
627f02f
.
lihaoyi Apr 25, 2026
e783899
.
lihaoyi Apr 25, 2026
ca5074c
.
lihaoyi Apr 25, 2026
a46b876
.
lihaoyi Apr 25, 2026
13786d9
.
lihaoyi Apr 25, 2026
8fa305b
.
lihaoyi Apr 25, 2026
ee32622
.
lihaoyi Apr 25, 2026
a8a2575
.
lihaoyi Apr 25, 2026
0161bb8
.
lihaoyi Apr 25, 2026
3dec97c
.
lihaoyi Apr 25, 2026
1b1017a
.
lihaoyi Apr 25, 2026
89b8037
.
lihaoyi Apr 25, 2026
f5df7f1
.
lihaoyi Apr 25, 2026
6cdd017
.
lihaoyi Apr 25, 2026
881b6d7
.
lihaoyi Apr 25, 2026
6c32d5f
.
lihaoyi Apr 25, 2026
5464d62
.
lihaoyi Apr 25, 2026
5ec150b
.
lihaoyi Apr 26, 2026
040a39b
.
lihaoyi Apr 26, 2026
32b1a0a
.
lihaoyi Apr 26, 2026
3df6898
.
lihaoyi Apr 26, 2026
ea6c560
.
lihaoyi Apr 26, 2026
e42075b
.
lihaoyi Apr 26, 2026
1875960
.
lihaoyi Apr 26, 2026
37be7c5
.
lihaoyi Apr 26, 2026
e50e088
.
lihaoyi Apr 26, 2026
76c0b44
.
lihaoyi Apr 26, 2026
64c6296
.
lihaoyi Apr 26, 2026
e31ac44
.
lihaoyi Apr 26, 2026
2ec6931
.
lihaoyi Apr 26, 2026
391ce5a
.
lihaoyi Apr 26, 2026
be0e105
.
lihaoyi Apr 26, 2026
a3a6393
.
lihaoyi Apr 26, 2026
e6d521a
.
lihaoyi Apr 26, 2026
bbbc0f3
.
lihaoyi Apr 26, 2026
61c38f9
.
lihaoyi Apr 26, 2026
b02753f
.
lihaoyi Apr 26, 2026
10c24e6
.
lihaoyi Apr 26, 2026
a626d8c
.
lihaoyi Apr 26, 2026
3f3f955
.
lihaoyi Apr 26, 2026
1864e2b
.
lihaoyi Apr 26, 2026
956ca73
.
lihaoyi Apr 26, 2026
b9b2b74
.
lihaoyi Apr 26, 2026
2354bd9
.
lihaoyi Apr 26, 2026
d71a07a
.
lihaoyi Apr 26, 2026
4fd43e7
.
lihaoyi Apr 26, 2026
0eef430
.
lihaoyi Apr 26, 2026
0be9897
.
lihaoyi Apr 26, 2026
df79a25
.
lihaoyi Apr 26, 2026
2d4fb2c
.
lihaoyi Apr 26, 2026
dd2d5f3
.
lihaoyi Apr 26, 2026
33079b2
.
lihaoyi Apr 26, 2026
f09c437
.
lihaoyi Apr 26, 2026
7179f87
.
lihaoyi Apr 26, 2026
389309a
.
lihaoyi Apr 26, 2026
29d6536
.
lihaoyi Apr 26, 2026
71dd699
.
lihaoyi Apr 27, 2026
273daa7
.
lihaoyi Apr 27, 2026
3c2e4f5
.
lihaoyi Apr 27, 2026
97aa2fc
.
lihaoyi Apr 27, 2026
f4d6f08
.
lihaoyi Apr 27, 2026
e84b0f2
.
lihaoyi Apr 27, 2026
86baa62
.
lihaoyi Apr 27, 2026
4dca24b
.
lihaoyi Apr 27, 2026
079b0e9
.
lihaoyi Apr 27, 2026
35336bb
[autofix.ci] apply automated fixes
autofix-ci[bot] Apr 27, 2026
73d2cee
.
lihaoyi Apr 28, 2026
c5002d0
.
lihaoyi Apr 28, 2026
c1397e9
.
lihaoyi Apr 28, 2026
e733461
.
lihaoyi Apr 28, 2026
0ff8deb
.
lihaoyi Apr 28, 2026
70fea27
.
lihaoyi Apr 28, 2026
12013cd
.
lihaoyi Apr 28, 2026
d76625d
.
lihaoyi Apr 28, 2026
63d36e5
.
lihaoyi Apr 28, 2026
192e167
.
lihaoyi Apr 28, 2026
8fe0d36
.
lihaoyi Apr 28, 2026
b2a77c7
.
lihaoyi Apr 28, 2026
30af340
.
lihaoyi Apr 29, 2026
9d55818
.
lihaoyi Apr 29, 2026
6f96f67
.
lihaoyi Apr 29, 2026
d58d623
.
lihaoyi Apr 29, 2026
f0efc3f
canonical lock ordering to avoid deadlocks
lihaoyi Apr 29, 2026
e20cbcc
.
lihaoyi Apr 29, 2026
4609030
.
lihaoyi Apr 29, 2026
20169d8
.
lihaoyi Apr 29, 2026
2197fa1
.
lihaoyi Apr 29, 2026
37f766b
.
lihaoyi Apr 29, 2026
dbdd065
.
lihaoyi Apr 29, 2026
fcfa98f
.
lihaoyi Apr 29, 2026
9c1737b
.
lihaoyi Apr 29, 2026
3d098ae
.
lihaoyi Apr 30, 2026
4572e94
.
lihaoyi Apr 30, 2026
101e594
.
lihaoyi Apr 30, 2026
36f33fb
.
lihaoyi Apr 30, 2026
7fb2029
.
lihaoyi Apr 30, 2026
760828b
.
lihaoyi Apr 30, 2026
a8c96fb
.
lihaoyi Apr 30, 2026
4486bfa
.
lihaoyi Apr 30, 2026
6099638
.
lihaoyi Apr 30, 2026
b8da2e7
.
lihaoyi Apr 30, 2026
db95421
.
lihaoyi Apr 30, 2026
a7c40d2
.
lihaoyi Apr 30, 2026
074668c
.
lihaoyi Apr 30, 2026
3570c98
.
lihaoyi Apr 30, 2026
1e62733
.
lihaoyi Apr 30, 2026
39dc959
.
lihaoyi Apr 30, 2026
17297ab
.
lihaoyi Apr 30, 2026
58c16fe
.
lihaoyi Apr 30, 2026
a2025e4
.
lihaoyi Apr 30, 2026
62afd33
.
lihaoyi Apr 30, 2026
be6a058
.
lihaoyi Apr 30, 2026
bc9b1f2
implement two-phase meta build invalidation checking both inputs and …
lihaoyi Apr 30, 2026
989cac1
fix tests
lihaoyi Apr 30, 2026
a6fb0c2
.
lihaoyi Apr 30, 2026
1099da2
.
lihaoyi Apr 30, 2026
964ce2b
.
lihaoyi Apr 30, 2026
d86cded
.
lihaoyi Apr 30, 2026
66233bf
.
lihaoyi Apr 30, 2026
1e7e8af
.
lihaoyi Apr 30, 2026
2660d3f
optimizations
lihaoyi Apr 30, 2026
fab4d08
optimizations
lihaoyi Apr 30, 2026
1a0dd90
optimizations
lihaoyi May 1, 2026
d7c35cc
optimizations
lihaoyi May 1, 2026
be77463
optimizations
lihaoyi May 1, 2026
dfcf539
optimizations
lihaoyi May 1, 2026
5cd3e55
Merge branch 'main' into concurrency
lihaoyi May 1, 2026
016f9e9
debug
lihaoyi May 1, 2026
f67f158
debug
lihaoyi May 1, 2026
5e080d1
debug
lihaoyi May 1, 2026
a546ba6
debug
lihaoyi May 2, 2026
aaf1bbe
debug
lihaoyi May 2, 2026
e7f20dd
debug
lihaoyi May 2, 2026
4ef45d1
debug
lihaoyi May 2, 2026
28a090b
debug
lihaoyi May 2, 2026
90934bd
debug
lihaoyi May 2, 2026
2a1f59c
debug
lihaoyi May 2, 2026
8f561ac
.
lihaoyi May 2, 2026
8200282
debug
lihaoyi May 3, 2026
bada474
.
lihaoyi May 3, 2026
3846362
.
lihaoyi May 3, 2026
6a1db7d
.
lihaoyi May 3, 2026
7f7333c
.
lihaoyi May 3, 2026
1a2289c
.
lihaoyi May 3, 2026
85d208a
.
lihaoyi May 3, 2026
ce968d9
.
lihaoyi May 3, 2026
8a195bf
.
lihaoyi May 3, 2026
4e90063
Merge branch 'main' into concurrency
lihaoyi May 4, 2026
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
11 changes: 9 additions & 2 deletions core/api/daemon/src/mill/api/daemon/internal/BuildFileApi.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,18 @@ import mill.api.daemon.Watchable
trait BuildFileApi {
def rootModule: RootModuleApi
def moduleWatchedValues: Seq[Watchable]
def evalWatchedValues: collection.mutable.Buffer[Watchable]

/**
* Run `body` with a fresh per-evaluation eval-watch buffer installed on the
* current thread; returns the body result and the accumulated watches.
* Concurrent evaluations get isolated buffers so they cannot lose or mix
* each other's watches.
*/
def withEvalWatchedValues[T](body: => T): (T, Seq[Watchable])
}
object BuildFileApi {
class Bootstrap(val rootModule: RootModuleApi) extends BuildFileApi {
def moduleWatchedValues = Nil
def evalWatchedValues = collection.mutable.Buffer[Watchable]()
def withEvalWatchedValues[T](body: => T): (T, Seq[Watchable]) = (body, Nil)
}
}
10 changes: 10 additions & 0 deletions core/api/daemon/src/mill/api/daemon/internal/EvaluatorApi.scala
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,14 @@ trait EvaluatorApi extends AutoCloseable {
@unused allowPositionalCommandArgs: Boolean = false
): Result[Boolean] = Result.Success(false)

private[mill] def probeSelectiveReuse(
@unused scriptArgs: Seq[String],
@unused selectMode: SelectMode,
@unused previousMetadata: String,
@unused allowPositionalCommandArgs: Boolean = false
): Result[EvaluatorApi.SelectiveReuseDecision] =
Result.Success(EvaluatorApi.SelectiveReuseDecision(reusable = false, previousMetadata))

/**
* Returns a copy of this evaluator with the isFinalDepth flag set to the given value.
* Used to defer the decision of whether this is the final depth until after
Expand All @@ -48,6 +56,8 @@ trait EvaluatorApi extends AutoCloseable {
private[mill] def withIsFinalDepth(isFinalDepth: Boolean): EvaluatorApi = this
}
object EvaluatorApi {
private[mill] case class SelectiveReuseDecision(reusable: Boolean, nextMetadata: String)

trait Result[T] {
def watchable: Seq[Watchable]
def values: mill.api.daemon.Result[Seq[T]]
Expand Down
146 changes: 146 additions & 0 deletions core/api/daemon/src/mill/api/daemon/internal/LauncherLocking.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
package mill.api.daemon.internal

import java.io.PrintStream
import java.nio.file.Path

/**
* Locking APIs that let multiple launchers run concurrently. Acquisition order:
* [[metaBuildLock]] -> [[exclusiveLock]] -> [[taskLock]] (outer to inner).
*/
private[mill] trait LauncherLocking extends AutoCloseable {

/**
* Per-meta-build-depth lock. Read-then-Write upgrade during bootstrap; Read
* lease is retained for the rest of the launcher run after the frame is
* published, so concurrent launchers share the same frame via Reads.
*/
def metaBuildLock(
depth: Int,
kind: LauncherLocking.LockKind,
waitReporter: LauncherLocking.WaitReporter
): LauncherLocking.Lease

/**
* Non-blocking, non-queued Write attempt on the meta-build lock at
* `depth`. Returns [[scala.None]] if Write is not immediately
* available; the caller can then back off, re-probe under Read, and
* decide whether Write is still needed. Failed tries do NOT register
* as queued writers, so the caller's subsequent Read attempts are not
* blocked by writer-priority — this is what makes the retryable
* read-then-write pattern in [[mill.internal.LockUpgrade.readThenWrite]]
* work without poisoning the caller's own re-probe.
*/
def tryMetaBuildWriteLock(depth: Int): Either[String, LauncherLocking.Lease]

/**
* Block on the meta-build lock at `depth` for up to `timeoutMs`,
* returning on the first lock state change (close/downgrade) or
* timeout. Used by the retryable read-then-write loop to sleep
* efficiently between try-Write attempts.
*/
def awaitMetaBuildStateChange(depth: Int, timeoutMs: Long): Unit

/**
* Per-task-`dest` lock. Read-then-Write upgrade on cache miss; Read lease is
* retained via `Execution.LeaseTracker` until all transitive downstream
* terminals complete, so concurrent launchers cannot overwrite outputs that
* another launcher's downstream is still reading.
*/
def taskLock(
path: Path,
displayLabel: String,
kind: LauncherLocking.LockKind,
waitReporter: LauncherLocking.WaitReporter
): LauncherLocking.Lease

/**
* Non-blocking, non-queued Write counterpart of [[taskLock]].
* See [[tryMetaBuildWriteLock]] for semantics.
*/
def tryTaskWriteLock(
path: Path,
displayLabel: String
): Either[String, LauncherLocking.Lease]

/**
* Bounded await on per-task lock state changes; counterpart of
* [[awaitMetaBuildStateChange]].
*/
def awaitTaskStateChange(path: Path, displayLabel: String, timeoutMs: Long): Unit

/**
* Daemon-wide lock taken by each task batch. Normal batches take Read so
* they overlap; `Task.Command(exclusive = true)` batches take Write so they
* run alone. Tasks that mutate in-tree sources (e.g. `build.mill`) MUST be
* declared `exclusive = true` — there is no per-source lock.
*/
def exclusiveLock(
kind: LauncherLocking.LockKind,
waitReporter: LauncherLocking.WaitReporter
): LauncherLocking.Lease
}

private[mill] object LauncherLocking {
enum LockKind {
case Read, Write
}

trait Lease extends AutoCloseable {
def downgradeToRead(): Unit = ()
}

/**
* Surface a "blocked on lock" status to the user when a lock acquisition has
* to wait. Implementations should display the message in a way that doesn't
* disturb the active console UI (e.g. via the multi-line prompt's detail
* line) and clear it when the returned token is closed.
*
* Implementations may also respond to holder changes by re-calling
* `reportWait` — each call replaces the previous wait status.
*/
trait WaitReporter {
def reportWait(message: String): AutoCloseable
}

object WaitReporter {
private val NoopToken: AutoCloseable = () => ()

/** Discards wait events. Useful for tests and noBuildLock. */
val Noop: WaitReporter = (_: String) => NoopToken

/**
* Prints the wait message once on `reportWait` and does nothing on close.
* Matches the legacy behavior: a one-shot stderr line that scrolls into
* the user's terminal history. Used when no live prompt is available
* (early bootstrap, no-daemon, BSP, `--ticker false`, etc.).
*/
def stderr(stream: PrintStream): WaitReporter = (msg: String) => {
stream.println(msg)
NoopToken
}
}

object Noop extends LauncherLocking {
private object NoopLease extends Lease {
override def close(): Unit = ()
}
override def metaBuildLock(
depth: Int,
kind: LockKind,
waitReporter: WaitReporter
): Lease = NoopLease
override def tryMetaBuildWriteLock(depth: Int): Either[String, Lease] = Right(NoopLease)
override def awaitMetaBuildStateChange(depth: Int, timeoutMs: Long): Unit = ()
override def taskLock(
path: Path,
displayLabel: String,
kind: LockKind,
waitReporter: WaitReporter
): Lease = NoopLease
override def tryTaskWriteLock(path: Path, displayLabel: String): Either[String, Lease] =
Right(NoopLease)
override def awaitTaskStateChange(path: Path, displayLabel: String, timeoutMs: Long): Unit = ()
override def exclusiveLock(kind: LockKind, waitReporter: WaitReporter): Lease = NoopLease
override def close(): Unit = ()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package mill.api.daemon.internal

import mill.constants.DaemonFiles
import mill.constants.OutFiles

import java.nio.file.Path

private[mill] trait LauncherOutFiles extends AutoCloseable {
def consoleTail: Path
def profile: Path
def chromeProfile: Path
def dependencyTree: Path
def invalidationTree: Path

def publishLiveArtifacts(): Unit

def publishArtifacts(): Unit
}

private[mill] object LauncherOutFiles {
def noop(out: Path): LauncherOutFiles = new LauncherOutFiles {
override val consoleTail: Path = out.resolve(DaemonFiles.millConsoleTail)
override val profile: Path = out.resolve(OutFiles.millProfile)
override val chromeProfile: Path = out.resolve(OutFiles.millChromeProfile)
override val dependencyTree: Path = out.resolve(OutFiles.millDependencyTree)
override val invalidationTree: Path = out.resolve(OutFiles.millInvalidationTree)
override def publishLiveArtifacts(): Unit = ()
override def publishArtifacts(): Unit = ()
override def close(): Unit = ()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package mill.api.daemon.internal

import java.util.concurrent.atomic.AtomicLong

/**
* Runnable wrapper used by daemon-level priority queues.
*
* This lives in `mill.api.daemon.internal` so it is loaded from the shared daemon API
* classloader rather than from refreshable build/evaluator classloaders.
*/
private[mill] class PriorityRunnable(val priority: Int, run0: () => Unit)
extends Runnable
with Comparable[PriorityRunnable] {
def run(): Unit = run0()

val priorityRunnableIndex: Long = PriorityRunnable.priorityRunnableCount.getAndIncrement()

override def compareTo(o: PriorityRunnable): Int = priority.compareTo(o.priority) match {
case 0 =>
// `Comparable` wants a total ordering. This index is assigned when a task
// is submitted, so equal-priority work runs in submission order.
assert(this == o || this.priorityRunnableIndex != o.priorityRunnableIndex)
this.priorityRunnableIndex.compareTo(o.priorityRunnableIndex)
case n => n
}
}

private object PriorityRunnable {
private val priorityRunnableCount = new AtomicLong()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package mill.api.daemon.internal.bsp

import mill.api.daemon.Watchable
import mill.api.daemon.internal.{CompileProblemReporter, EvaluatorApi}

/**
* Daemon-side hook the BSP worker calls once per BSP request. The worker
* supplies the active command label, a meta-build compile reporter keyed by
* depth, and the body that consumes the freshly-bootstrapped evaluators; the
* daemon runs `MillBuildBootstrap` and threads the resulting evaluators (with
* the watches captured during the run, plus any error message) into `body`.
*
* Declared as a SAM trait rather than a Scala 3 polymorphic-function alias so
* the worker can locate the method via `Class.getMethod` without depending on
* the polymorphic-function's erased shape (the previous `[T] => (a, b, c) => T`
* alias erased to `scala.Function3` and caused a `NoSuchMethodException` at
* the reflection boundary when the lookup mistakenly used `Function2`).
*/
trait BspBootstrapBridge {
def apply[T](
activeCommandMessage: String,
metaBuildReporter: Int => Option[CompileProblemReporter],
body: (Seq[EvaluatorApi], Seq[Watchable], Option[String]) => T
): T
}
Original file line number Diff line number Diff line change
@@ -1,23 +1,14 @@
package mill.api.daemon.internal.bsp

import mill.api.daemon.internal.EvaluatorApi
import mill.api.daemon.Watchable

import scala.concurrent.Future

/** With this server handle you can interact with a running Mill BSP server. */
trait BspServerHandle {

/**
* Starts a new session with the given evaluator. Doesn't block or wait for the session to end.
* Completes when the BSP server shuts down or asks the launcher to reload the workspace.
*/
def startSession(
evaluators: Seq[EvaluatorApi],
errored: Boolean,
watched: Seq[Watchable]
): Future[BspServerResult]

def resetSession(): Unit
def shutdownFuture: Future[BspServerResult]

/** Stops the BSP server. */
def close(): Unit
Expand Down
38 changes: 31 additions & 7 deletions core/api/java11/src/mill/api/BuildCtx.scala
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,32 @@ object BuildCtx {
def withFilesystemCheckerDisabled[T](block: => T): T =
os.checker.withValue(os.Checker.Nop) { block }

protected[mill] val watchedValues: mutable.Buffer[Watchable] = mutable.Buffer.empty[Watchable]
protected[mill] val evalWatchedValues: mutable.Buffer[Watchable] = mutable.Buffer.empty[Watchable]
// Module-init watches: populated by `<clinit>` of the build classloader
// (serialized by the JVM), so a shared default buffer is safe.
private val moduleWatchedDefault: mutable.Buffer[Watchable] = mutable.Buffer.empty[Watchable]

// Eval watches: populated per evaluation via `BuildCtx.evalWatch`.
// The DynamicVariable + `ExecutionContexts.ThreadPool` capture/rebind
// give each evaluation an isolated buffer so concurrent launchers
// don't clobber each other's watches.
private[mill] val watchedValues0: DynamicVariable[mutable.Buffer[Watchable]] =
new DynamicVariable(moduleWatchedDefault)
private[mill] val evalWatchedValues0: DynamicVariable[mutable.Buffer[Watchable]] =
new DynamicVariable(mutable.Buffer.empty[Watchable])

protected[mill] def watchedValues: mutable.Buffer[Watchable] = watchedValues0.value
protected[mill] def evalWatchedValues: mutable.Buffer[Watchable] = evalWatchedValues0.value

/**
* Run `body` with a fresh per-evaluation `evalWatchedValues` buffer; returns
* the buffer alongside the body result so the caller can collect the
* accumulated watches without racing other evaluations.
*/
private[mill] def withEvalWatchedValues[T](body: => T): (T, mutable.Buffer[Watchable]) = {
val buf = mutable.Buffer.empty[Watchable]
val result = evalWatchedValues0.withValue(buf)(body)
(result, buf)
}

/**
* Register a compute value as watched during module initialization, so Mill knows
Expand All @@ -52,23 +76,23 @@ object BuildCtx {
v.hashCode(),
fn.value + ":" + ln.value
)
watchedValues.append(watchable)
watchedValues0.value.append(watchable)
v
}
}

def watch(p: os.Path): os.Path = withFilesystemCheckerDisabled {
val watchable = Watchable.Path.from(PathRef(p))
watchedValues.append(watchable)
watchedValues0.value.append(watchable)
p
}
def evalWatch(p: os.Path): os.Path = withFilesystemCheckerDisabled {
val watchable = Watchable.Path.from(PathRef(p))
evalWatchedValues.append(watchable)
evalWatchedValues0.value.append(watchable)
p
}

def watch0(w: Watchable): Unit = watchedValues.append(w)
def watch0(w: Watchable): Unit = watchedValues0.value.append(w)

def evalWatch0(w: Watchable): Unit = evalWatchedValues.append(w)
def evalWatch0(w: Watchable): Unit = evalWatchedValues0.value.append(w)
}
Loading
Loading