Skip to content

Commit 6c2e3bb

Browse files
committed
Define dependency to scala files in using directives
1 parent bce1ca2 commit 6c2e3bb

File tree

20 files changed

+391
-60
lines changed

20 files changed

+391
-60
lines changed

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

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -186,8 +186,8 @@ object Build {
186186
buildTests: Boolean,
187187
partial: Option[Boolean]
188188
): Either[BuildException, Builds] = either {
189-
190-
val crossSources = value {
189+
// allInputs contains elements from using directives
190+
val (crossSources, allInputs) = value {
191191
CrossSources.forInputs(
192192
inputs,
193193
Sources.defaultPreprocessors(
@@ -237,7 +237,7 @@ object Build {
237237
val testOptions = testSources.buildOptions
238238

239239
val inputs0 = updateInputs(
240-
inputs,
240+
allInputs,
241241
mainOptions, // update hash in inputs with options coming from the CLI or cross-building, not from the sources
242242
Some(testOptions)
243243
)
@@ -579,9 +579,11 @@ object Build {
579579
logger
580580
))
581581

582+
var res: Either[BuildException, Builds] = null
583+
582584
def run(): Unit = {
583585
try {
584-
val res = build(
586+
res = build(
585587
inputs,
586588
options,
587589
logger,
@@ -605,8 +607,16 @@ object Build {
605607

606608
val watcher = new Watcher(ListBuffer(), threads.fileWatcher, run(), compiler.shutdown())
607609

608-
def doWatch(): Unit =
609-
for (elem <- inputs.elements) {
610+
def doWatch(): Unit = {
611+
val elements: Seq[Inputs.Element] =
612+
if (res == null) inputs.elements
613+
else
614+
res.map { builds =>
615+
val mainElems = builds.main.inputs.elements
616+
val testElems = builds.get(Scope.Test).map(_.inputs.elements).getOrElse(Nil)
617+
(mainElems ++ testElems).distinct
618+
}.getOrElse(inputs.elements)
619+
for (elem <- elements) {
610620
val depth = elem match {
611621
case _: Inputs.SingleFile => -1
612622
case _ => Int.MaxValue
@@ -637,6 +647,7 @@ object Build {
637647
}
638648
}
639649
}
650+
}
640651

641652
try doWatch()
642653
catch {

modules/build/src/main/scala/scala/build/CrossSources.scala

Lines changed: 48 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ package scala.build
22

33
import scala.build.EitherCps.{either, value}
44
import scala.build.Ops.*
5-
import scala.build.errors.{BuildException, CompositeBuildException}
5+
import scala.build.Positioned
6+
import scala.build.errors.{BuildException, CompositeBuildException, MalformedDirectiveError}
67
import scala.build.options.{
78
BuildOptions,
89
BuildRequirements,
@@ -115,14 +116,17 @@ object CrossSources {
115116
}
116117
}
117118

119+
/** @return
120+
* a CrossSources and Inputs which contains element processed from using directives
121+
*/
118122
def forInputs(
119123
inputs: Inputs,
120124
preprocessors: Seq[Preprocessor],
121125
logger: Logger
122-
): Either[BuildException, CrossSources] = either {
126+
): Either[BuildException, (CrossSources, Inputs)] = either {
123127

124-
val preprocessedSources = value {
125-
inputs.flattened()
128+
def preprocessSources(elems: Seq[Inputs.SingleElement]) =
129+
elems
126130
.map { elem =>
127131
preprocessors
128132
.iterator
@@ -135,7 +139,20 @@ object CrossSources {
135139
.sequence
136140
.left.map(CompositeBuildException(_))
137141
.map(_.flatten)
138-
}
142+
143+
val preprocessedInputFromArgs = value(preprocessSources(inputs.flattened()))
144+
145+
val sourcesFromDirectives =
146+
preprocessedInputFromArgs
147+
.flatMap(_.options)
148+
.flatMap(_.internal.extraSourceFiles)
149+
.distinct
150+
val inputsElemFromDirectives = value(resolveInputsFromSources(sourcesFromDirectives))
151+
val preprocessedSourcesFromDirectives = value(preprocessSources(inputsElemFromDirectives))
152+
val allInputs = inputs.add(inputsElemFromDirectives)
153+
154+
val preprocessedSources =
155+
(preprocessedInputFromArgs ++ preprocessedSourcesFromDirectives).distinct
139156

140157
val scopedRequirements = preprocessedSources.flatMap(_.scopedRequirements)
141158
val scopedRequirementsByRoot = scopedRequirements.groupBy(_.path.root)
@@ -151,7 +168,7 @@ object CrossSources {
151168
// If file has `using target <scope>` directive this take precendeces.
152169
if (
153170
fromDirectives.scope.isEmpty &&
154-
(path.path.last.endsWith(".test.scala") || withinTestSubDirectory(path, inputs))
171+
(path.path.last.endsWith(".test.scala") || withinTestSubDirectory(path, allInputs))
155172
)
156173
fromDirectives.copy(scope = Some(BuildRequirements.ScopeRequirement(Scope.Test)))
157174
else fromDirectives
@@ -170,7 +187,7 @@ object CrossSources {
170187
}
171188

172189
val defaultMainClassOpt = for {
173-
mainClassPath <- inputs.defaultMainClassElement.map(s => ScopePath.fromPath(s.path).path)
190+
mainClassPath <- allInputs.defaultMainClassElement.map(s => ScopePath.fromPath(s.path).path)
174191
processedMainClass <- preprocessedSources.find(_.scopePath.path == mainClassPath)
175192
mainClass <- processedMainClass.mainClassOpt
176193
} yield mainClass
@@ -180,7 +197,7 @@ object CrossSources {
180197
val baseReqs0 = baseReqs(d.scopePath)
181198
HasBuildRequirements(
182199
d.requirements.fold(baseReqs0)(_ orElse baseReqs0),
183-
(d.path, d.path.relativeTo(inputs.workspace))
200+
(d.path, d.path.relativeTo(allInputs.workspace))
184201
)
185202
}
186203
val inMemory = preprocessedSources.collect {
@@ -192,13 +209,34 @@ object CrossSources {
192209
)
193210
}
194211

195-
val resourceDirs = inputs.elements.collect {
212+
val resourceDirs = allInputs.elements.collect {
196213
case r: Inputs.ResourceDirectory =>
197214
HasBuildRequirements(BuildRequirements(), r.path)
198215
} ++ preprocessedSources.flatMap(_.options).flatMap(_.classPathOptions.resourcesDir).map(
199216
HasBuildRequirements(BuildRequirements(), _)
200217
)
201218

202-
CrossSources(paths, inMemory, defaultMainClassOpt, resourceDirs, buildOptions)
219+
(CrossSources(paths, inMemory, defaultMainClassOpt, resourceDirs, buildOptions), allInputs)
203220
}
221+
222+
private def resolveInputsFromSources(sources: Seq[Positioned[os.Path]]) =
223+
sources.map { source =>
224+
val sourcePath = source.value
225+
lazy val dir = sourcePath / os.up
226+
lazy val subPath = sourcePath.subRelativeTo(dir)
227+
if (os.isDir(sourcePath)) Right(Inputs.singleFilesFromDirectory(Inputs.Directory(sourcePath)))
228+
else if (sourcePath.ext == "scala") Right(Seq(Inputs.ScalaFile(dir, subPath)))
229+
else if (sourcePath.ext == "sc") Right(Seq(Inputs.Script(dir, subPath)))
230+
else if (sourcePath.ext == "java") Right(Seq(Inputs.JavaFile(dir, subPath)))
231+
else {
232+
val msg =
233+
if (os.exists(sourcePath))
234+
s"$sourcePath: unrecognized source type (expected .scala, .sc, .java extension or directory) in using directive."
235+
else s"$sourcePath: not found path defined in using directive."
236+
Left(new MalformedDirectiveError(msg, source.positions))
237+
}
238+
}.sequence
239+
.left.map(CompositeBuildException(_))
240+
.map(_.flatten)
241+
204242
}

modules/build/src/main/scala/scala/build/Inputs.scala

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ final case class Inputs(
3030
def singleFiles(): Seq[Inputs.SingleFile] =
3131
elements.flatMap {
3232
case f: Inputs.SingleFile => Seq(f)
33-
case d: Inputs.Directory => singleFilesFromDirectory(d)
33+
case d: Inputs.Directory => Inputs.singleFilesFromDirectory(d)
3434
case _: Inputs.ResourceDirectory => Nil
3535
case _: Inputs.Virtual => Nil
3636
}
@@ -51,7 +51,7 @@ final case class Inputs(
5151
def flattened(): Seq[Inputs.SingleElement] =
5252
elements.flatMap {
5353
case f: Inputs.SingleFile => Seq(f)
54-
case d: Inputs.Directory => singleFilesFromDirectory(d)
54+
case d: Inputs.Directory => Inputs.singleFilesFromDirectory(d)
5555
case _: Inputs.ResourceDirectory => Nil
5656
case v: Inputs.Virtual => Seq(v)
5757
}
@@ -73,7 +73,7 @@ final case class Inputs(
7373

7474
def add(extraElements: Seq[Inputs.Element]): Inputs =
7575
if (elements.isEmpty) this
76-
else copy(elements = elements ++ extraElements)
76+
else copy(elements = (elements ++ extraElements).distinct)
7777

7878
def generatedSrcRoot(scope: Scope): os.Path =
7979
workspace / Constants.workspaceDirName / projectName / "src_generated" / scope.name
@@ -109,7 +109,7 @@ final case class Inputs(
109109
case elem: Inputs.OnDisk =>
110110
val content = elem match {
111111
case dirInput: Inputs.Directory =>
112-
Seq("dir:") ++ singleFilesFromDirectory(dirInput)
112+
Seq("dir:") ++ Inputs.singleFilesFromDirectory(dirInput)
113113
.map(file => s"${file.path}:" + os.read(file.path))
114114
case resDirInput: Inputs.ResourceDirectory =>
115115
// Resource changes for SN require relinking, so they should also be hashed
@@ -129,22 +129,6 @@ final case class Inputs(
129129
String.format(s"%040x", calculatedSum)
130130
}
131131

132-
private def singleFilesFromDirectory(d: Inputs.Directory): Seq[Inputs.SingleFile] = {
133-
import Ordering.Implicits.seqOrdering
134-
os.walk.stream(d.path, skip = _.last.startsWith("."))
135-
.filter(os.isFile(_))
136-
.collect {
137-
case p if p.last.endsWith(".java") =>
138-
Inputs.JavaFile(d.path, p.subRelativeTo(d.path))
139-
case p if p.last.endsWith(".scala") =>
140-
Inputs.ScalaFile(d.path, p.subRelativeTo(d.path))
141-
case p if p.last.endsWith(".sc") =>
142-
Inputs.Script(d.path, p.subRelativeTo(d.path))
143-
}
144-
.toVector
145-
.sortBy(_.subPath.segments)
146-
}
147-
148132
def nativeWorkDir: os.Path =
149133
workspace / Constants.workspaceDirName / projectName / "native"
150134
def nativeImageWorkDir: os.Path =
@@ -222,6 +206,22 @@ object Inputs {
222206
final case class VirtualData(content: Array[Byte], source: String)
223207
extends Virtual
224208

209+
def singleFilesFromDirectory(d: Inputs.Directory): Seq[Inputs.SingleFile] = {
210+
import Ordering.Implicits.seqOrdering
211+
os.walk.stream(d.path, skip = _.last.startsWith("."))
212+
.filter(os.isFile(_))
213+
.collect {
214+
case p if p.last.endsWith(".java") =>
215+
Inputs.JavaFile(d.path, p.subRelativeTo(d.path))
216+
case p if p.last.endsWith(".scala") =>
217+
Inputs.ScalaFile(d.path, p.subRelativeTo(d.path))
218+
case p if p.last.endsWith(".sc") =>
219+
Inputs.Script(d.path, p.subRelativeTo(d.path))
220+
}
221+
.toVector
222+
.sortBy(_.subPath.segments)
223+
}
224+
225225
private def inputsHash(elements: Seq[Element]): String = {
226226
def bytes(s: String): Array[Byte] = s.getBytes(StandardCharsets.UTF_8)
227227
val it = elements.iterator.flatMap {

modules/build/src/main/scala/scala/build/bsp/BspImpl.scala

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,8 @@ final class BspImpl(
5757
val bspServer = currentBloopSession.bspServer
5858
val inputs = currentBloopSession.inputs
5959

60-
val crossSources = value {
60+
// allInputs contains elements from using directives
61+
val (crossSources, allInputs) = value {
6162
CrossSources.forInputs(
6263
inputs,
6364
Sources.defaultPreprocessors(
@@ -86,16 +87,16 @@ final class BspImpl(
8687
val options0Main = sourcesMain.buildOptions
8788
val options0Test = sourcesTest.buildOptions.orElse(options0Main)
8889

89-
val generatedSourcesMain = sourcesMain.generateSources(inputs.generatedSrcRoot(Scope.Main))
90-
val generatedSourcesTest = sourcesTest.generateSources(inputs.generatedSrcRoot(Scope.Test))
90+
val generatedSourcesMain = sourcesMain.generateSources(allInputs.generatedSrcRoot(Scope.Main))
91+
val generatedSourcesTest = sourcesTest.generateSources(allInputs.generatedSrcRoot(Scope.Test))
9192

9293
bspServer.setExtraDependencySources(options0Main.classPathOptions.extraSourceJars)
9394
bspServer.setGeneratedSources(Scope.Main, generatedSourcesMain)
9495
bspServer.setGeneratedSources(Scope.Test, generatedSourcesTest)
9596

9697
val (classesDir0Main, scalaParamsMain, artifactsMain, projectMain, buildChangedMain) = value {
9798
val res = Build.prepareBuild(
98-
inputs,
99+
allInputs,
99100
sourcesMain,
100101
generatedSourcesMain,
101102
options0Main,
@@ -110,7 +111,7 @@ final class BspImpl(
110111

111112
val (classesDir0Test, scalaParamsTest, artifactsTest, projectTest, buildChangedTest) = value {
112113
val res = Build.prepareBuild(
113-
inputs,
114+
allInputs,
114115
sourcesTest,
115116
generatedSourcesTest,
116117
options0Test,

modules/build/src/main/scala/scala/build/preprocessing/ScalaPreprocessor.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ case object ScalaPreprocessor extends Preprocessor {
5959
UsingScalaJsOptionsDirectiveHandler,
6060
UsingScalaNativeOptionsDirectiveHandler,
6161
UsingScalaVersionDirectiveHandler,
62+
UsingSourceDirectiveHandler,
6263
UsingTestFrameworkDirectiveHandler
6364
)
6465

modules/build/src/test/scala/scala/build/tests/BuildTests.scala

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -987,4 +987,46 @@ abstract class BuildTests(server: Boolean) extends munit.FunSuite {
987987
expect(coreCp.length == 1) // only classes directory, no stubs jar
988988
}
989989
}
990+
991+
test("declared sources in using directive should be included to count project hash") {
992+
val helloFile = "Hello.scala"
993+
val inputs =
994+
TestInputs(
995+
os.rel / helloFile ->
996+
"""|//> using file "Utils.scala"
997+
|
998+
|object Hello extends App {
999+
| println(Utils.hello)
1000+
|}""".stripMargin,
1001+
os.rel / "Utils.scala" ->
1002+
s"""|object Utils {
1003+
| val hello = "Hello"
1004+
|}""".stripMargin,
1005+
os.rel / "Helper.scala" ->
1006+
s"""|object Helper {
1007+
| val hello = "Hello"
1008+
|}""".stripMargin
1009+
)
1010+
inputs.withBuild(baseOptions, buildThreads, bloopConfigOpt) { (root, _, maybeBuild) =>
1011+
expect(maybeBuild.exists(_.success))
1012+
val build = maybeBuild.toOption.flatMap(_.successfulOpt).getOrElse(sys.error("cannot happen"))
1013+
1014+
// updating sources in using directive should change project name
1015+
val updatedHelloScala =
1016+
"""|//> using file "Helper.scala"
1017+
|
1018+
|object Hello extends App {
1019+
| println(Helper.hello)
1020+
|}""".stripMargin
1021+
os.write.over(root / helloFile, updatedHelloScala)
1022+
1023+
inputs.withBuild(baseOptions, buildThreads, bloopConfigOpt) { (_, _, maybeUpdatedBuild) =>
1024+
expect(maybeUpdatedBuild.exists(_.success))
1025+
val updatedBuild =
1026+
maybeUpdatedBuild.toOption.flatMap(_.successfulOpt).getOrElse(sys.error("cannot happen"))
1027+
// project name should be change after updating source in using directive
1028+
expect(build.inputs.projectName != updatedBuild.inputs.projectName)
1029+
}
1030+
}
1031+
}
9901032
}

0 commit comments

Comments
 (0)