Skip to content

Commit

Permalink
lightbend-labs#170 Add filter file support
Browse files Browse the repository at this point in the history
  • Loading branch information
2m committed Apr 29, 2017
1 parent 8d46c68 commit 3bb5840
Show file tree
Hide file tree
Showing 37 changed files with 153 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ class BaseMimaKeys {
final val mimaFindBinaryIssues = taskKey[Map[ModuleID, (List[core.Problem], List[core.Problem])]]("All backward and forward binary incompatibilities between a given module and current project.")
final val mimaReportBinaryIssues = taskKey[Unit]("Logs all binary incompatibilities to the sbt console/logs.")

final val mimaFiltersDirectory = settingKey[File]("Directory containing issue filters.")

final val mimaBinaryIssueFilters = taskKey[Seq[core.ProblemFilter]]("Filters to apply to binary issues found. Applies both to backward and forward binary compatibility checking.")
final val mimaBackwardIssueFilters = taskKey[Map[String, Seq[core.ProblemFilter]]]("Filters to apply to binary issues found grouped by version of a module checked against. These filters only apply to backward compatibility checking.")
final val mimaForwardIssueFilters = taskKey[Map[String, Seq[core.ProblemFilter]]]("Filters to apply to binary issues found grouped by version of a module checked against. These filters only apply to forward compatibility checking.")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package com.typesafe.tools.mima
package plugin

import sbt._
import sbt.Keys.{ fullClasspath, streams, classDirectory, ivySbt, name, ivyScala }
import sbt.Keys._

/** Sbt plugin for using MiMa. */
object MimaPlugin extends AutoPlugin {
Expand All @@ -17,9 +17,10 @@ object MimaPlugin extends AutoPlugin {
/** Just configures MiMa to compare previous/current classfiles.*/
def mimaReportSettings: Seq[Setting[_]] = Seq(
mimaCheckDirection := "backward",
mimaFiltersDirectory := (sourceDirectory in Compile).value / "mima-filters",
mimaBinaryIssueFilters := Nil,
mimaBackwardIssueFilters := Map.empty,
mimaForwardIssueFilters := Map.empty,
mimaBackwardIssueFilters := SbtMima.issueFiltersFromFiles(mimaFiltersDirectory.value, "\\.(?:backward[s]?|both)\\.excludes".r, streams.value),
mimaForwardIssueFilters := SbtMima.issueFiltersFromFiles(mimaFiltersDirectory.value, "\\.(?:forward[s]?|both)\\.excludes".r, streams.value),
mimaFindBinaryIssues := {
if (mimaPreviousClassfiles.value.isEmpty) {
streams.value.log.info(s"${name.value}: previous-artifact not set, not analyzing binary compatibility")
Expand Down Expand Up @@ -87,4 +88,5 @@ object MimaPlugin extends AutoPlugin {
},
fullClasspath in mimaFindBinaryIssues := (fullClasspath in Compile).value
) ++ mimaReportSettings

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@ package plugin

import sbt._
import sbt.Keys.TaskStreams
import com.typesafe.tools.mima.core._
import com.typesafe.tools.mima.core.util.log.Logging
import scala.tools.nsc.util.JavaClassPath
import scala.tools.nsc.util.DirectoryClassPath
import scala.util.Try
import scala.io.Source
import scala.util._
import scala.util.matching._
import core.DefaultJavaContext

/** Wrapper on SBT logging for MiMa */
Expand Down Expand Up @@ -134,4 +137,57 @@ object SbtMima {
} yield file).headOption
optFile getOrElse sys.error("Could not resolve previous ABI: " + m)
}

def issueFiltersFromFiles(filtersDirectory: File, fileExtension: Regex, s: TaskStreams): Map[String, Seq[ProblemFilter]] = {
if (filtersDirectory.exists) loadMimaIgnoredProblems(filtersDirectory, fileExtension, s.log)
else Map.empty
}

def loadMimaIgnoredProblems(directory: File, fileExtension: Regex, logger: Logger): Map[String, Seq[ProblemFilter]] = {
val ExclusionPattern = """ProblemFilters\.exclude\[([^\]]+)\]\("([^"]+)"\)""".r

def findFiles(): Seq[(File, String)] = directory.listFiles().flatMap(f => fileExtension.findFirstIn(f.getName).map((f, _)))
def parseFile(file: File, extension: String): Either[Seq[Throwable], (String, Seq[ProblemFilter])] = {
val version = file.getName.dropRight(extension.size)

def parseLine(text: String, line: Int): Try[ProblemFilter] =
Try {
text match {
case ExclusionPattern(className, target) => ProblemFilters.exclude(className, target)
case x => throw new RuntimeException(s"Couldn't parse '$x'")
}
}.transform(Success(_), ex => Failure(new ParsingException(file, line, ex)))

val (excludes, failures) =
Source.fromFile(file)
.getLines()
.zipWithIndex
.filterNot { case (str, line) => str.trim.isEmpty || str.trim.startsWith("#") }
.map((parseLine _).tupled)
.partition(_.isSuccess)

if (failures.isEmpty) Right(version -> excludes.map(_.get).toSeq)
else Left(failures.map(_.failed.get).toSeq)
}

require(directory.exists(), s"Mima filter directory did not exist: ${directory.getAbsolutePath}")

val (mappings, failures) =
findFiles()
.map((parseFile _).tupled)
.partition(_.isRight)

if (failures.isEmpty)
mappings
.map(_.right.get)
.groupBy(_._1)
.map { case (version, filters) => version -> filters.flatMap(_._2) }
else {
failures.flatMap(_.left.get).foreach(ex => logger.error(ex.getMessage))

throw new RuntimeException(s"Loading Mima filters failed with ${failures.size} failures.")
}
}

case class ParsingException(file: File, line: Int, ex: Throwable) extends RuntimeException(s"Error while parsing $file, line $line: ${ex.getMessage}", ex)
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import com.typesafe.tools.mima.core._

mimaPreviousArtifacts := Set("0.0.1-SNAPSHOT") map { v => organization.value %% name.value % v }

val issueFilters = SettingKey[Map[String, Seq[ProblemFilter]]]("")
issueFilters := Map(
mimaBackwardIssueFilters := Map(
"0.0.1-SNAPSHOT" -> Seq(ProblemFilters.exclude[MissingMethodProblem]("A.bar"))
)
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
> set scalaSource in Compile := baseDirectory.value /"src" /"v1"
> set scalaSource in Compile := baseDirectory.value /"src" /"main" /"scala" /"v1"
> set version := s"0.0.1-SNAPSHOT"
> publishLocal

> set scalaSource in Compile := baseDirectory.value /"src" /"main"
> set scalaSource in Compile := baseDirectory.value /"src" /"main" /"scala" /"v2"
> set version := s"0.0.2-SNAPSHOT"

-> mimaReportBinaryIssues
> set mimaBackwardIssueFilters := issueFilters.value
# filters are set in the build file, so mima check should pass
> mimaReportBinaryIssues

# remove all filters so mima check fails
> set mimaBackwardIssueFilters := Map.empty
-> mimaReportBinaryIssues
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
mimaPreviousArtifacts := Set(organization.value %% name.value % "0.0.1-SNAPSHOT")
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % System.getProperty("plugin.version"))
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ProblemFilters.exclude[MissingMethodProblem]("A.foz")
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ProblemFilters.exclude[MissingMethodProblem]("A.bar")
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ProblemFilters.exclude[MissingMethodProblem]("A.baz")
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
class A {
def foo = 1
def bar = foo
def baz = foo
def foz = foo
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
> set scalaSource in Compile := baseDirectory.value /"src" /"main" /"scala" /"v1"
> set version := s"0.0.1-SNAPSHOT"
> publishLocal

> set scalaSource in Compile := baseDirectory.value /"src" /"main" /"scala" /"v2"
> set version := s"0.0.2-SNAPSHOT"

# filters are read from file, so mima check should pass
> mimaReportBinaryIssues

# remove all filters so mima check fails
> set mimaBackwardIssueFilters := Map.empty
-> mimaReportBinaryIssues
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import com.typesafe.tools.mima.core._

mimaPreviousArtifacts := Set("0.0.1-SNAPSHOT", "0.0.2-SNAPSHOT") map { v => organization.value %% name.value % v }

val issueFilters = SettingKey[Map[String, Seq[ProblemFilter]]]("")
issueFilters := Map(
mimaBackwardIssueFilters := Map(
"0.0.1-SNAPSHOT" -> Seq(ProblemFilters.exclude[MissingMethodProblem]("A.fooBar")),
"0.0.2-SNAPSHOT" -> Seq(ProblemFilters.exclude[MissingMethodProblem]("A.bar"))
)
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
> set scalaSource in Compile := baseDirectory.value /"src" /"v1"
> set scalaSource in Compile := baseDirectory.value /"src" /"main" /"scala" /"v1"
> set version := s"0.0.1-SNAPSHOT"
> publishLocal

> set scalaSource in Compile := baseDirectory.value /"src" /"v2"
> set scalaSource in Compile := baseDirectory.value /"src" /"main" /"scala" /"v2"
> set version := s"0.0.2-SNAPSHOT"
> publishLocal

> set scalaSource in Compile := baseDirectory.value /"src" /"main"
> set scalaSource in Compile := baseDirectory.value /"src" /"main" /"scala" /"v2"
> set version := s"0.0.3-SNAPSHOT"

-> mimaReportBinaryIssues
> set mimaBackwardIssueFilters := issueFilters.value
# filters are set in the build file, so mima check should pass
> mimaReportBinaryIssues

# remove all filters so mima check fails
> set mimaBackwardIssueFilters := Map.empty
-> mimaReportBinaryIssues
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
mimaCheckDirection := "forward"
mimaPreviousArtifacts := Set(organization.value %% name.value % "0.0.1-SNAPSHOT")
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % System.getProperty("plugin.version"))
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ProblemFilters.exclude[MissingMethodProblem]("A.baz")
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ProblemFilters.exclude[MissingMethodProblem]("A.foz")
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ProblemFilters.exclude[MissingMethodProblem]("A.bar")
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
class A {
def foo = 1
def bar = foo
def baz = foo
def foz = foo
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
> set scalaSource in Compile := baseDirectory.value /"src" /"main" /"scala" /"v1"
> set version := s"0.0.1-SNAPSHOT"
> publishLocal

> set scalaSource in Compile := baseDirectory.value /"src" /"main" /"scala" /"v2"
> set version := s"0.0.2-SNAPSHOT"

# filters are read from file, so mima check should pass
> mimaReportBinaryIssues

# remove all filters so mima check fails
> set mimaForwardIssueFilters := Map.empty
-> mimaReportBinaryIssues
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@ import com.typesafe.tools.mima.core._

mimaCheckDirection := "forward"
mimaPreviousArtifacts := Set("0.0.1-SNAPSHOT", "0.0.2-SNAPSHOT") map { v => organization.value %% name.value % v }

val issueFilters = SettingKey[Map[String, Seq[ProblemFilter]]]("")
issueFilters := Map(
mimaForwardIssueFilters := Map(
"0.0.1-SNAPSHOT" -> Seq(ProblemFilters.exclude[MissingMethodProblem]("A.bar")),
"0.0.2-SNAPSHOT" -> Seq(ProblemFilters.exclude[MissingMethodProblem]("A.fooBar"))
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
class A {
def foo = 1
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
> set scalaSource in Compile := baseDirectory.value /"src" /"v1"
> set scalaSource in Compile := baseDirectory.value /"src" /"main" /"scala" /"v1"
> set version := s"0.0.1-SNAPSHOT"
> publishLocal

> set scalaSource in Compile := baseDirectory.value /"src" /"v2"
> set scalaSource in Compile := baseDirectory.value /"src" /"main" /"scala" /"v2"
> set version := s"0.0.2-SNAPSHOT"
> publishLocal

> set scalaSource in Compile := baseDirectory.value /"src" /"main"
> set scalaSource in Compile := baseDirectory.value /"src" /"main" /"scala" /"v3"
> set version := s"0.0.3-SNAPSHOT"

-> mimaReportBinaryIssues
> set mimaForwardIssueFilters := issueFilters.value
# filters are set in the build file, so mima check should pass
> mimaReportBinaryIssues

# remove all filters so mima check fails
> set mimaForwardIssueFilters := Map.empty
-> mimaReportBinaryIssues
4 changes: 1 addition & 3 deletions sbtplugin/src/sbt-test/sbt-mima-plugin/minimal/build.sbt
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import com.typesafe.tools.mima.core._

mimaPreviousArtifacts := Set(organization.value %% name.value % "0.0.1-SNAPSHOT")

val issueFilters = SettingKey[Seq[ProblemFilter]]("")
issueFilters := Seq(
mimaBinaryIssueFilters := Seq(
ProblemFilters.exclude[MissingMethodProblem]("A.bar")
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
class A {
def foo = 1
}
11 changes: 7 additions & 4 deletions sbtplugin/src/sbt-test/sbt-mima-plugin/minimal/test
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
> set scalaSource in Compile := baseDirectory.value /"src" /"v1"
> set scalaSource in Compile := baseDirectory.value /"src" /"main" /"scala" /"v1"
> set version := s"0.0.1-SNAPSHOT"
> publishLocal

> set scalaSource in Compile := baseDirectory.value /"src" /"main"
> set scalaSource in Compile := baseDirectory.value /"src" /"main" /"scala" /"v2"
> set version := s"0.0.2-SNAPSHOT"

-> mimaReportBinaryIssues
> set mimaBinaryIssueFilters := issueFilters.value
# filters are set in the build file, so mima check should pass
> mimaReportBinaryIssues

# remove all filters so mima check fails
> set mimaBinaryIssueFilters := Seq.empty
-> mimaReportBinaryIssues

0 comments on commit 3bb5840

Please sign in to comment.