Skip to content

Commit

Permalink
Define dependency to scala files in using directives
Browse files Browse the repository at this point in the history
  • Loading branch information
lwronski committed Jul 13, 2022
1 parent bce1ca2 commit 6c2e3bb
Show file tree
Hide file tree
Showing 20 changed files with 391 additions and 60 deletions.
23 changes: 17 additions & 6 deletions modules/build/src/main/scala/scala/build/Build.scala
Original file line number Diff line number Diff line change
Expand Up @@ -186,8 +186,8 @@ object Build {
buildTests: Boolean,
partial: Option[Boolean]
): Either[BuildException, Builds] = either {

val crossSources = value {
// allInputs contains elements from using directives
val (crossSources, allInputs) = value {
CrossSources.forInputs(
inputs,
Sources.defaultPreprocessors(
Expand Down Expand Up @@ -237,7 +237,7 @@ object Build {
val testOptions = testSources.buildOptions

val inputs0 = updateInputs(
inputs,
allInputs,
mainOptions, // update hash in inputs with options coming from the CLI or cross-building, not from the sources
Some(testOptions)
)
Expand Down Expand Up @@ -579,9 +579,11 @@ object Build {
logger
))

var res: Either[BuildException, Builds] = null

def run(): Unit = {
try {
val res = build(
res = build(
inputs,
options,
logger,
Expand All @@ -605,8 +607,16 @@ object Build {

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

def doWatch(): Unit =
for (elem <- inputs.elements) {
def doWatch(): Unit = {
val elements: Seq[Inputs.Element] =
if (res == null) inputs.elements
else
res.map { builds =>
val mainElems = builds.main.inputs.elements
val testElems = builds.get(Scope.Test).map(_.inputs.elements).getOrElse(Nil)
(mainElems ++ testElems).distinct
}.getOrElse(inputs.elements)
for (elem <- elements) {
val depth = elem match {
case _: Inputs.SingleFile => -1
case _ => Int.MaxValue
Expand Down Expand Up @@ -637,6 +647,7 @@ object Build {
}
}
}
}

try doWatch()
catch {
Expand Down
58 changes: 48 additions & 10 deletions modules/build/src/main/scala/scala/build/CrossSources.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ package scala.build

import scala.build.EitherCps.{either, value}
import scala.build.Ops.*
import scala.build.errors.{BuildException, CompositeBuildException}
import scala.build.Positioned
import scala.build.errors.{BuildException, CompositeBuildException, MalformedDirectiveError}
import scala.build.options.{
BuildOptions,
BuildRequirements,
Expand Down Expand Up @@ -115,14 +116,17 @@ object CrossSources {
}
}

/** @return
* a CrossSources and Inputs which contains element processed from using directives
*/
def forInputs(
inputs: Inputs,
preprocessors: Seq[Preprocessor],
logger: Logger
): Either[BuildException, CrossSources] = either {
): Either[BuildException, (CrossSources, Inputs)] = either {

val preprocessedSources = value {
inputs.flattened()
def preprocessSources(elems: Seq[Inputs.SingleElement]) =
elems
.map { elem =>
preprocessors
.iterator
Expand All @@ -135,7 +139,20 @@ object CrossSources {
.sequence
.left.map(CompositeBuildException(_))
.map(_.flatten)
}

val preprocessedInputFromArgs = value(preprocessSources(inputs.flattened()))

val sourcesFromDirectives =
preprocessedInputFromArgs
.flatMap(_.options)
.flatMap(_.internal.extraSourceFiles)
.distinct
val inputsElemFromDirectives = value(resolveInputsFromSources(sourcesFromDirectives))
val preprocessedSourcesFromDirectives = value(preprocessSources(inputsElemFromDirectives))
val allInputs = inputs.add(inputsElemFromDirectives)

val preprocessedSources =
(preprocessedInputFromArgs ++ preprocessedSourcesFromDirectives).distinct

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

val defaultMainClassOpt = for {
mainClassPath <- inputs.defaultMainClassElement.map(s => ScopePath.fromPath(s.path).path)
mainClassPath <- allInputs.defaultMainClassElement.map(s => ScopePath.fromPath(s.path).path)
processedMainClass <- preprocessedSources.find(_.scopePath.path == mainClassPath)
mainClass <- processedMainClass.mainClassOpt
} yield mainClass
Expand All @@ -180,7 +197,7 @@ object CrossSources {
val baseReqs0 = baseReqs(d.scopePath)
HasBuildRequirements(
d.requirements.fold(baseReqs0)(_ orElse baseReqs0),
(d.path, d.path.relativeTo(inputs.workspace))
(d.path, d.path.relativeTo(allInputs.workspace))
)
}
val inMemory = preprocessedSources.collect {
Expand All @@ -192,13 +209,34 @@ object CrossSources {
)
}

val resourceDirs = inputs.elements.collect {
val resourceDirs = allInputs.elements.collect {
case r: Inputs.ResourceDirectory =>
HasBuildRequirements(BuildRequirements(), r.path)
} ++ preprocessedSources.flatMap(_.options).flatMap(_.classPathOptions.resourcesDir).map(
HasBuildRequirements(BuildRequirements(), _)
)

CrossSources(paths, inMemory, defaultMainClassOpt, resourceDirs, buildOptions)
(CrossSources(paths, inMemory, defaultMainClassOpt, resourceDirs, buildOptions), allInputs)
}

private def resolveInputsFromSources(sources: Seq[Positioned[os.Path]]) =
sources.map { source =>
val sourcePath = source.value
lazy val dir = sourcePath / os.up
lazy val subPath = sourcePath.subRelativeTo(dir)
if (os.isDir(sourcePath)) Right(Inputs.singleFilesFromDirectory(Inputs.Directory(sourcePath)))
else if (sourcePath.ext == "scala") Right(Seq(Inputs.ScalaFile(dir, subPath)))
else if (sourcePath.ext == "sc") Right(Seq(Inputs.Script(dir, subPath)))
else if (sourcePath.ext == "java") Right(Seq(Inputs.JavaFile(dir, subPath)))
else {
val msg =
if (os.exists(sourcePath))
s"$sourcePath: unrecognized source type (expected .scala, .sc, .java extension or directory) in using directive."
else s"$sourcePath: not found path defined in using directive."
Left(new MalformedDirectiveError(msg, source.positions))
}
}.sequence
.left.map(CompositeBuildException(_))
.map(_.flatten)

}
40 changes: 20 additions & 20 deletions modules/build/src/main/scala/scala/build/Inputs.scala
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ final case class Inputs(
def singleFiles(): Seq[Inputs.SingleFile] =
elements.flatMap {
case f: Inputs.SingleFile => Seq(f)
case d: Inputs.Directory => singleFilesFromDirectory(d)
case d: Inputs.Directory => Inputs.singleFilesFromDirectory(d)
case _: Inputs.ResourceDirectory => Nil
case _: Inputs.Virtual => Nil
}
Expand All @@ -51,7 +51,7 @@ final case class Inputs(
def flattened(): Seq[Inputs.SingleElement] =
elements.flatMap {
case f: Inputs.SingleFile => Seq(f)
case d: Inputs.Directory => singleFilesFromDirectory(d)
case d: Inputs.Directory => Inputs.singleFilesFromDirectory(d)
case _: Inputs.ResourceDirectory => Nil
case v: Inputs.Virtual => Seq(v)
}
Expand All @@ -73,7 +73,7 @@ final case class Inputs(

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

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

private def singleFilesFromDirectory(d: Inputs.Directory): Seq[Inputs.SingleFile] = {
import Ordering.Implicits.seqOrdering
os.walk.stream(d.path, skip = _.last.startsWith("."))
.filter(os.isFile(_))
.collect {
case p if p.last.endsWith(".java") =>
Inputs.JavaFile(d.path, p.subRelativeTo(d.path))
case p if p.last.endsWith(".scala") =>
Inputs.ScalaFile(d.path, p.subRelativeTo(d.path))
case p if p.last.endsWith(".sc") =>
Inputs.Script(d.path, p.subRelativeTo(d.path))
}
.toVector
.sortBy(_.subPath.segments)
}

def nativeWorkDir: os.Path =
workspace / Constants.workspaceDirName / projectName / "native"
def nativeImageWorkDir: os.Path =
Expand Down Expand Up @@ -222,6 +206,22 @@ object Inputs {
final case class VirtualData(content: Array[Byte], source: String)
extends Virtual

def singleFilesFromDirectory(d: Inputs.Directory): Seq[Inputs.SingleFile] = {
import Ordering.Implicits.seqOrdering
os.walk.stream(d.path, skip = _.last.startsWith("."))
.filter(os.isFile(_))
.collect {
case p if p.last.endsWith(".java") =>
Inputs.JavaFile(d.path, p.subRelativeTo(d.path))
case p if p.last.endsWith(".scala") =>
Inputs.ScalaFile(d.path, p.subRelativeTo(d.path))
case p if p.last.endsWith(".sc") =>
Inputs.Script(d.path, p.subRelativeTo(d.path))
}
.toVector
.sortBy(_.subPath.segments)
}

private def inputsHash(elements: Seq[Element]): String = {
def bytes(s: String): Array[Byte] = s.getBytes(StandardCharsets.UTF_8)
val it = elements.iterator.flatMap {
Expand Down
11 changes: 6 additions & 5 deletions modules/build/src/main/scala/scala/build/bsp/BspImpl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@ final class BspImpl(
val bspServer = currentBloopSession.bspServer
val inputs = currentBloopSession.inputs

val crossSources = value {
// allInputs contains elements from using directives
val (crossSources, allInputs) = value {
CrossSources.forInputs(
inputs,
Sources.defaultPreprocessors(
Expand Down Expand Up @@ -86,16 +87,16 @@ final class BspImpl(
val options0Main = sourcesMain.buildOptions
val options0Test = sourcesTest.buildOptions.orElse(options0Main)

val generatedSourcesMain = sourcesMain.generateSources(inputs.generatedSrcRoot(Scope.Main))
val generatedSourcesTest = sourcesTest.generateSources(inputs.generatedSrcRoot(Scope.Test))
val generatedSourcesMain = sourcesMain.generateSources(allInputs.generatedSrcRoot(Scope.Main))
val generatedSourcesTest = sourcesTest.generateSources(allInputs.generatedSrcRoot(Scope.Test))

bspServer.setExtraDependencySources(options0Main.classPathOptions.extraSourceJars)
bspServer.setGeneratedSources(Scope.Main, generatedSourcesMain)
bspServer.setGeneratedSources(Scope.Test, generatedSourcesTest)

val (classesDir0Main, scalaParamsMain, artifactsMain, projectMain, buildChangedMain) = value {
val res = Build.prepareBuild(
inputs,
allInputs,
sourcesMain,
generatedSourcesMain,
options0Main,
Expand All @@ -110,7 +111,7 @@ final class BspImpl(

val (classesDir0Test, scalaParamsTest, artifactsTest, projectTest, buildChangedTest) = value {
val res = Build.prepareBuild(
inputs,
allInputs,
sourcesTest,
generatedSourcesTest,
options0Test,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ case object ScalaPreprocessor extends Preprocessor {
UsingScalaJsOptionsDirectiveHandler,
UsingScalaNativeOptionsDirectiveHandler,
UsingScalaVersionDirectiveHandler,
UsingSourceDirectiveHandler,
UsingTestFrameworkDirectiveHandler
)

Expand Down
42 changes: 42 additions & 0 deletions modules/build/src/test/scala/scala/build/tests/BuildTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -987,4 +987,46 @@ abstract class BuildTests(server: Boolean) extends munit.FunSuite {
expect(coreCp.length == 1) // only classes directory, no stubs jar
}
}

test("declared sources in using directive should be included to count project hash") {
val helloFile = "Hello.scala"
val inputs =
TestInputs(
os.rel / helloFile ->
"""|//> using file "Utils.scala"
|
|object Hello extends App {
| println(Utils.hello)
|}""".stripMargin,
os.rel / "Utils.scala" ->
s"""|object Utils {
| val hello = "Hello"
|}""".stripMargin,
os.rel / "Helper.scala" ->
s"""|object Helper {
| val hello = "Hello"
|}""".stripMargin
)
inputs.withBuild(baseOptions, buildThreads, bloopConfigOpt) { (root, _, maybeBuild) =>
expect(maybeBuild.exists(_.success))
val build = maybeBuild.toOption.flatMap(_.successfulOpt).getOrElse(sys.error("cannot happen"))

// updating sources in using directive should change project name
val updatedHelloScala =
"""|//> using file "Helper.scala"
|
|object Hello extends App {
| println(Helper.hello)
|}""".stripMargin
os.write.over(root / helloFile, updatedHelloScala)

inputs.withBuild(baseOptions, buildThreads, bloopConfigOpt) { (_, _, maybeUpdatedBuild) =>
expect(maybeUpdatedBuild.exists(_.success))
val updatedBuild =
maybeUpdatedBuild.toOption.flatMap(_.successfulOpt).getOrElse(sys.error("cannot happen"))
// project name should be change after updating source in using directive
expect(build.inputs.projectName != updatedBuild.inputs.projectName)
}
}
}
}
Loading

0 comments on commit 6c2e3bb

Please sign in to comment.