Skip to content

Commit 3d399b5

Browse files
Merge pull request #1727 from alexarchambault/watch-artifacts-for-changes
Watch artifacts for changes
2 parents 24036ed + 4206b76 commit 3d399b5

File tree

4 files changed

+110
-15
lines changed

4 files changed

+110
-15
lines changed

modules/build/src/main/scala/scala/build/Build.scala

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -675,11 +675,13 @@ object Build {
675675
val elements: Seq[Element] =
676676
if (res == null) inputs.elements
677677
else
678-
res.map { builds =>
679-
val mainElems = builds.main.inputs.elements
680-
val testElems = builds.get(Scope.Test).map(_.inputs.elements).getOrElse(Nil)
681-
(mainElems ++ testElems).distinct
682-
}.getOrElse(inputs.elements)
678+
res
679+
.map { builds =>
680+
val mainElems = builds.main.inputs.elements
681+
val testElems = builds.get(Scope.Test).map(_.inputs.elements).getOrElse(Nil)
682+
(mainElems ++ testElems).distinct
683+
}
684+
.getOrElse(inputs.elements)
683685
for (elem <- elements) {
684686
val depth = elem match {
685687
case _: SingleFile => -1
@@ -711,6 +713,26 @@ object Build {
711713
}
712714
}
713715
}
716+
717+
val artifacts = res
718+
.map { builds =>
719+
def artifacts(build: Build): Seq[os.Path] =
720+
build.successfulOpt.toSeq.flatMap(_.artifacts.classPath)
721+
val main = artifacts(builds.main)
722+
val test = builds.get(Scope.Test).map(artifacts).getOrElse(Nil)
723+
(main ++ test).distinct
724+
}
725+
.getOrElse(Nil)
726+
for (artifact <- artifacts) {
727+
val depth = if (os.isFile(artifact)) -1 else Int.MaxValue
728+
val watcher0 = watcher.newWatcher()
729+
watcher0.register(artifact.toNIO, depth)
730+
watcher0.addObserver {
731+
onChangeBufferedObserver { _ =>
732+
watcher.schedule()
733+
}
734+
}
735+
}
714736
}
715737

716738
try doWatch()

modules/integration/src/test/scala/scala/cli/integration/BloopTests.scala

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ package scala.cli.integration
33
import com.eed3si9n.expecty.Expecty.expect
44

55
import scala.cli.integration.util.BloopUtil
6+
import scala.concurrent.ExecutionContext
67
import scala.concurrent.duration.Duration
7-
import scala.concurrent.{Await, ExecutionContext, Future}
88

