print-warnings task for Scala 2.10+ to avoid needing to rerun 'compile' to see deprecation/unchecked warnings

This commit is contained in:
Mark Harrah 2012-03-17 19:31:55 -04:00
parent 9049c6cb68
commit 1cbb7ce93c
12 changed files with 156 additions and 35 deletions

View File

@ -9,15 +9,15 @@ package sbt
// Original author: Martin Odersky
import xsbti.{Maybe,Position,Problem,Reporter,Severity}
import java.io.File
import java.util.EnumMap
import scala.collection.mutable
import LoggerReporter._
import Logger.{m2o,o2m,position,problem}
import Severity.{Error,Info => SInfo,Warn}
object LoggerReporter
{
def m2o[S](m: Maybe[S]): Option[S] = if(m.isDefined) Some(m.get) else None
final class PositionKey(pos: Position)
{
def offset = pos.offset
@ -49,7 +49,7 @@ class LoggerReporter(maximumErrors: Int, log: Logger) extends xsbti.Reporter
{
val positions = new mutable.HashMap[PositionKey, Severity]
val count = new EnumMap[Severity, Int](classOf[Severity])
private val allProblems = new mutable.ListBuffer[Problem]
private[this] val allProblems = new mutable.ListBuffer[Problem]
reset()
@ -63,7 +63,7 @@ class LoggerReporter(maximumErrors: Int, log: Logger) extends xsbti.Reporter
}
def hasWarnings = count.get(Warn) > 0
def hasErrors = count.get(Error) > 0
def problems = allProblems.toArray
def problems: Array[Problem] = allProblems.toArray
def printSummary()
{
@ -126,13 +126,6 @@ class LoggerReporter(maximumErrors: Int, log: Logger) extends xsbti.Reporter
case _ => display(pos, msg, severity)
}
}
def problem(pos: Position, msg: String, sev: Severity): Problem =
new Problem
{
val position = pos
val message = msg
val severity = sev
}
def testAndLog(pos: Position, severity: Severity): Boolean =
{

View File

@ -13,12 +13,13 @@ trait Analysis
val stamps: Stamps
val apis: APIs
val relations: Relations
val infos: SourceInfos
def ++(other: Analysis): Analysis
def -- (sources: Iterable[File]): Analysis
def copy(stamps: Stamps = stamps, apis: APIs = apis, relations: Relations = relations): Analysis
def copy(stamps: Stamps = stamps, apis: APIs = apis, relations: Relations = relations, infos: SourceInfos = infos): Analysis
def addSource(src: File, api: Source, stamp: Stamp, internalDeps: Iterable[File]): Analysis
def addSource(src: File, api: Source, stamp: Stamp, internalDeps: Iterable[File], info: SourceInfo): Analysis
def addBinaryDep(src: File, dep: File, className: String, stamp: Stamp): Analysis
def addExternalDep(src: File, dep: String, api: Source): Analysis
def addProduct(src: File, product: File, stamp: Stamp, name: String): Analysis
@ -28,25 +29,27 @@ trait Analysis
object Analysis
{
lazy val Empty: Analysis = new MAnalysis(Stamps.empty, APIs.empty, Relations.empty)
lazy val Empty: Analysis = new MAnalysis(Stamps.empty, APIs.empty, Relations.empty, SourceInfos.empty)
def summary(a: Analysis): String =
{
val (j, s) = a.apis.allInternalSources.partition(_.getName.endsWith(".java"))
val c = a.stamps.allProducts
val ext = a.apis.allExternals
val jars = a.relations.allBinaryDeps.filter(_.getName.endsWith(".jar"))
val unreportedCount = a.infos.allInfos.values.map(_.unreportedProblems.size).sum
val sections =
counted("Scala source", "", "s", s.size) ++
counted("Java source", "", "s", j.size) ++
counted("class", "", "es", c.size) ++
counted("external source dependenc", "y", "ies", ext.size) ++
counted("binary dependenc", "y", "ies", jars.size)
counted("binary dependenc", "y", "ies", jars.size) ++
counted("unreported warning", "", "s", unreportedCount)
sections.mkString("Analysis: ", ", ", "")
}
}
private class MAnalysis(val stamps: Stamps, val apis: APIs, val relations: Relations) extends Analysis
private class MAnalysis(val stamps: Stamps, val apis: APIs, val relations: Relations, val infos: SourceInfos) extends Analysis
{
def ++ (o: Analysis): Analysis = new MAnalysis(stamps ++ o.stamps, apis ++ o.apis, relations ++ o.relations)
def ++ (o: Analysis): Analysis = new MAnalysis(stamps ++ o.stamps, apis ++ o.apis, relations ++ o.relations, infos ++ o.infos)
def -- (sources: Iterable[File]): Analysis =
{
val newRelations = relations -- sources
@ -54,19 +57,20 @@ private class MAnalysis(val stamps: Stamps, val apis: APIs, val relations: Relat
val newAPIs = apis.removeInternal(sources).filterExt( keep(_ usesExternal _) )
val newStamps = stamps.filter( keep(_ produced _), sources, keep(_ usesBinary _))
new MAnalysis(newStamps, newAPIs, newRelations)
val newInfos = infos -- sources
new MAnalysis(newStamps, newAPIs, newRelations, newInfos)
}
def copy(stamps: Stamps, apis: APIs, relations: Relations): Analysis = new MAnalysis(stamps, apis, relations)
def copy(stamps: Stamps, apis: APIs, relations: Relations, infos: SourceInfos): Analysis = new MAnalysis(stamps, apis, relations, infos)
def addSource(src: File, api: Source, stamp: Stamp, internalDeps: Iterable[File]): Analysis =
copy( stamps.markInternalSource(src, stamp), apis.markInternalSource(src, api), relations.addInternalSrcDeps(src, internalDeps) )
def addSource(src: File, api: Source, stamp: Stamp, internalDeps: Iterable[File], info: SourceInfo): Analysis =
copy( stamps.markInternalSource(src, stamp), apis.markInternalSource(src, api), relations.addInternalSrcDeps(src, internalDeps), infos.add(src, info) )
def addBinaryDep(src: File, dep: File, className: String, stamp: Stamp): Analysis =
copy( stamps.markBinary(dep, className, stamp), apis, relations.addBinaryDep(src, dep) )
copy( stamps.markBinary(dep, className, stamp), apis, relations.addBinaryDep(src, dep), infos )
def addExternalDep(src: File, dep: String, depAPI: Source): Analysis =
copy( stamps, apis.markExternalAPI(dep, depAPI), relations.addExternalDep(src, dep) )
copy( stamps, apis.markExternalAPI(dep, depAPI), relations.addExternalDep(src, dep), infos )
def addProduct(src: File, product: File, stamp: Stamp, name: String): Analysis =
copy( stamps.markProduct(product, stamp), apis, relations.addProduct(src, product, name) )
copy( stamps.markProduct(product, stamp), apis, relations.addProduct(src, product, name), infos )
}

View File

@ -5,6 +5,8 @@ package sbt
package inc
import xsbti.api.{Source, SourceAPI}
import xsbti.{Position,Problem,Severity}
import Logger.{m2o, problem}
import java.io.File
object IncrementalCompile
@ -44,6 +46,8 @@ private final class AnalysisCallback(internalMap: File => Option[File], external
import collection.mutable.{HashMap, HashSet, ListBuffer, Map, Set}
private[this] val apis = new HashMap[File, (Int, SourceAPI)]
private[this] val unreporteds = new HashMap[File, ListBuffer[Problem]]
private[this] val reporteds = new HashMap[File, ListBuffer[Problem]]
private[this] val binaryDeps = new HashMap[File, Set[File]]
// source file to set of generated (class file, class name)
private[this] val classes = new HashMap[File, Set[(File, String)]]
@ -58,6 +62,14 @@ private final class AnalysisCallback(internalMap: File => Option[File], external
private def add[A,B](map: Map[A,Set[B]], a: A, b: B): Unit =
map.getOrElseUpdate(a, new HashSet[B]) += b
def problem(pos: Position, msg: String, severity: Severity, reported: Boolean): Unit =
{
for(source <- m2o(pos.sourceFile)) {
val map = if(reported) reporteds else unreporteds
map.getOrElseUpdate(source, ListBuffer.empty) += Logger.problem(pos, msg, severity)
}
}
def sourceDependency(dependsOn: File, source: File) = if(source != dependsOn) add(sourceDeps, source, dependsOn)
def externalBinaryDependency(binary: File, className: String, source: File)
{
@ -120,8 +132,10 @@ private final class AnalysisCallback(internalMap: File => Option[File], external
// TODO store this in Relations, rather than Source.
val hasMacro: Boolean = macroSources.contains(src)
val s = new xsbti.api.Source(compilation, hash, api._2, api._1, hasMacro)
a.addSource(src, s, stamp, sourceDeps.getOrElse(src, Nil: Iterable[File]))
val info = SourceInfos.makeInfo(getOrNil(reporteds, src), getOrNil(unreporteds, src))
a.addSource(src, s, stamp, sourceDeps.getOrElse(src, Nil: Iterable[File]), info)
}
def getOrNil[A,B](m: collection.Map[A, Seq[B]], a: A): Seq[B] = m.get(a).toList.flatten
def addExternals(base: Analysis): Analysis = (base /: extSrcDeps) { case (a, (source, name, api)) => a.addExternalDep(source, name, api) }
def addAll[A,B](base: Analysis, m: Map[A, Set[B]])( f: (Analysis, A, B) => Analysis): Analysis =

View File

@ -0,0 +1,37 @@
package sbt
package inc
import xsbti.Problem
import java.io.File
trait SourceInfo
{
def reportedProblems: Seq[Problem]
def unreportedProblems: Seq[Problem]
}
trait SourceInfos
{
def ++(o: SourceInfos): SourceInfos
def add(file: File, info: SourceInfo): SourceInfos
def --(files: Iterable[File]): SourceInfos
def get(file: File): SourceInfo
def allInfos: Map[File, SourceInfo]
}
object SourceInfos
{
def empty: SourceInfos = make(Map.empty)
def make(m: Map[File, SourceInfo]): SourceInfos = new MSourceInfos(m)
val emptyInfo: SourceInfo = makeInfo(Nil, Nil)
def makeInfo(reported: Seq[Problem], unreported: Seq[Problem]): SourceInfo =
new MSourceInfo(reported, unreported)
}
private final class MSourceInfos(val allInfos: Map[File, SourceInfo]) extends SourceInfos
{
def ++(o: SourceInfos) = new MSourceInfos(allInfos ++ o.allInfos)
def --(sources: Iterable[File]) = new MSourceInfos(allInfos -- sources)
def add(file: File, info: SourceInfo) = new MSourceInfos(allInfos + ((file, info)))
def get(file:File) = allInfos.getOrElse(file, SourceInfos.emptyInfo)
}
private final class MSourceInfo(val reportedProblems: Seq[Problem], val unreportedProblems: Seq[Problem]) extends SourceInfo

View File

@ -3,7 +3,7 @@
*/
package xsbt
import xsbti.{AnalysisCallback,Logger,Problem,Reporter}
import xsbti.{AnalysisCallback,Logger,Problem,Reporter,Severity}
import scala.tools.nsc.{Phase, SubComponent}
import Log.debug
@ -17,10 +17,10 @@ class CompilerInterface
val settings = new Settings(Log.settingsError(log))
val command = Command(args.toList, settings)
val reporter = DelegatingReporter(settings, delegate)
def noErrors = !reporter.hasErrors && command.ok
val dreporter = DelegatingReporter(settings, delegate)
def noErrors = !dreporter.hasErrors && command.ok
object compiler extends Global(command.settings, reporter)
object compiler extends Global(command.settings, dreporter)
{
object dummy // temporary fix for #4426
object sbtAnalyzer extends
@ -65,10 +65,24 @@ class CompilerInterface
meth.setAccessible(true)
meth.invoke(this).asInstanceOf[List[SubComponent]]
}
def logUnreportedWarnings(seq: List[(Position,String)]): Unit = // Scala 2.10.x and later
{
for( (pos, msg) <- seq) yield
callback.problem(dreporter.convert(pos), msg, Severity.Warn, false)
}
def logUnreportedWarnings(count: Boolean): Unit = () // for source compatibility with Scala 2.8.x
def logUnreportedWarnings(count: Int): Unit = () // for source compatibility with Scala 2.9.x
}
def processUnreportedWarnings(run: compiler.Run)
{
implicit def listToBoolean[T](l: List[T]): Boolean = error("source compatibility only, should never be called")
implicit def listToInt[T](l: List[T]): Int = error("source compatibility only, should never be called")
compiler.logUnreportedWarnings(run.deprecationWarnings)
compiler.logUnreportedWarnings(run.uncheckedWarnings)
}
if(command.shouldStopWithInfo)
{
reporter.info(null, command.getInfoMessage(compiler), true)
dreporter.info(null, command.getInfoMessage(compiler), true)
throw new InterfaceCompileFailed(args, Array(), "Compiler option supplied that disabled actual compilation.")
}
if(noErrors)
@ -76,12 +90,14 @@ class CompilerInterface
val run = new compiler.Run
debug(log, args.mkString("Calling Scala compiler with arguments (CompilerInterface):\n\t", "\n\t", ""))
run compile command.files
processUnreportedWarnings(run)
dreporter.problems foreach { p => callback.problem(p.position, p.message, p.severity, true) }
}
reporter.printSummary()
dreporter.printSummary()
if(!noErrors)
{
debug(log, "Compilation failed (CompilerInterface)")
throw new InterfaceCompileFailed(args, reporter.problems, "Compilation failed")
throw new InterfaceCompileFailed(args, dreporter.problems, "Compilation failed")
}
}
}

View File

@ -37,7 +37,7 @@ private final class DelegatingReporter(warnFatal: Boolean, delegate: xsbti.Repor
val severity = if(warnFatal && rawSeverity == WARNING) ERROR else rawSeverity
delegate.log(convert(pos), msg, convert(severity))
}
private[this] def convert(posIn: Position): xsbti.Position =
def convert(posIn: Position): xsbti.Position =
{
val pos =
posIn match

View File

@ -5,9 +5,11 @@ package sbt
package inc
import xsbti.api.Source
import xsbti.{Position,Problem,Severity}
import java.io.File
import sbinary._
import DefaultProtocol._
import Logger.{m2o, position, problem}
object AnalysisFormats
{
@ -40,8 +42,26 @@ object AnalysisFormats
}
}
implicit def analysisFormat(implicit stampsF: Format[Stamps], apisF: Format[APIs], relationsF: Format[Relations]): Format[Analysis] =
asProduct3( Analysis.Empty.copy _)( a => (a.stamps, a.apis, a.relations))(stampsF, apisF, relationsF)
implicit def analysisFormat(implicit stampsF: Format[Stamps], apisF: Format[APIs], relationsF: Format[Relations], infosF: Format[SourceInfos]): Format[Analysis] =
asProduct4( Analysis.Empty.copy _)( a => (a.stamps, a.apis, a.relations, a.infos))(stampsF, apisF, relationsF, infosF)
implicit def infosFormat(implicit infoF: Format[Map[File, SourceInfo]]): Format[SourceInfos] =
wrap[SourceInfos, Map[File, SourceInfo]]( _.allInfos, SourceInfos.make _)
implicit def infoFormat(implicit infoF: Format[Problem]): Format[SourceInfo] =
wrap[SourceInfo, (Seq[Problem],Seq[Problem])](si => (si.reportedProblems, si.unreportedProblems), { case (a,b) => SourceInfos.makeInfo(a,b)})
implicit def problemFormat(implicit posF: Format[Position], msgF: Format[String], sevF: Format[Severity]): Format[Problem] =
asProduct3(problem _)( p => (p.position, p.message, p.severity))
implicit def positionFormat: Format[Position] =
asProduct7( position _ )( p => (m2o(p.line), p.lineContent, m2o(p.offset), m2o(p.pointer), m2o(p.pointerSpace), m2o(p.sourcePath), m2o(p.sourceFile)))
implicit val fileOptionFormat: Format[Option[File]] = optionsAreFormat[File](fileFormat)
implicit val integerFormat: Format[Integer] = wrap[Integer, Int](_.toInt, Integer.valueOf)
implicit val severityFormat: Format[Severity] =
wrap[Severity, Byte]( _.ordinal.toByte, b => Severity.values.apply(b.toInt) )
implicit def setupFormat(implicit outDirF: Format[File], optionF: Format[CompileOptions], compilerVersion: Format[String], orderF: Format[CompileOrder.Value]): Format[CompileSetup] =
asProduct4[CompileSetup, File, CompileOptions, String, CompileOrder.Value]( (a,b,c,d) => new CompileSetup(a,b,c,d) )(s => (s.outputDirectory, s.options, s.compilerVersion, s.order))(outDirF, optionF, compilerVersion, orderF)

View File

@ -24,4 +24,7 @@ public interface AnalysisCallback
public void endSource(File sourcePath);
/** Called when the public API of a source file is extracted. */
public void api(File sourceFile, xsbti.api.SourceAPI source);
/** Provides problems discovered during compilation. These may be reported (logged) or unreported.
* Unreported problems are usually unreported because reporting was not enabled via a command line switch. */
public void problem(Position pos, String msg, Severity severity, boolean reported);
}

View File

@ -20,4 +20,5 @@ class TestCallback extends AnalysisCallback
def endSource(source: File) { endedSources += source }
def api(source: File, sourceAPI: xsbti.api.SourceAPI) { apis += ((source, sourceAPI)) }
def problem(pos: xsbti.Position, message: String, severity: xsbti.Severity, reported: Boolean) {}
}

