mirror of https://github.com/sbt/sbt.git
[2.x] Show warnings when scalaVersion is missing
**Problem** There's a disconnect between what is perceived to be the current Scala version, and what sbt uses internally, and thus what it chooses to be the default scalaVersion. **Solution** This displays a warning if scalaVersion setting is missing.
This commit is contained in:
parent
55aa1b52ff
commit
6cb786d010
|
|
@ -290,8 +290,11 @@ lazy val utilPosition = (project in file("internal") / "util-position")
|
|||
utilCommonSettings,
|
||||
name := "Util Position",
|
||||
scalacOptions += "-language:experimental.macros",
|
||||
libraryDependencies ++= Seq(scalatest % "test"),
|
||||
libraryDependencies ++= Seq(hedgehog % Test),
|
||||
mimaSettings,
|
||||
mimaBinaryIssueFilters ++= Seq(
|
||||
exclude[ReversedMissingMethodProblem]("sbt.internal.util.FilePosition.sourceCode"),
|
||||
),
|
||||
)
|
||||
|
||||
lazy val utilCore = project
|
||||
|
|
|
|||
|
|
@ -23,13 +23,14 @@ abstract class SourcePositionImpl {
|
|||
object SourcePositionImpl {
|
||||
|
||||
def fromEnclosingImpl(using Quotes): Expr[SourcePosition] = {
|
||||
val x = quotes.reflect.Position.ofMacroExpansion
|
||||
|
||||
'{
|
||||
LinePosition(
|
||||
path = ${ Expr(x.sourceFile.name) },
|
||||
startLine = ${ Expr(x.startLine + 1) }
|
||||
)
|
||||
}
|
||||
val pos = quotes.reflect.Position.ofMacroExpansion
|
||||
if pos.startLine >= 0 then
|
||||
'{
|
||||
LinePosition(
|
||||
path = ${ Expr(pos.sourceFile.name) },
|
||||
startLine = ${ Expr(pos.startLine) }
|
||||
).withSourceCode(${ Expr(pos.sourceCode) })
|
||||
}
|
||||
else '{ NoPosition }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,18 +13,40 @@ sealed trait SourcePosition
|
|||
sealed trait FilePosition extends SourcePosition {
|
||||
def path: String
|
||||
def startLine: Int
|
||||
def sourceCode: Option[String]
|
||||
}
|
||||
|
||||
case object NoPosition extends SourcePosition
|
||||
|
||||
final case class LinePosition(path: String, startLine: Int) extends FilePosition
|
||||
final case class LinePosition(path: String, startLine: Int) extends FilePosition {
|
||||
private var _sourceCode: Option[String] = None
|
||||
def sourceCode: Option[String] = _sourceCode
|
||||
def withSourceCode(code: String): LinePosition =
|
||||
val o = copy()
|
||||
o._sourceCode = Some(code)
|
||||
o
|
||||
def withSourceCode(c: Option[String]): LinePosition =
|
||||
c match
|
||||
case Some(code) => this.withSourceCode(code)
|
||||
case None => this
|
||||
}
|
||||
|
||||
final case class LineRange(start: Int, end: Int) {
|
||||
def shift(n: Int) = new LineRange(start + n, end + n)
|
||||
}
|
||||
|
||||
final case class RangePosition(path: String, range: LineRange) extends FilePosition {
|
||||
private var _sourceCode: Option[String] = None
|
||||
def startLine = range.start
|
||||
def sourceCode: Option[String] = _sourceCode
|
||||
def withSourceCode(code: String): RangePosition =
|
||||
val o = copy()
|
||||
o._sourceCode = Some(code)
|
||||
o
|
||||
def withSourceCode(c: Option[String]): RangePosition =
|
||||
c match
|
||||
case Some(code) => this.withSourceCode(code)
|
||||
case None => this
|
||||
}
|
||||
|
||||
object SourcePosition extends SourcePositionImpl
|
||||
|
|
|
|||
|
|
@ -8,19 +8,29 @@
|
|||
|
||||
package sbt.internal.util
|
||||
|
||||
import org.scalatest.flatspec.AnyFlatSpec
|
||||
import hedgehog.*
|
||||
import hedgehog.runner.*
|
||||
|
||||
class SourcePositionSpec extends AnyFlatSpec {
|
||||
"SourcePosition()" should "return a sane SourcePosition" in {
|
||||
val filename = "SourcePositionSpec.scala"
|
||||
val lineNumber = 17
|
||||
SourcePosition.fromEnclosing() match {
|
||||
case LinePosition(path, startLine) => assert(path === filename && startLine === lineNumber)
|
||||
case RangePosition(path, range) => assert(path === filename && inRange(range, lineNumber))
|
||||
case NoPosition => fail("No source position found")
|
||||
}
|
||||
}
|
||||
object SourcePositionSpec extends Properties:
|
||||
override def tests: List[Test] = List(
|
||||
example(
|
||||
"SourcePosition() should return a SourcePosition", {
|
||||
val filename = "SourcePositionSpec.scala"
|
||||
val lineNumber = 19
|
||||
SourcePosition.fromEnclosing() match {
|
||||
case pos @ LinePosition(path, startLine) =>
|
||||
Result.assert(path == filename && startLine == lineNumber).log(pos.toString())
|
||||
Result
|
||||
.assert(pos.sourceCode == Some("SourcePosition.fromEnclosing()"))
|
||||
.log(pos.sourceCode.toString())
|
||||
case pos @ RangePosition(path, range) =>
|
||||
Result.assert(path == filename && inRange(range, lineNumber)).log(pos.toString())
|
||||
case NoPosition => Result.assert(false).log("No source position found")
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
private def inRange(range: LineRange, lineNo: Int) =
|
||||
range.start until range.end contains lineNo
|
||||
}
|
||||
end SourcePositionSpec
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ package std
|
|||
import Def.{ Initialize, Setting }
|
||||
import sbt.internal.util.Types.Id
|
||||
import sbt.internal.util.appmacro.{ Cont, ContextUtil, ContextUtil0 }
|
||||
import sbt.internal.util.{ LinePosition, NoPosition, SourcePosition }
|
||||
import sbt.internal.util.{ SourcePosition, SourcePositionImpl }
|
||||
|
||||
import language.experimental.macros
|
||||
import scala.quoted.*
|
||||
|
|
@ -184,13 +184,7 @@ object TaskMacro:
|
|||
}
|
||||
|
||||
private[sbt] def sourcePosition(using qctx: Quotes): Expr[SourcePosition] =
|
||||
import qctx.reflect.*
|
||||
val pos = Position.ofMacroExpansion
|
||||
if pos.startLine >= 0 && pos.sourceCode != None then
|
||||
val name = Expr(pos.sourceCode.get)
|
||||
val line = Expr(pos.startLine)
|
||||
'{ LinePosition($name, $line) }
|
||||
else '{ NoPosition }
|
||||
SourcePositionImpl.fromEnclosingImpl
|
||||
|
||||
end TaskMacro
|
||||
|
||||
|
|
|
|||
|
|
@ -973,7 +973,11 @@ object BuiltinCommands {
|
|||
st => setupGlobalFileTreeRepository(Clean.addCacheStoreFactoryFactory(st))
|
||||
)
|
||||
val s4 = s3.put(Keys.useLog4J.key, Project.extract(s3).get(Keys.useLog4J))
|
||||
addSuperShellParams(CheckBuildSources.init(LintUnused.lintUnusedFunc(s4)))
|
||||
addSuperShellParams(
|
||||
CheckBuildSources.init(
|
||||
LintUnused.lintScalaVersion(LintUnused.lintUnusedFunc(s4))
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private val setupGlobalFileTreeRepository: State => State = { state =>
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ package sbt
|
|||
package internal
|
||||
|
||||
import Keys.*
|
||||
import sbt.internal.util.{ FilePosition, NoPosition, SourcePosition }
|
||||
import sbt.internal.util.{ FilePosition, LinePosition, NoPosition, SourcePosition }
|
||||
import java.io.File
|
||||
import ProjectExtra.{ extract, scopedKeyData }
|
||||
import Scope.Global
|
||||
|
|
@ -165,6 +165,40 @@ object LintUnused {
|
|||
unusedKeys.map(u => (u.scoped, display.show(u.scoped), u.positions)).sortBy(_._2)
|
||||
}
|
||||
|
||||
def lintScalaVersion(state: State): State = {
|
||||
val log = state.log
|
||||
val extracted = Project.extract(state)
|
||||
val structure = extracted.structure
|
||||
val comp = structure.compiledMap
|
||||
for
|
||||
p <- structure.allProjectRefs
|
||||
scope = Scope.Global.rescope(p)
|
||||
key = scalaVersion.rescope(scope)
|
||||
data = Project.scopedKeyData(structure, key.scopedKey)
|
||||
sv = extracted.get(key)
|
||||
isPlugin = extracted.get(sbtPlugin.rescope(scope))
|
||||
mb = extracted.get(isMetaBuild.rescope(scope))
|
||||
auto = extracted.get(autoScalaLibrary.rescope(scope))
|
||||
msi = extracted.get(managedScalaInstance.rescope(scope))
|
||||
(_, sk) = extracted.runTask(skip.rescope(scope.rescope(publish.key)), state)
|
||||
display = p match
|
||||
case ProjectRef(_, id) => id
|
||||
case _ | null => Reference.display(p)
|
||||
c <- comp.get(data.map(_.definingKey).getOrElse(key.scopedKey))
|
||||
setting <- c.settings.headOption
|
||||
do
|
||||
if auto && msi && !isPlugin && !mb && !sk then
|
||||
setting.pos match
|
||||
case LinePosition(path, _) if path.endsWith("Defaults.scala") =>
|
||||
log.warn(
|
||||
s"""scalaVersion for subproject $display fell back to a default value $sv; declare it explicitly in build.sbt:
|
||||
scalaVersion := "$sv""""
|
||||
)
|
||||
case _ => ()
|
||||
else ()
|
||||
state
|
||||
}
|
||||
|
||||
private case class UnusedKey(
|
||||
scoped: ScopedKey[?],
|
||||
positions: Seq[SourcePosition],
|
||||
|
|
|
|||
|
|
@ -0,0 +1,39 @@
|
|||
@transient
|
||||
lazy val checkScalaVersionWarning = taskKey[Unit]("")
|
||||
|
||||
// exempt publish skipped projects
|
||||
lazy val `scala-version-root` = (project in file("."))
|
||||
.settings(
|
||||
name := "scala-version-root",
|
||||
checkScalaVersionWarning := {
|
||||
val state = Keys.state.value
|
||||
val logging = state.globalLogging
|
||||
val sv = scalaVersion.value
|
||||
val contents = IO.read(logging.backing.file)
|
||||
assert(contents.contains(s"""scalaVersion for subproject nievab1 fell back to a default value $sv"""))
|
||||
assert(!contents.contains(s"""scalaVersion for subproject scala-version-root fell back to a default value $sv"""))
|
||||
assert(!contents.contains(s"""scalaVersion for subproject nievab2 fell back to a default value $sv"""))
|
||||
assert(!contents.contains(s"""scalaVersion for subproject nievab3 fell back to a default value $sv"""))
|
||||
assert(!contents.contains(s"""scalaVersion for subproject nievab4 fell back to a default value $sv"""))
|
||||
()
|
||||
},
|
||||
publish / skip := true,
|
||||
)
|
||||
|
||||
lazy val nievab1 = project
|
||||
|
||||
// exempt plugin projects
|
||||
lazy val nievab2 = project
|
||||
.enablePlugins(SbtPlugin)
|
||||
|
||||
// exempt Java projects
|
||||
lazy val nievab3 = project
|
||||
.settings(
|
||||
autoScalaLibrary := false,
|
||||
)
|
||||
|
||||
// exempt SCALA_HOME projects
|
||||
lazy val nievab4 = project
|
||||
.settings(
|
||||
managedScalaInstance := false,
|
||||
)
|
||||
|
|
@ -0,0 +1 @@
|
|||
> checkScalaVersionWarning
|
||||
Loading…
Reference in New Issue