99
class BloopTests extends ScalaCliSuite {
1010

@@ -144,13 +144,8 @@ class BloopTests extends ScalaCliSuite {
144144
test("Restart Bloop server while watching") {
145145
TestUtil.withThreadPool("bloop-restart-test", 2) { pool =>
146146
val timeout = Duration("20 seconds")
147-
def readLine(stream: os.SubProcess.OutputStream): String = {
148-
implicit val ec = ExecutionContext.fromExecutorService(pool)
149-
val readLineF = Future {
150-
stream.readLine()
151-
}
152-
Await.result(readLineF, timeout)
153-
}
147+
val ec = ExecutionContext.fromExecutorService(pool)
148+
154149
def content(message: String) =
155150
s"""object Hello {
156151
| def main(args: Array[String]): Unit =
@@ -164,14 +159,14 @@ class BloopTests extends ScalaCliSuite {
164159
inputs.fromRoot { root =>
165160
val proc = os.proc(TestUtil.cli, "run", "-w", ".")
166161
.spawn(cwd = root)
167-
val firstLine = readLine(proc.stdout)
162+
val firstLine = TestUtil.readLine(proc.stdout, ec, timeout)
168163
expect(firstLine == "Hello")
169164

170165
os.proc(TestUtil.cli, "bloop", "exit")
171166
.call(cwd = root)
172167

173168
os.write.over(root / sourcePath, content("Foo"))
174-
val secondLine = readLine(proc.stdout)
169+
val secondLine = TestUtil.readLine(proc.stdout, ec, timeout)
175170
expect(secondLine == "Foo")
176171

177172
proc.destroy()

modules/integration/src/test/scala/scala/cli/integration/RunTestsDefault.scala

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ package scala.cli.integration
22

33
import com.eed3si9n.expecty.Expecty.expect
44

5+
import scala.concurrent.ExecutionContext
6+
import scala.concurrent.duration.Duration
57
import scala.util.Properties
68

79
class RunTestsDefault extends RunTestDefinitions(scalaVersionOpt = None) {
@@ -64,4 +66,67 @@ class RunTestsDefault extends RunTestDefinitions(scalaVersionOpt = None) {
6466
}
6567
}
6668

69+
test("watch artifacts") {
70+
val libSourcePath = os.rel / "lib" / "Messages.scala"
71+
def libSource(hello: String) =
72+
s"""//> using publish.organization "test-org"
73+
|//> using publish.name "messages"
74+
|//> using publish.version "0.1.0"
75+
|
76+
|package messages
77+
|
78+
|object Messages {
79+
| def hello(name: String) = s"$hello $$name"
80+
|}
81+
|""".stripMargin
82+
val inputs = TestInputs(
83+
libSourcePath -> libSource("Hello"),
84+
os.rel / "app" / "TestApp.scala" ->
85+
"""//> using lib "test-org::messages:0.1.0"
86+
|
87+
|package testapp
88+
|
89+
|import messages.Messages
90+
|
91+
|@main
92+
|def run(): Unit =
93+
| println(Messages.hello("user"))
94+
|""".stripMargin
95+
)
96+
inputs.fromRoot { root =>
97+
val testRepo = root / "test-repo"
98+
99+
def publishLib(): Unit =
100+
os.proc(TestUtil.cli, "publish", "--publish-repo", testRepo, "lib")
101+
.call(cwd = root)
102+
103+
publishLib()
104+
105+
val proc = os.proc(TestUtil.cli, "run", "app", "-w", "-r", testRepo.toNIO.toUri.toASCIIString)
106+
.spawn(cwd = root)
107+
108+
try
109+
TestUtil.withThreadPool("watch-artifacts-test", 2) { pool =>
110+
val timeout = Duration("20 seconds")
111+
val ec = ExecutionContext.fromExecutorService(pool)
112+
113+
val output = TestUtil.readLine(proc.stdout, ec, timeout)
114+
expect(output == "Hello user")
115+
116+
os.write.over(root / libSourcePath, libSource("Hola"))
117+
publishLib()
118+
119+
val secondOutput = TestUtil.readLine(proc.stdout, ec, timeout)
120+
expect(secondOutput == "Hola user")
121+
}
122+
finally
123+
if (proc.isAlive()) {
124+
proc.destroy()
125+
Thread.sleep(200L)
126+
if (proc.isAlive())
127+
proc.destroyForcibly()
128+
}
129+
}
130+
}
131+
67132
}

modules/integration/src/test/scala/scala/cli/integration/TestUtil.scala

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import java.util.concurrent.{ExecutorService, Executors, ScheduledExecutorServic
88

99
import scala.annotation.tailrec
1010
import scala.concurrent.duration.{Duration, DurationInt, FiniteDuration}
11+
import scala.concurrent.{Await, ExecutionContext, Future}
1112
import scala.util.Properties
1213

1314
object TestUtil {
@@ -259,4 +260,16 @@ object TestUtil {
259260
}
260261
Map(pathVarName -> s"$binDir${File.pathSeparator}$currentPath")
261262
}
263+
264+
def readLine(
265+
stream: os.SubProcess.OutputStream,
266+
ec: ExecutionContext,
267+
timeout: Duration
268+
): String = {
269+
implicit val ec0 = ec
270+
val readLineF = Future {
271+
stream.readLine()
272+
}
273+
Await.result(readLineF, timeout)
274+
}
262275
}

0 commit comments

Comments
 (0)