Merge pull request #8205 from Duhemm/mduhem/build-sbt-annotations

Support annotated definitions in build.sbt
This commit is contained in:
eugene yokota 2025-08-11 13:17:10 -04:00 committed by GitHub
commit 16269ba88e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 68 additions and 14 deletions

View File

@ -22,6 +22,7 @@ import scala.jdk.CollectionConverters.*
import xsbti.PathBasedFile
import xsbti.VirtualFile
import xsbti.VirtualFileRef
import dotty.tools.dotc.ast.untpd.{ Annotated, ValOrDefDef, Tree }
/**
* This file is responsible for compiling the .sbt files used to configure sbt builds.
@ -97,12 +98,13 @@ private[sbt] object EvaluateConfigurations {
builtinImports: Seq[String],
offset: Int
): ParsedFile = {
def loseTree(l: (String, Tree, LineRange)): (String, LineRange) = (l._1, l._3)
val (importStatements, settingsAndDefinitions) = splitExpressions(file, lines)
val allImports = builtinImports.map(s => (s, -1)) ++ addOffset(offset, importStatements)
val (definitions, settings) = splitSettingsDefinitions(
addOffsetToRange(offset, settingsAndDefinitions)
)
new ParsedFile(allImports, definitions, settings)
new ParsedFile(allImports, definitions.map(loseTree), settings.map(loseTree))
}
/**
@ -194,11 +196,14 @@ private[sbt] object EvaluateConfigurations {
private def resolveBase(f: File, p: Project) =
p.copy(base = IO.resolve(f, p.base))
def addOffset(offset: Int, lines: Seq[(String, Int)]): Seq[(String, Int)] =
private def addOffset(offset: Int, lines: Seq[(String, Int)]): Seq[(String, Int)] =
lines.map { (s, i) => (s, i + offset) }
def addOffsetToRange(offset: Int, ranges: Seq[(String, LineRange)]): Seq[(String, LineRange)] =
ranges.map { (s, r) => (s, r.shift(offset)) }
private def addOffsetToRange(
offset: Int,
ranges: Seq[(String, Tree, LineRange)]
): Seq[(String, Tree, LineRange)] =
ranges.map { (s, t, r) => (s, t, r.shift(offset)) }
/**
* The name of the class we cast DSL "setting" (vs. definition) lines to.
@ -286,20 +291,28 @@ private[sbt] object EvaluateConfigurations {
private[sbt] def splitExpressions(
file: VirtualFileRef,
lines: Seq[String]
): (Seq[(String, Int)], Seq[(String, LineRange)]) =
): (Seq[(String, Int)], Seq[(String, Tree, LineRange)]) =
val split = SbtParser(file, lines)
// TODO - Look at pulling the parsed expression trees from the SbtParser and stitch them back into a different
// scala compiler rather than re-parsing.
(split.imports, split.settings)
(
split.imports,
split.settings.zip(split.settingsTrees).map { case ((s, r), (_, t)) =>
(s, t, r)
}
)
private def splitSettingsDefinitions(
lines: Seq[(String, LineRange)]
): (Seq[(String, LineRange)], Seq[(String, LineRange)]) =
lines partition { case (line, _) => isDefinition(line) }
lines: Seq[(String, Tree, LineRange)]
): (Seq[(String, Tree, LineRange)], Seq[(String, Tree, LineRange)]) =
lines partition { case (_, tree, _) => isDefinition(tree) }
private def isDefinition(line: String): Boolean = {
val trimmed = line.trim
DefinitionKeywords.exists(trimmed.startsWith(_))
private def isDefinition(tree: Tree): Boolean = {
tree match {
case Annotated(arg, annot) => isDefinition(arg)
case _: ValOrDefDef => true
case _ => false
}
}
private def extractedValTypes: Seq[String] =

View File

@ -12,8 +12,9 @@ package parser
import sbt.internal.util.LineRange
import xsbti.VirtualFileRef
import dotty.tools.dotc.ast.untpd.Tree
object SplitExpressions:
type SplitExpression =
(VirtualFileRef, Seq[String]) => (Seq[(String, Int)], Seq[(String, LineRange)])
(VirtualFileRef, Seq[String]) => (Seq[(String, Int)], Seq[(String, Tree, LineRange)])
end SplitExpressions

View File

@ -12,10 +12,11 @@ package parser
import sbt.internal.util.LineRange
import xsbti.VirtualFileRef
import dotty.tools.dotc.ast.untpd.Tree
trait SplitExpression {
extension (splitter: SplitExpressions.SplitExpression)
def apply(s: String): (Seq[(String, Int)], Seq[(String, LineRange)]) =
def apply(s: String): (Seq[(String, Int)], Seq[(String, Tree, LineRange)]) =
splitter(VirtualFileRef.of("noFile"), s.split('\n').toSeq)
}
@ -53,6 +54,16 @@ trait SplitExpressionsBehavior extends SplitExpression { this: verify.BasicTestS
assert(imports.size == 2)
assert(settingsAndDefs.size == 1)
}
test("parse a config containing an annotated definition") {
val (imports, settingsAndDefs) = splitter(
"""|import foo.Bar
|@foo
|lazy val root = (project in file(".")).enablePlugins(PlayScala)""".stripMargin
)
assert(imports.size == 1)
assert(settingsAndDefs.size == 1)
}
}
}

View File

@ -0,0 +1,23 @@
import scala.annotation.{tailrec, nowarn}
import sbt.util.cacheLevel
@tailrec
def even(x: Int): Boolean = Math.abs(x) match
case 0 => true
case 1 => false
case n => even(n - 2)
@transient val foo = 4
@cacheLevel(include = Array.empty)
lazy val myTask = taskKey[Boolean]("...")
@nowarn
lazy val myProject = project.settings(
myTask := {
assert(!file("ran").exists)
println("running")
IO.touch(file("ran"))
even(2)
}
)

View File

@ -0,0 +1,6 @@
$ absent ran
> myTask
$ exists ran
# verify the task is not cached (i.e. annotation is not lost)
-> myTask