diff --git a/compile/LoggerReporter.scala b/compile/LoggerReporter.scala index 649e056c2..85db4ee44 100644 --- a/compile/LoggerReporter.scala +++ b/compile/LoggerReporter.scala @@ -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 = { diff --git a/compile/inc/Analysis.scala b/compile/inc/Analysis.scala index 2fdcf59c1..d56132498 100644 --- a/compile/inc/Analysis.scala +++ b/compile/inc/Analysis.scala @@ -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 ) } \ No newline at end of file diff --git a/compile/inc/Compile.scala b/compile/inc/Compile.scala index 8baa39426..f5d867352 100644 --- a/compile/inc/Compile.scala +++ b/compile/inc/Compile.scala @@ -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 = diff --git a/compile/inc/SourceInfo.scala b/compile/inc/SourceInfo.scala new file mode 100644 index 000000000..fc10c041d --- /dev/null +++ b/compile/inc/SourceInfo.scala @@ -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 diff --git a/compile/interface/CompilerInterface.scala b/compile/interface/CompilerInterface.scala index 8dbecae1e..d830e9df5 100644 --- a/compile/interface/CompilerInterface.scala +++ b/compile/interface/CompilerInterface.scala @@ -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") } } } diff --git a/compile/interface/DelegatingReporter.scala b/compile/interface/DelegatingReporter.scala index 3e0777368..9dcb1520d 100644 --- a/compile/interface/DelegatingReporter.scala +++ b/compile/interface/DelegatingReporter.scala @@ -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 diff --git a/compile/persist/AnalysisFormats.scala b/compile/persist/AnalysisFormats.scala index 4f817621f..19c73f388 100644 --- a/compile/persist/AnalysisFormats.scala +++ b/compile/persist/AnalysisFormats.scala @@ -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) diff --git a/interface/src/main/java/xsbti/AnalysisCallback.java b/interface/src/main/java/xsbti/AnalysisCallback.java index 53a33253b..2ba6f6c83 100644 --- a/interface/src/main/java/xsbti/AnalysisCallback.java +++ b/interface/src/main/java/xsbti/AnalysisCallback.java @@ -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); } \ No newline at end of file diff --git a/interface/src/test/scala/TestCallback.scala b/interface/src/test/scala/TestCallback.scala index 6621a40ef..95fcbf96c 100644 --- a/interface/src/test/scala/TestCallback.scala +++ b/interface/src/test/scala/TestCallback.scala @@ -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) {} } \ No newline at end of file diff --git a/main/Defaults.scala b/main/Defaults.scala index 8772fb6ec..fd607f0da 100755 --- a/main/Defaults.scala +++ b/main/Defaults.scala @@ -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) diff --git a/main/Keys.scala b/main/Keys.scala index cd13b6c9b..81e8f0cea 100644 --- a/main/Keys.scala +++ b/main/Keys.scala @@ -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.") diff --git a/util/log/Logger.scala b/util/log/Logger.scala index 04babcc4e..1f6d4d3a5 100644 --- a/util/log/Logger.scala +++ b/util/log/Logger.scala @@ -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.