Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[wip] new task to create exclusion files from issues #432

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,21 @@ object ProblemReporting {
versionedFilters: Map[String, Seq[ProblemFilter]]
)(problem: Problem): Boolean = {
// version string "x.y.z" is converted to an Int tuple (x, y, z) for comparison
val VersionRegex = """(\d+)\.?(\d+)?\.?(.*)?""".r
val versionPartToInt = (versionPart: String) =>
Try(versionPart.replace("x", Short.MaxValue.toString).filter(_.isDigit).toInt).getOrElse(0)
val versionOrdering = Ordering[(Int, Int, Int)].on[String] { case VersionRegex(x, y, z) =>
(versionPartToInt(x), versionPartToInt(y), versionPartToInt(z))
}

val versionMatchingFilters = versionedFilters
// get all filters that apply to given module version or any version after it
.collect { case (version, filters) if versionOrdering.gteq(version, version) => filters }
.flatten

(versionMatchingFilters.iterator ++ filters).forall(filter => filter(problem))
}

private[mima] val versionOrdering: Ordering[String] = {
val VersionRegex = """(\d+)\.?(\d+)?\.?(.*)?""".r
val versionPartToInt = (versionPart: String) =>
Try(versionPart.replace("x", Short.MaxValue.toString).filter(_.isDigit).toInt).getOrElse(0)

Ordering[(Int, Int, Int)].on[String] { case VersionRegex(x, y, z) =>
(versionPartToInt(x), versionPartToInt(y), versionPartToInt(z))
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,5 @@ class MimaKeys {
final val mimaForwardIssueFilters = taskKey[Map[String, Seq[ProblemFilter]]]("Filters to apply to binary issues found grouped by version of a module checked against. These filters only apply to forward compatibility checking.")
final val mimaFiltersDirectory = settingKey[File]("Directory containing issue filters.")

final val mimaCreateExclusionFile = taskKey[Seq[File]]("Creates an exclusion file for all the found issues")
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.typesafe.tools.mima
package plugin

import java.nio.charset.Charset

import sbt._
import sbt.Keys._

Expand Down Expand Up @@ -58,6 +60,7 @@ object MimaPlugin extends AutoPlugin {
mimaBackwardIssueFilters := SbtMima.issueFiltersFromFiles(mimaFiltersDirectory.value, "\\.(?:backward[s]?|both)\\.excludes".r, streams.value),
mimaForwardIssueFilters := SbtMima.issueFiltersFromFiles(mimaFiltersDirectory.value, "\\.(?:forward[s]?|both)\\.excludes".r, streams.value),
mimaFiltersDirectory := (sourceDirectory in Compile).value / "mima-filters",
mimaCreateExclusionFile := createExclusionFile.value
)

/** Setup MiMa with default settings, applicable for most projects. */
Expand Down Expand Up @@ -89,6 +92,71 @@ object MimaPlugin extends AutoPlugin {
}
}

private def createExclusionFile = Def.task {
import com.typesafe.tools.mima.core.ProblemReporting.versionOrdering

val log = streams.value.log
val highestVersion = mimaPreviousArtifacts.value.toSeq.maxBy(_.revision)(versionOrdering).revision
val prefix = sys.props.getOrElse("akka.http.mima.exclusion-template-prefix", "_generated")

val allExcludes =
mimaPreviousClassfiles.value.toSeq.flatMap {
case (module, file) =>
val (backward, forward) = SbtMima.runMima(
file,
mimaCurrentClassfiles.value,
(fullClasspath in mimaFindBinaryIssues).value,
mimaCheckDirection.value,
new SbtLogger(streams.value))

val filters = mimaBinaryIssueFilters.value
val backwardFilters = mimaBackwardIssueFilters.value
val forwardFilters = mimaForwardIssueFilters.value

def isReported(module: ModuleID, verionedFilters: Map[String, Seq[core.ProblemFilter]])(
problem: core.Problem) =
(verionedFilters.collect {
// get all filters that apply to given module version or any version after it
case f @ (version, filters) if versionOrdering.gteq(version, module.revision) => filters
}.flatten ++ filters).forall { f =>
f(problem)
}

val backErrors = backward.filter(isReported(module, backwardFilters))
val forwErrors = forward.filter(isReported(module, forwardFilters))

val filteredCount = backward.size + forward.size - backErrors.size - forwErrors.size
val filteredNote = if (filteredCount > 0) " (filtered " + filteredCount + ")" else ""

if (backErrors.size + forwErrors.size > 0) backErrors.flatMap(p => p.howToFilter.map((_, module.revision)))
else Nil
}

if (allExcludes.nonEmpty) {
val lines: List[String] =
"# Autogenerated MiMa filters, please pull branch and check validity, and add comments why they are needed etc." ::
"# Don't merge like this." :: "" ::
allExcludes
.groupBy(_._1)
.mapValues(_.map(_._2).toSet)
.groupBy(_._2)
.flatMap {
case (versionsAffected, exclusions) =>
val versions = versionsAffected.toSeq.sorted(versionOrdering)
s"# Incompatibilities against ${versions.mkString(", ")}" +: exclusions.keys.toVector.sorted :+ ""
}
.toList

val targetFile = new File(mimaFiltersDirectory.value, s"$highestVersion.backwards.excludes/$prefix.excludes")
targetFile.getParentFile.mkdirs()
IO.writeLines(targetFile, lines, utf8, false)
log.info(
s"Created ${targetFile.getPath} as a template to ignore pending mima issues. Remove `.template` suffix to activate.")
targetFile :: Nil
} else
Nil
}

// Used to differentiate unset mimaPreviousArtifacts from empty mimaPreviousArtifacts
private object NoPreviousArtifacts extends EmptySet[ModuleID]
private object NoPreviousClassfiles extends EmptyMap[ModuleID, File]
Expand Down Expand Up @@ -118,4 +186,5 @@ object MimaPlugin extends AutoPlugin {
override def apply(key: K) = throw new NoSuchElementException(s"key not found: $key")
}

private val utf8 = Charset.forName("utf-8")
}