View File

@ -211,6 +211,7 @@ object Defaults extends BuildCommon
initialCommands in GlobalScope :== "",
cleanupCommands in GlobalScope :== "",
compile <<= compileTask tag(Tags.Compile, Tags.CPU),
printWarnings <<= printWarningsTask,
compileIncSetup <<= compileIncSetupTask,
console <<= consoleTask,
consoleQuick <<= consoleQuickTask,
@ -562,6 +563,12 @@ object Defaults extends BuildCommon
Compiler.inputs(classes +: data(cp), srcs, classes, optsPair._1, optsPair._2, maxErr, order)(cs, incSetup, s.log)
})
}
def printWarningsTask: Initialize[Task[Unit]] =
(streams, compile, maxErrors) map { (s, analysis, max) =>
val problems = analysis.infos.allInfos.values.flatMap(i => i.reportedProblems++ i.unreportedProblems)
val reporter = new LoggerReporter(max, s.log)
problems foreach { p => reporter.display(p.position, p.message, p.severity) }
}
def sbtPluginExtra(m: ModuleID, sbtV: String, scalaV: String): ModuleID =
m.extra(CustomPomParser.SbtVersionKey -> sbtV, CustomPomParser.ScalaVersionKey -> scalaV).copy(crossVersion = CrossVersion.Disabled)

