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,
|
utilCommonSettings,
|
||||||
name := "Util Position",
|
name := "Util Position",
|
||||||
scalacOptions += "-language:experimental.macros",
|
scalacOptions += "-language:experimental.macros",
|
||||||
libraryDependencies ++= Seq(scalatest % "test"),
|
libraryDependencies ++= Seq(hedgehog % Test),
|
||||||
mimaSettings,
|
mimaSettings,
|
||||||
|
mimaBinaryIssueFilters ++= Seq(
|
||||||
|
exclude[ReversedMissingMethodProblem]("sbt.internal.util.FilePosition.sourceCode"),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
lazy val utilCore = project
|
lazy val utilCore = project
|
||||||
|
|
|
||||||
|
|
@ -23,13 +23,14 @@ abstract class SourcePositionImpl {
|
||||||
object SourcePositionImpl {
|
object SourcePositionImpl {
|
||||||
|
|
||||||
def fromEnclosingImpl(using Quotes): Expr[SourcePosition] = {
|
def fromEnclosingImpl(using Quotes): Expr[SourcePosition] = {
|
||||||
val x = quotes.reflect.Position.ofMacroExpansion
|
val pos = quotes.reflect.Position.ofMacroExpansion
|
||||||
|
if pos.startLine >= 0 then
|
||||||
'{
|
'{
|
||||||
LinePosition(
|
LinePosition(
|
||||||
path = ${ Expr(x.sourceFile.name) },
|
path = ${ Expr(pos.sourceFile.name) },
|
||||||
startLine = ${ Expr(x.startLine + 1) }
|
startLine = ${ Expr(pos.startLine) }
|
||||||
)
|
).withSourceCode(${ Expr(pos.sourceCode) })
|
||||||
}
|
}
|
||||||
|
else '{ NoPosition }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,18 +13,40 @@ sealed trait SourcePosition
|
||||||
sealed trait FilePosition extends SourcePosition {
|
sealed trait FilePosition extends SourcePosition {
|
||||||
def path: String
|
def path: String
|
||||||
def startLine: Int
|
def startLine: Int
|
||||||
|
def sourceCode: Option[String]
|
||||||
}
|
}
|
||||||
|
|
||||||
case object NoPosition extends SourcePosition
|
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) {
|
final case class LineRange(start: Int, end: Int) {
|
||||||
def shift(n: Int) = new LineRange(start + n, end + n)
|
def shift(n: Int) = new LineRange(start + n, end + n)
|
||||||
}
|
}
|
||||||
|
|
||||||
final case class RangePosition(path: String, range: LineRange) extends FilePosition {
|
final case class RangePosition(path: String, range: LineRange) extends FilePosition {
|
||||||
|
private var _sourceCode: Option[String] = None
|
||||||
def startLine = range.start
|
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
|
object SourcePosition extends SourcePositionImpl
|
||||||
|
|
|
||||||
|
|
@ -8,19 +8,29 @@
|
||||||
|
|
||||||
package sbt.internal.util
|
package sbt.internal.util
|
||||||
|
|
||||||
import org.scalatest.flatspec.AnyFlatSpec
|
import hedgehog.*
|
||||||
|
import hedgehog.runner.*
|
||||||
|
|
||||||
class SourcePositionSpec extends AnyFlatSpec {
|
object SourcePositionSpec extends Properties:
|
||||||
"SourcePosition()" should "return a sane SourcePosition" in {
|
override def tests: List[Test] = List(
|
||||||
val filename = "SourcePositionSpec.scala"
|
example(
|
||||||
val lineNumber = 17
|
"SourcePosition() should return a SourcePosition", {
|
||||||
SourcePosition.fromEnclosing() match {
|
val filename = "SourcePositionSpec.scala"
|
||||||
case LinePosition(path, startLine) => assert(path === filename && startLine === lineNumber)
|
val lineNumber = 19
|
||||||
case RangePosition(path, range) => assert(path === filename && inRange(range, lineNumber))
|
SourcePosition.fromEnclosing() match {
|
||||||
case NoPosition => fail("No source position found")
|
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) =
|
private def inRange(range: LineRange, lineNo: Int) =
|
||||||
range.start until range.end contains lineNo
|
range.start until range.end contains lineNo
|
||||||
}
|
end SourcePositionSpec
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ package std
|
||||||
import Def.{ Initialize, Setting }
|
import Def.{ Initialize, Setting }
|
||||||
import sbt.internal.util.Types.Id
|
import sbt.internal.util.Types.Id
|
||||||
import sbt.internal.util.appmacro.{ Cont, ContextUtil, ContextUtil0 }
|
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 language.experimental.macros
|
||||||
import scala.quoted.*
|
import scala.quoted.*
|
||||||
|
|
@ -184,13 +184,7 @@ object TaskMacro:
|
||||||
}
|
}
|
||||||
|
|
||||||
private[sbt] def sourcePosition(using qctx: Quotes): Expr[SourcePosition] =
|
private[sbt] def sourcePosition(using qctx: Quotes): Expr[SourcePosition] =
|
||||||
import qctx.reflect.*
|
SourcePositionImpl.fromEnclosingImpl
|
||||||
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 }
|
|
||||||
|
|
||||||
end TaskMacro
|
end TaskMacro
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -973,7 +973,11 @@ object BuiltinCommands {
|
||||||
st => setupGlobalFileTreeRepository(Clean.addCacheStoreFactoryFactory(st))
|
st => setupGlobalFileTreeRepository(Clean.addCacheStoreFactoryFactory(st))
|
||||||
)
|
)
|
||||||
val s4 = s3.put(Keys.useLog4J.key, Project.extract(s3).get(Keys.useLog4J))
|
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 =>
|
private val setupGlobalFileTreeRepository: State => State = { state =>
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ package sbt
|
||||||
package internal
|
package internal
|
||||||
|
|
||||||
import Keys.*
|
import Keys.*
|
||||||
import sbt.internal.util.{ FilePosition, NoPosition, SourcePosition }
|
import sbt.internal.util.{ FilePosition, LinePosition, NoPosition, SourcePosition }
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import ProjectExtra.{ extract, scopedKeyData }
|
import ProjectExtra.{ extract, scopedKeyData }
|
||||||
import Scope.Global
|
import Scope.Global
|
||||||
|
|
@ -165,6 +165,40 @@ object LintUnused {
|
||||||
unusedKeys.map(u => (u.scoped, display.show(u.scoped), u.positions)).sortBy(_._2)
|
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(
|
private case class UnusedKey(
|
||||||
scoped: ScopedKey[?],
|
scoped: ScopedKey[?],
|
||||||
positions: Seq[SourcePosition],
|
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