Skip to content

Commit 5843e25

Browse files
authored
Merge pull request #1157 from lwronski/directive-source-file
Define dependency to scala files in using directives
2 parents 9132c1d + 6c2e3bb commit 5843e25

File tree

24 files changed

+437
-86
lines changed

24 files changed

+437
-86
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: 26 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import java.security.MessageDigest
77

88
import scala.annotation.tailrec
99
import scala.build.Inputs.WorkspaceOrigin
10+
import scala.build.errors.{BuildException, InputsException}
1011
import scala.build.internal.Constants
1112
import scala.build.internal.zip.WrappedZipInputStream
1213
import scala.build.options.Scope
@@ -29,7 +30,7 @@ final case class Inputs(
2930
def singleFiles(): Seq[Inputs.SingleFile] =
3031
elements.flatMap {
3132
case f: Inputs.SingleFile => Seq(f)
32-
case d: Inputs.Directory => singleFilesFromDirectory(d)
33+
case d: Inputs.Directory => Inputs.singleFilesFromDirectory(d)
3334
case _: Inputs.ResourceDirectory => Nil
3435
case _: Inputs.Virtual => Nil
3536
}
@@ -50,7 +51,7 @@ final case class Inputs(
5051
def flattened(): Seq[Inputs.SingleElement] =
5152
elements.flatMap {
5253
case f: Inputs.SingleFile => Seq(f)
53-
case d: Inputs.Directory => singleFilesFromDirectory(d)
54+
case d: Inputs.Directory => Inputs.singleFilesFromDirectory(d)
5455
case _: Inputs.ResourceDirectory => Nil
5556
case v: Inputs.Virtual => Seq(v)
5657
}
@@ -72,7 +73,7 @@ final case class Inputs(
7273

7374
def add(extraElements: Seq[Inputs.Element]): Inputs =
7475
if (elements.isEmpty) this
75-
else copy(elements = elements ++ extraElements)
76+
else copy(elements = (elements ++ extraElements).distinct)
7677

7778
def generatedSrcRoot(scope: Scope): os.Path =
7879
workspace / Constants.workspaceDirName / projectName / "src_generated" / scope.name
@@ -108,7 +109,7 @@ final case class Inputs(
108109
case elem: Inputs.OnDisk =>
109110
val content = elem match {
110111
case dirInput: Inputs.Directory =>
111-
Seq("dir:") ++ singleFilesFromDirectory(dirInput)
112+
Seq("dir:") ++ Inputs.singleFilesFromDirectory(dirInput)
112113
.map(file => s"${file.path}:" + os.read(file.path))
113114
case resDirInput: Inputs.ResourceDirectory =>
114115
// Resource changes for SN require relinking, so they should also be hashed
@@ -128,22 +129,6 @@ final case class Inputs(
128129
String.format(s"%040x", calculatedSum)
129130
}
130131

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

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+
224225
private def inputsHash(elements: Seq[Element]): String = {
225226
def bytes(s: String): Array[Byte] = s.getBytes(StandardCharsets.UTF_8)
226227
val it = elements.iterator.flatMap {
@@ -414,7 +415,7 @@ object Inputs {
414415
javaSnippetOpt: Option[String],
415416
acceptFds: Boolean,
416417
forcedWorkspace: Option[os.Path]
417-
): Either[String, Inputs] = {
418+
): Either[BuildException, Inputs] = {
418419
val validatedArgs: Seq[Either[String, Seq[Element]]] =
419420
validateArgs(args, cwd, download, stdinOpt, acceptFds)
420421
val validatedExpressions: Seq[Either[String, Seq[Element]]] =
@@ -432,7 +433,7 @@ object Inputs {
432433
Right(forValidatedElems(validElems, baseProjectName, directories, forcedWorkspace))
433434
}
434435
else
435-
Left(invalid.mkString(System.lineSeparator()))
436+
Left(new InputsException(invalid.mkString(System.lineSeparator())))
436437
}
437438

438439
def apply(
@@ -448,13 +449,13 @@ object Inputs {
448449
javaSnippetOpt: Option[String] = None,
449450
acceptFds: Boolean = false,
450451
forcedWorkspace: Option[os.Path] = None
451-
): Either[String, Inputs] =
452+
): Either[BuildException, Inputs] =
452453
if (
453454
args.isEmpty && scriptSnippetOpt.isEmpty && scalaSnippetOpt.isEmpty && javaSnippetOpt.isEmpty
454455
)
455-
defaultInputs().toRight(
456+
defaultInputs().toRight(new InputsException(
456457
"No inputs provided (expected files with .scala or .sc extensions, and / or directories)."
457-
)
458+
))
458459
else
459460
forNonEmptyArgs(
460461
args,

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package scala.build.bsp
33
import java.io.{InputStream, OutputStream}
44

55
import scala.build.Inputs
6+
import scala.build.errors.BuildException
67
import scala.concurrent.Future
78

89
trait Bsp {
@@ -12,7 +13,7 @@ trait Bsp {
1213

1314
object Bsp {
1415
def create(
15-
argsToInputs: Seq[String] => Either[String, Inputs],
16+
argsToInputs: Seq[String] => Either[BuildException, Inputs],
1617
bspReloadableOptionsReference: BspReloadableOptions.Reference,
1718
threads: BspThreads,
1819
in: InputStream,

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

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import scala.build.EitherCps.{either, value}
1313
import scala.build._
1414
import scala.build.bloop.{BloopServer, ScalaDebugServer}
1515
import scala.build.compiler.BloopCompiler
16-
import scala.build.errors.{BuildException, Diagnostic}
16+
import scala.build.errors.{BuildException, Diagnostic, ParsingInputsException}
1717
import scala.build.internal.{Constants, CustomCodeWrapper}
1818
import scala.build.options.{BuildOptions, Scope}
1919
import scala.collection.mutable.ListBuffer
@@ -23,7 +23,7 @@ import scala.jdk.CollectionConverters._
2323
import scala.util.{Failure, Success}
2424

2525
final class BspImpl(
26-
argsToInputs: Seq[String] => Either[String, Inputs],
26+
argsToInputs: Seq[String] => Either[BuildException, Inputs],
2727
bspReloadableOptionsReference: BspReloadableOptions.Reference,
2828
threads: BspThreads,
2929
in: InputStream,
@@ -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,
@@ -459,13 +460,13 @@ final class BspImpl(
459460
val ideInputsJsonPath =
460461
currentBloopSession.inputs.workspace / Constants.workspaceDirName / "ide-inputs.json"
461462
if (os.isFile(ideInputsJsonPath)) {
462-
val maybeResponse = either[String] {
463+
val maybeResponse = either[BuildException] {
463464
val ideInputs = value {
464465
try Right(readFromArray(os.read.bytes(ideInputsJsonPath))(IdeInputs.codec))
465466
catch {
466467
case e: JsonReaderException =>
467468
logger.debug(s"Caught $e while decoding $ideInputsJsonPath")
468-
Left(e.getMessage)
469+
Left(new ParsingInputsException(e.getMessage, e))
469470
}
470471
}
471472
val newInputs = value(argsToInputs(ideInputs.args))

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

0 commit comments

Comments
 (0)