View File

@ -141,6 +141,7 @@ object Keys
val classpathOptions = SettingKey[ClasspathOptions]("classpath-options", "Configures handling of Scala classpaths.")
val definedSbtPlugins = TaskKey[Set[String]]("defined-sbt-plugins", "The set of names of Plugin implementations defined by this project.")
val sbtPlugin = SettingKey[Boolean]("sbt-plugin", "If true, enables adding sbt as a dependency and auto-generation of the plugin descriptor file.")
val printWarnings = TaskKey[Unit]("print-warnings", "Shows warnings from compilation, including ones that weren't printed initially.")
val clean = TaskKey[Unit]("clean", "Deletes files produced by the build, such as generated sources, compiled classes, and task caches.")
val console = TaskKey[Unit]("console", "Starts the Scala interpreter with the project classes on the classpath.")

View File

@ -4,6 +4,9 @@
package sbt
import xsbti.{Logger => xLogger, F0}
import xsbti.{Maybe,Position,Problem,Severity}
import java.io.File
abstract class AbstractLogger extends Logger
{
@ -61,6 +64,28 @@ object Logger
}
}
def f0[T](t: =>T): F0[T] = new F0[T] { def apply = t }
def m2o[S](m: Maybe[S]): Option[S] = if(m.isDefined) Some(m.get) else None
def o2m[S](o: Option[S]): Maybe[S] = o match { case Some(v) => Maybe.just(v); case None => Maybe.nothing() }
def position(line0: Option[Integer], content: String, offset0: Option[Integer], pointer0: Option[Integer], pointerSpace0: Option[String], sourcePath0: Option[String], sourceFile0: Option[File]): Position =
new Position {
val line = o2m(line0)
val lineContent = content
val offset = o2m(offset0)
val pointer = o2m(pointer0)
val pointerSpace = o2m(pointerSpace0)
val sourcePath = o2m(sourcePath0)
val sourceFile = o2m(sourceFile0)
}
def problem(pos: Position, msg: String, sev: Severity): Problem =
new Problem
{
val position = pos
val message = msg
val severity = sev
}
}
/** This is intended to be the simplest logging interface for use by code that wants to log.