Skip to content

Introduce BuildInfo #2249

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jul 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 1 addition & 3 deletions modules/build/src/main/scala/scala/build/Build.scala
Original file line number Diff line number Diff line change
Expand Up @@ -267,9 +267,7 @@ object Build {

val baseOptions = overrideOptions.orElse(sharedOptions)

val wrappedScriptsSources = crossSources.withWrappedScripts(baseOptions)

val scopedSources = value(wrappedScriptsSources.scopedSources(baseOptions))
val scopedSources = value(crossSources.scopedSources(baseOptions))

val mainSources = scopedSources.sources(Scope.Main, baseOptions)
val mainOptions = mainSources.buildOptions
Expand Down
75 changes: 13 additions & 62 deletions modules/build/src/main/scala/scala/build/CrossSources.scala
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,8 @@ import scala.build.testrunner.DynamicTestRunner.globPattern
import scala.util.Try
import scala.util.chaining.*

/** CrossSources with unwrapped scripts, use [[withWrappedScripts]] to wrap them and obtain an
* instance of CrossSources
*
* See [[CrossSources]] for more information
/** Information gathered from preprocessing command inputs - sources (including unwrapped scripts)
* and build options from using directives
*
* @param paths
* paths and realtive paths to sources on disk, wrapped in their build requirements
Expand All @@ -45,79 +43,26 @@ import scala.util.chaining.*
* @param unwrappedScripts
* in memory script sources, their code must be wrapped before compiling
*/
sealed class UnwrappedCrossSources(
final case class CrossSources(
paths: Seq[WithBuildRequirements[(os.Path, os.RelPath)]],
inMemory: Seq[WithBuildRequirements[Sources.InMemory]],
defaultMainClass: Option[String],
resourceDirs: Seq[WithBuildRequirements[os.Path]],
buildOptions: Seq[WithBuildRequirements[BuildOptions]],
unwrappedScripts: Seq[WithBuildRequirements[Sources.UnwrappedScript]]
) {

/** For all unwrapped script sources contained in this object wrap them according to provided
* BuildOptions
*
* @param buildOptions
* options used to choose the script wrapper
* @return
* CrossSources with all the scripts wrapped
*/
def withWrappedScripts(buildOptions: BuildOptions): CrossSources = {
val sharedOptions = this.sharedOptions(buildOptions)
val codeWrapper = ScriptPreprocessor.getScriptWrapper(sharedOptions)

val wrappedScripts = unwrappedScripts.map { unwrapppedWithRequirements =>
unwrapppedWithRequirements.map(_.wrap(codeWrapper))
}

CrossSources(
paths,
inMemory ++ wrappedScripts,
defaultMainClass,
resourceDirs,
this.buildOptions
)
}

def sharedOptions(baseOptions: BuildOptions): BuildOptions =
buildOptions
.filter(_.requirements.isEmpty)
.map(_.value)
.foldLeft(baseOptions)(_ orElse _)

protected def needsScalaVersion =
private def needsScalaVersion =
paths.exists(_.needsScalaVersion) ||
inMemory.exists(_.needsScalaVersion) ||
resourceDirs.exists(_.needsScalaVersion) ||
buildOptions.exists(_.needsScalaVersion)
}

/** Information gathered from preprocessing command inputs - sources and build options from using
* directives
*
* @param paths
* paths and realtive paths to sources on disk, wrapped in their build requirements
* @param inMemory
* in memory sources (e.g. snippets and wrapped scripts) wrapped in their build requirements
* @param defaultMainClass
* @param resourceDirs
* @param buildOptions
* build options from sources
*/
final case class CrossSources(
paths: Seq[WithBuildRequirements[(os.Path, os.RelPath)]],
inMemory: Seq[WithBuildRequirements[Sources.InMemory]],
defaultMainClass: Option[String],
resourceDirs: Seq[WithBuildRequirements[os.Path]],
buildOptions: Seq[WithBuildRequirements[BuildOptions]]
) extends UnwrappedCrossSources(
paths,
inMemory,
defaultMainClass,
resourceDirs,
buildOptions,
Nil
) {
def scopedSources(baseOptions: BuildOptions): Either[BuildException, ScopedSources] = either {

val sharedOptions0 = sharedOptions(baseOptions)
Expand Down Expand Up @@ -154,6 +99,9 @@ final case class CrossSources(
.flatMap(_.withPlatform(platform.value).toSeq),
buildOptions = buildOptions
.filter(!_.requirements.isEmpty)
.flatMap(_.withScalaVersion(retainedScalaVersion).toSeq)
.flatMap(_.withPlatform(platform.value).toSeq),
unwrappedScripts = unwrappedScripts
.flatMap(_.withScalaVersion(retainedScalaVersion).toSeq)
.flatMap(_.withPlatform(platform.value).toSeq)
)
Expand All @@ -171,6 +119,8 @@ final case class CrossSources(
.flatMap(_.withPlatform(platform.value).toSeq),
buildOptions = buildOptions
.filter(!_.requirements.isEmpty)
.flatMap(_.withPlatform(platform.value).toSeq),
unwrappedScripts = unwrappedScripts
.flatMap(_.withPlatform(platform.value).toSeq)
)
}
Expand All @@ -181,7 +131,8 @@ final case class CrossSources(
crossSources0.inMemory.map(_.scopedValue(defaultScope)),
defaultMainClass,
crossSources0.resourceDirs.map(_.scopedValue(defaultScope)),
crossSources0.buildOptions.map(_.scopedValue(defaultScope))
crossSources0.buildOptions.map(_.scopedValue(defaultScope)),
crossSources0.unwrappedScripts.map(_.scopedValue(defaultScope))
)
}
}
Expand Down Expand Up @@ -210,7 +161,7 @@ object CrossSources {
suppressWarningOptions: SuppressWarningOptions,
exclude: Seq[Positioned[String]] = Nil,
maybeRecoverOnError: BuildException => Option[BuildException] = e => Some(e)
)(using ScalaCliInvokeData): Either[BuildException, (UnwrappedCrossSources, Inputs)] = either {
)(using ScalaCliInvokeData): Either[BuildException, (CrossSources, Inputs)] = either {

def preprocessSources(elems: Seq[SingleElement])
: Either[BuildException, Seq[PreprocessedSource]] =
Expand Down Expand Up @@ -401,7 +352,7 @@ object CrossSources {
val inMemory = inMemoryWithDirectivePositions.map(_._1)
val unwrappedScripts = unwrappedScriptsWithDirectivePositions.map(_._1)
(
UnwrappedCrossSources(
CrossSources(
paths,
inMemory,
defaultMainClassOpt,
Expand Down
95 changes: 91 additions & 4 deletions modules/build/src/main/scala/scala/build/ScopedSources.scala
Original file line number Diff line number Diff line change
@@ -1,13 +1,33 @@
package scala.build

import scala.build.info.{BuildInfo, ScopedBuildInfo}
import scala.build.options.{BuildOptions, HasScope, Scope}
import scala.build.preprocessing.ScriptPreprocessor

/** Information gathered from preprocessing command inputs - sources (including unwrapped scripts)
* and build options from using directives. Only scope requirements remain in this object after
* resolving them in [[CrossSources.scopedSources]]
*
* @param paths
* paths and relative paths to sources on disk with the scope they belong to
* @param inMemory
* in memory sources (e.g. snippets) with the scope they belong to
* @param defaultMainClass
* @param resourceDirs
* @param buildOptions
* build options sources with the scope they belong to
* @param unwrappedScripts
* in memory script sources with the scope they belong to, their code must be wrapped before
* compiling
*/

final case class ScopedSources(
paths: Seq[HasScope[(os.Path, os.RelPath)]],
inMemory: Seq[HasScope[Sources.InMemory]],
defaultMainClass: Option[String],
resourceDirs: Seq[HasScope[os.Path]],
buildOptions: Seq[HasScope[BuildOptions]]
buildOptions: Seq[HasScope[BuildOptions]],
unwrappedScripts: Seq[HasScope[Sources.UnwrappedScript]]
) {
def buildOptionsFor(scope: Scope): Seq[BuildOptions] =
scope match {
Expand All @@ -16,13 +36,80 @@ final case class ScopedSources(
case _ => buildOptions.flatMap(_.valueFor(scope).toSeq)
}

/** Resolve scope requirements and create a Sources instance
* @param scope
* scope to be resolved
* @param baseOptions
* options that have already been collected for this build, they should consist of:
* - options from the console
* - options from using directives from the sources
* - options from resolved using directives that had Scala version and platform requirements
* that fit the current build
* @return
* [[Sources]] instance that belong to specified scope
*/
def sources(scope: Scope, baseOptions: BuildOptions): Sources =
val combinedOptions = combinedBuildOptions(scope, baseOptions)

val codeWrapper = ScriptPreprocessor.getScriptWrapper(combinedOptions)

val wrappedScripts = unwrappedScripts
.flatMap(_.valueFor(scope).toSeq)
.map(_.wrap(codeWrapper))

val needsBuildInfo = combinedOptions.sourceGeneratorOptions.useBuildInfo.getOrElse(false)

val maybeBuildInfoSource = if (needsBuildInfo && scope == Scope.Main)
Seq(Sources.InMemory(
Left("build-info"),
os.rel / "BuildInfo.scala",
buildInfo(combinedOptions).generateContents(),
None
))
else Nil

Sources(
paths.flatMap(_.valueFor(scope).toSeq),
inMemory.flatMap(_.valueFor(scope).toSeq),
inMemory.flatMap(_.valueFor(scope).toSeq) ++ wrappedScripts ++ maybeBuildInfoSource,
defaultMainClass,
resourceDirs.flatMap(_.valueFor(scope).toSeq),
buildOptionsFor(scope)
.foldRight(baseOptions)(_ orElse _)
combinedOptions
)

/** Combine build options that had no requirements (console and using directives) or their
* requirements have been resolved (e.g. target using directives) with build options that require
* the specified scope
*
* @param scope
* scope to be resolved
* @param baseOptions
* options that have already been collected for this build (had no requirements or they have
* been resolved)
* @return
* Combined BuildOptions, baseOptions' values take precedence
*/
def combinedBuildOptions(scope: Scope, baseOptions: BuildOptions): BuildOptions =
buildOptionsFor(scope)
.foldRight(baseOptions)(_ orElse _)

def buildInfo(baseOptions: BuildOptions): BuildInfo = {
def getScopedBuildInfo(scope: Scope): ScopedBuildInfo =
val combinedOptions = combinedBuildOptions(scope, baseOptions)
val sourcePaths = paths.flatMap(_.valueFor(scope).toSeq).map(_._1.toString)
val inMemoryPaths =
(inMemory.flatMap(_.valueFor(scope).toSeq).flatMap(_.originalPath.toOption) ++
unwrappedScripts.flatMap(_.valueFor(scope).toSeq).flatMap(_.originalPath.toOption))
.map(_._2.toString)

ScopedBuildInfo(combinedOptions, sourcePaths ++ inMemoryPaths)

val baseBuildInfo = BuildInfo(combinedBuildOptions(Scope.Main, baseOptions))

val mainBuildInfo = getScopedBuildInfo(Scope.Main)
val testBuildInfo = getScopedBuildInfo(Scope.Test)

baseBuildInfo
.withScope(Scope.Main.name, mainBuildInfo)
.withScope(Scope.Test.name, testBuildInfo)
}
}
8 changes: 6 additions & 2 deletions modules/build/src/main/scala/scala/build/Sources.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package scala.build
import coursier.cache.ArchiveCache
import coursier.util.Task

import scala.build.info.BuildInfo
import scala.build.input.Inputs
import scala.build.internal.{CodeWrapper, WrapperParams}
import scala.build.options.{BuildOptions, Scope}
Expand All @@ -28,6 +29,11 @@ final case class Sources(
)
}

/** Write all in-memory sources to disk.
*
* @param generatedSrcRoot
* the root directory where the sources should be written
*/
def generateSources(generatedSrcRoot: os.Path): Seq[GeneratedSource] = {
val generated =
for (inMemSource <- inMemory) yield {
Expand Down Expand Up @@ -86,8 +92,6 @@ object Sources {

/** The default preprocessor list.
*
* @param codeWrapper
* used by the Scala script preprocessor to "wrap" user code
* @param archiveCache
* used from native launchers by the Java preprocessor, to download a java-class-name binary,
* used to infer the class name of unnamed Java sources (like stdin)
Expand Down
7 changes: 3 additions & 4 deletions modules/build/src/main/scala/scala/build/bsp/BspImpl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -111,14 +111,13 @@ final class BspImpl(
).left.map((_, Scope.Main))
}

val sharedOptions = crossSources.sharedOptions(buildOptions)
val wrappedScriptsSources = crossSources.withWrappedScripts(buildOptions)
val sharedOptions = crossSources.sharedOptions(buildOptions)

if (verbosity >= 3)
pprint.err.log(wrappedScriptsSources)
pprint.err.log(crossSources)

val scopedSources =
value(wrappedScriptsSources.scopedSources(buildOptions).left.map((_, Scope.Main)))
value(crossSources.scopedSources(buildOptions).left.map((_, Scope.Main)))

if (verbosity >= 3)
pprint.err.log(scopedSources)
Expand Down
13 changes: 13 additions & 0 deletions modules/build/src/main/scala/scala/build/bsp/BspServer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,19 @@ class BspServer(
sourceItem.setUri(updatedUri)
sourceItem.setGenerated(false)
}

// GeneratedSources not corresponding to files that exist on disk (unlike script wrappers)
val sourcesWithReportingPathString = generatedSources.values.flatMap(_.sources)
.filter(_.reportingPath.isLeft)

for {
item <- res.getItems.asScala
if validTarget(item.getTarget)
sourceItem <- item.getSources.asScala
if sourcesWithReportingPathString.exists(
_.generated.toNIO.toUri.toASCIIString == sourceItem.getUri
)
} sourceItem.setGenerated(true)
}

protected def forwardTo
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import scala.build.preprocessing.directives
object DirectivesPreprocessingUtils {
val usingDirectiveHandlers: Seq[DirectiveHandler[BuildOptions]] =
Seq[DirectiveHandler[_ <: HasBuildOptions]](
directives.BuildInfo.handler,
directives.Exclude.handler,
directives.JavaHome.handler,
directives.Jvm.handler,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,7 @@ class ExcludeTests extends munit.FunSuite {
TestLogger(),
SuppressWarningOptions()
)(using ScalaCliInvokeData.dummy).orThrow
val scopedSources = crossSources.withWrappedScripts(BuildOptions())
.scopedSources(BuildOptions())
val scopedSources = crossSources.scopedSources(BuildOptions())
.orThrow
val sources = scopedSources.sources(Scope.Main, crossSources.sharedOptions(BuildOptions()))

Expand All @@ -122,8 +121,7 @@ class ExcludeTests extends munit.FunSuite {
TestLogger(),
SuppressWarningOptions()
)(using ScalaCliInvokeData.dummy).orThrow
val scopedSources = crossSources.withWrappedScripts(BuildOptions())
.scopedSources(BuildOptions())
val scopedSources = crossSources.scopedSources(BuildOptions())
.orThrow
val sources = scopedSources.sources(Scope.Main, crossSources.sharedOptions(BuildOptions()))

Expand All @@ -150,8 +148,7 @@ class ExcludeTests extends munit.FunSuite {
TestLogger(),
SuppressWarningOptions()
)(using ScalaCliInvokeData.dummy).orThrow
val scopedSources = crossSources.withWrappedScripts(BuildOptions())
.scopedSources(BuildOptions())
val scopedSources = crossSources.scopedSources(BuildOptions())
.orThrow
val sources = scopedSources.sources(Scope.Main, crossSources.sharedOptions(BuildOptions()))

Expand All @@ -178,8 +175,7 @@ class ExcludeTests extends munit.FunSuite {
TestLogger(),
SuppressWarningOptions()
)(using ScalaCliInvokeData.dummy).orThrow
val scopedSources = crossSources.withWrappedScripts(BuildOptions())
.scopedSources(BuildOptions())
val scopedSources = crossSources.scopedSources(BuildOptions())
.orThrow
val sources = scopedSources.sources(Scope.Main, crossSources.sharedOptions(BuildOptions()))

Expand Down
Loading