diff --git a/core/src/main/scala/com/typesafe/tools/mima/core/Problems.scala b/core/src/main/scala/com/typesafe/tools/mima/core/Problems.scala index 9403d15a..8fee9104 100644 --- a/core/src/main/scala/com/typesafe/tools/mima/core/Problems.scala +++ b/core/src/main/scala/com/typesafe/tools/mima/core/Problems.scala @@ -1,12 +1,5 @@ package com.typesafe.tools.mima.core -object Problem { - object ClassVersion extends Enumeration { - val New = Value("new") - val Old = Value("old") - } -} - trait ProblemRef { type Ref def ref: Ref @@ -35,8 +28,12 @@ trait MemberRef extends ProblemRef { } sealed abstract class Problem extends ProblemRef { - var affectedVersion = Problem.ClassVersion.New - def description: String + // each description accepts a name for the affected files, + // and generates the corresponding diagnostic message. + // For backward checking, the affected version is "current", + // while for forward checking it could be "other" or "previous", + // for example. + def description: String => String } abstract class TemplateProblem(override val ref: ClassInfo) extends Problem with TemplateRef { @@ -48,87 +45,97 @@ abstract class MemberProblem(override val ref: MemberInfo) extends Problem with } case class MissingFieldProblem(oldfld: MemberInfo) extends MemberProblem(oldfld) { - def description = oldfld.fieldString + " does not have a correspondent in " + affectedVersion + " version" + def description = affectedVersion => oldfld.fieldString + " does not have a correspondent in " + affectedVersion + " version" } case class MissingMethodProblem(meth: MemberInfo) extends MemberProblem(meth) { - def description = (if (meth.isDeferred && !meth.owner.isTrait) "abstract " else "") + meth.methodString + " does not have a correspondent in " + affectedVersion + " version" + def description = affectedVersion => (if (meth.isDeferred && !meth.owner.isTrait) "abstract " else "") + meth.methodString + " does not have a correspondent in " + affectedVersion + " version" +} + +case class ReversedMissingMethodProblem(meth: MemberInfo) extends MemberProblem(meth) { + def description = affectedVersion => (if (meth.isDeferred && !meth.owner.isTrait) "abstract " else "") + meth.methodString + " is present only in " + affectedVersion + " version" } case class UpdateForwarderBodyProblem(meth: MemberInfo) extends MemberProblem(meth) { assert(meth.owner.isTrait) assert(meth.owner.hasStaticImpl(meth)) - def description = "classes mixing " + meth.owner.fullName + " needs to update body of " + meth.shortMethodString + def description = affectedVersion => "in " + affectedVersion + " version, classes mixing " + meth.owner.fullName + " needs to update body of " + meth.shortMethodString } case class MissingClassProblem(oldclazz: ClassInfo) extends TemplateProblem(oldclazz) { - def description = oldclazz.classString + " does not have a correspondent in " + affectedVersion + " version" + def description = affectedVersion => oldclazz.classString + " does not have a correspondent in " + affectedVersion + " version" } case class AbstractClassProblem(oldclazz: ClassInfo) extends TemplateProblem(oldclazz) { - def description = oldclazz.classString + " was concrete; is declared abstract in " + affectedVersion + " version" + def description = affectedVersion => oldclazz.classString + " was concrete; is declared abstract in " + affectedVersion + " version" } case class FinalClassProblem(oldclazz: ClassInfo) extends TemplateProblem(oldclazz) { - def description = oldclazz.classString + " is declared final in " + affectedVersion + " version" + def description = affectedVersion => oldclazz.classString + " is declared final in " + affectedVersion + " version" } case class FinalMethodProblem(newmemb: MemberInfo) extends MemberProblem(newmemb) { - def description = newmemb.methodString + " is declared final in " + affectedVersion + " version" + def description = affectedVersion => newmemb.methodString + " is declared final in " + affectedVersion + " version" } case class IncompatibleFieldTypeProblem(oldfld: MemberInfo, newfld: MemberInfo) extends MemberProblem(oldfld) { - def description = newfld.fieldString + "'s type has changed; was: " + oldfld.tpe + ", is now: " + newfld.tpe + def description = affectedVersion => newfld.fieldString + "'s type is different in " + affectedVersion + " version, where it is: " + newfld.tpe + " rather than: " + oldfld.tpe } case class IncompatibleMethTypeProblem(oldmeth: MemberInfo, newmeths: List[MemberInfo]) extends MemberProblem(oldmeth) { - def description = { + def description = affectedVersion => { oldmeth.methodString + (if (newmeths.tail.isEmpty) - "'s type has changed; was " + oldmeth.tpe + ", is now: " + newmeths.head.tpe + "'s type is different in " + affectedVersion + " version, where it is " + newmeths.head.tpe + " instead of " + oldmeth.tpe else - " does not have a correspondent with same parameter signature among " + + " in " + affectedVersion + " version does not have a correspondent with same parameter signature among " + (newmeths map (_.tpe) mkString ", ")) } } case class IncompatibleResultTypeProblem(oldmeth: MemberInfo, newmeth: MemberInfo) extends MemberProblem(oldmeth) { - def description = { - oldmeth.methodString + " has now a different result type; was: " + - oldmeth.tpe.resultType + ", is now: " + newmeth.tpe.resultType + def description = affectedVersion => { + oldmeth.methodString + " has a different result type in " + affectedVersion + " version, where it is " + newmeth.tpe.resultType + + " rather than " + oldmeth.tpe.resultType } } +// In some older code within Mima, the affectedVersion could be reversed. We split AbstractMethodProblem and MissingMethodProblem +// into two, in case the affected version is the other one, rather than the current one. (reversed if forward check). case class AbstractMethodProblem(newmeth: MemberInfo) extends MemberProblem(newmeth) { - def description = "abstract " + newmeth.methodString + " does not have a correspondent in " + affectedVersion + " version" + def description = affectedVersion => "abstract " + newmeth.methodString + " does not have a correspondent in " + affectedVersion + " version" +} + +case class ReversedAbstractMethodProblem(newmeth: MemberInfo) extends MemberProblem(newmeth) { + def description = affectedVersion => "in " + affectedVersion + " version there is abstract " + newmeth.methodString + ", which does not have a correspondent" } case class IncompatibleTemplateDefProblem(oldclazz: ClassInfo, newclazz: ClassInfo) extends TemplateProblem(oldclazz) { - def description = { - "declaration of " + oldclazz.description + " has changed to " + newclazz.description + - " in new version; changing " + oldclazz.declarationPrefix + " to " + newclazz.declarationPrefix + " breaks client code" + def description = affectedVersion => { + "declaration of " + oldclazz.description + " is " + newclazz.description + + " in " + affectedVersion + " version; changing " + oldclazz.declarationPrefix + " to " + newclazz.declarationPrefix + " breaks client code" } } case class MissingTypesProblem(newclazz: ClassInfo, missing: Iterable[ClassInfo]) extends TemplateProblem(newclazz) { - def description = "the type hierarchy of " + newclazz.description + " has changed in new version. " + + def description = affectedVersion => "the type hierarchy of " + newclazz.description + " is different in " + affectedVersion + " version. " + "Missing types " + missing.map(_.fullName).mkString("{", ",", "}") } case class CyclicTypeReferenceProblem(clz: ClassInfo) extends TemplateProblem(clz) { - def description = { - "the type hierarchy of " + clz.description + " has changed in new version. Type " + clz.bytecodeName + " appears to be a subtype of itself" + def description = affectedVersion => { + "the type hierarchy of " + clz.description + " is different in " + affectedVersion + " version. Type " + clz.bytecodeName + " appears to be a subtype of itself" } } case class InaccessibleFieldProblem(newfld: MemberInfo) extends MemberProblem(newfld) { - def description = newfld.fieldString + " was public; is inaccessible in " + affectedVersion + " version" + def description = affectedVersion => newfld.fieldString + " is inaccessible in " + affectedVersion + " version, it must be public." } case class InaccessibleMethodProblem(newmeth: MemberInfo) extends MemberProblem(newmeth) { - def description = newmeth.methodString + " was public; is inaccessible in " + affectedVersion + " version" + def description = affectedVersion => newmeth.methodString + " is inaccessible in " + affectedVersion + " version, it must be public." } case class InaccessibleClassProblem(newclazz: ClassInfo) extends TemplateProblem(newclazz) { - def description = newclazz.classString + " was public; is inaccessible in " + affectedVersion + " version" + def description = affectedVersion => newclazz.classString + " is inaccessible in " + affectedVersion + " version, it must be public." } diff --git a/reporter/functional-tests/src/main/scala/com/typesafe/tools/mima/lib/CollectProblemsTest.scala b/reporter/functional-tests/src/main/scala/com/typesafe/tools/mima/lib/CollectProblemsTest.scala index 8be24445..1bfa19ce 100644 --- a/reporter/functional-tests/src/main/scala/com/typesafe/tools/mima/lib/CollectProblemsTest.scala +++ b/reporter/functional-tests/src/main/scala/com/typesafe/tools/mima/lib/CollectProblemsTest.scala @@ -18,7 +18,7 @@ class CollectProblemsTest { val mima = new MiMaLib(Config.baseClassPath) // SUT - val problems = mima.collectProblems(oldJarPath, newJarPath).map(_.description) + val problems = mima.collectProblems(oldJarPath, newJarPath).map(_.description("new")) // load oracle var expectedProblems = Source.fromFile(oraclePath).getLines.toList diff --git a/reporter/functional-tests/src/test/case-class-concrete-becomes-abstract-nok/problems.txt b/reporter/functional-tests/src/test/case-class-concrete-becomes-abstract-nok/problems.txt index 96494cee..511886c1 100644 --- a/reporter/functional-tests/src/test/case-class-concrete-becomes-abstract-nok/problems.txt +++ b/reporter/functional-tests/src/test/case-class-concrete-becomes-abstract-nok/problems.txt @@ -1,3 +1,3 @@ class A was concrete; is declared abstract in new version method apply()A in object A does not have a correspondent in new version -the type hierarchy of object A has changed in new version. Missing types {scala.runtime.AbstractFunction0} +the type hierarchy of object A is different in new version. Missing types {scala.runtime.AbstractFunction0} diff --git a/reporter/functional-tests/src/test/class-becomes-trait-nok/problems.txt b/reporter/functional-tests/src/test/class-becomes-trait-nok/problems.txt index 70563078..44870cef 100644 --- a/reporter/functional-tests/src/test/class-becomes-trait-nok/problems.txt +++ b/reporter/functional-tests/src/test/class-becomes-trait-nok/problems.txt @@ -1 +1 @@ -declaration of class A has changed to trait A in new version; changing class to trait breaks client code \ No newline at end of file +declaration of class A is trait A in new version; changing class to trait breaks client code diff --git a/reporter/functional-tests/src/test/class-changed-val-type-in-new-version-nok/problems.txt b/reporter/functional-tests/src/test/class-changed-val-type-in-new-version-nok/problems.txt index 293fa1d1..f923d050 100644 --- a/reporter/functional-tests/src/test/class-changed-val-type-in-new-version-nok/problems.txt +++ b/reporter/functional-tests/src/test/class-changed-val-type-in-new-version-nok/problems.txt @@ -1 +1 @@ -method foo()Int in class A has now a different result type; was: Int, is now: java.lang.Object \ No newline at end of file +method foo()Int in class A has a different result type in new version, where it is java.lang.Object rather than Int diff --git a/reporter/functional-tests/src/test/class-changed-var-type-in-new-version-nok/problems.txt b/reporter/functional-tests/src/test/class-changed-var-type-in-new-version-nok/problems.txt index 97b53475..16b18185 100644 --- a/reporter/functional-tests/src/test/class-changed-var-type-in-new-version-nok/problems.txt +++ b/reporter/functional-tests/src/test/class-changed-var-type-in-new-version-nok/problems.txt @@ -1,2 +1,2 @@ -method foo()Int in class A has now a different result type; was: Int, is now: java.lang.Object -method foo_=(Int)Unit in class A's type has changed; was (Int)Unit, is now: (java.lang.Object)Unit \ No newline at end of file +method foo()Int in class A has a different result type in new version, where it is java.lang.Object rather than Int +method foo_=(Int)Unit in class A's type is different in new version, where it is (java.lang.Object)Unit instead of (Int)Unit diff --git a/reporter/functional-tests/src/test/class-method-abstract-override-of-concrete-superclass-method-nok/problems.txt b/reporter/functional-tests/src/test/class-method-abstract-override-of-concrete-superclass-method-nok/problems.txt index f46b53e4..433e4360 100644 --- a/reporter/functional-tests/src/test/class-method-abstract-override-of-concrete-superclass-method-nok/problems.txt +++ b/reporter/functional-tests/src/test/class-method-abstract-override-of-concrete-superclass-method-nok/problems.txt @@ -1 +1 @@ -abstract method foo()Int in class B does not have a correspondent in old version \ No newline at end of file +in new version there is abstract method foo()Int in class B, which does not have a correspondent diff --git a/reporter/functional-tests/src/test/class-method-abstract-override-of-concrete-supertrait-method-nok/problems.txt b/reporter/functional-tests/src/test/class-method-abstract-override-of-concrete-supertrait-method-nok/problems.txt index 37607b47..e5966fa7 100644 --- a/reporter/functional-tests/src/test/class-method-abstract-override-of-concrete-supertrait-method-nok/problems.txt +++ b/reporter/functional-tests/src/test/class-method-abstract-override-of-concrete-supertrait-method-nok/problems.txt @@ -1,2 +1,2 @@ abstract method foo()Int in class B does not have a correspondent in new version -abstract method foo()Int in class B does not have a correspondent in old version \ No newline at end of file +in new version there is abstract method foo()Int in class B, which does not have a correspondent diff --git a/reporter/functional-tests/src/test/class-method-changed-parameter-type-nok/problems.txt b/reporter/functional-tests/src/test/class-method-changed-parameter-type-nok/problems.txt index 58667dd3..a7f9bba7 100644 --- a/reporter/functional-tests/src/test/class-method-changed-parameter-type-nok/problems.txt +++ b/reporter/functional-tests/src/test/class-method-changed-parameter-type-nok/problems.txt @@ -1 +1 @@ -method foo(Int)Int in class A's type has changed; was (Int)Int, is now: (java.lang.Object)java.lang.Object \ No newline at end of file +method foo(Int)Int in class A's type is different in new version, where it is (java.lang.Object)java.lang.Object instead of (Int)Int diff --git a/reporter/functional-tests/src/test/class-method-changed-parameters2-nok/problems.txt b/reporter/functional-tests/src/test/class-method-changed-parameters2-nok/problems.txt index aff1ef01..31eeb810 100644 --- a/reporter/functional-tests/src/test/class-method-changed-parameters2-nok/problems.txt +++ b/reporter/functional-tests/src/test/class-method-changed-parameters2-nok/problems.txt @@ -1 +1 @@ -method foo(java.lang.Object,Int)Int in class A's type has changed; was (java.lang.Object,Int)Int, is now: (Int,java.lang.Object)Int \ No newline at end of file +method foo(java.lang.Object,Int)Int in class A's type is different in new version, where it is (Int,java.lang.Object)Int instead of (java.lang.Object,Int)Int diff --git a/reporter/functional-tests/src/test/class-narrowing-method-type-in-new-version-ok/problems.txt b/reporter/functional-tests/src/test/class-narrowing-method-type-in-new-version-ok/problems.txt index 974e9762..0ed7998c 100644 --- a/reporter/functional-tests/src/test/class-narrowing-method-type-in-new-version-ok/problems.txt +++ b/reporter/functional-tests/src/test/class-narrowing-method-type-in-new-version-ok/problems.txt @@ -1 +1 @@ -method foo()Foo in class A has now a different result type; was: Foo, is now: Bar \ No newline at end of file +method foo()Foo in class A has a different result type in new version, where it is Bar rather than Foo diff --git a/reporter/functional-tests/src/test/class-widening-method-type-in-new-version-nok/problems.txt b/reporter/functional-tests/src/test/class-widening-method-type-in-new-version-nok/problems.txt index 293fa1d1..f923d050 100644 --- a/reporter/functional-tests/src/test/class-widening-method-type-in-new-version-nok/problems.txt +++ b/reporter/functional-tests/src/test/class-widening-method-type-in-new-version-nok/problems.txt @@ -1 +1 @@ -method foo()Int in class A has now a different result type; was: Int, is now: java.lang.Object \ No newline at end of file +method foo()Int in class A has a different result type in new version, where it is java.lang.Object rather than Int diff --git a/reporter/functional-tests/src/test/trait-abstract-method-becomes-concrete2-ok/problems.txt b/reporter/functional-tests/src/test/trait-abstract-method-becomes-concrete2-ok/problems.txt index 5cce2c8d..12bd1a8c 100644 --- a/reporter/functional-tests/src/test/trait-abstract-method-becomes-concrete2-ok/problems.txt +++ b/reporter/functional-tests/src/test/trait-abstract-method-becomes-concrete2-ok/problems.txt @@ -1 +1 @@ -method foo()Int in trait A1 does not have a correspondent in old version \ No newline at end of file +method foo()Int in trait A1 is present only in new version diff --git a/reporter/functional-tests/src/test/trait-abstract-val-become-concrete-ok/problems.txt b/reporter/functional-tests/src/test/trait-abstract-val-become-concrete-ok/problems.txt index e5b271aa..ed6a8cba 100644 --- a/reporter/functional-tests/src/test/trait-abstract-val-become-concrete-ok/problems.txt +++ b/reporter/functional-tests/src/test/trait-abstract-val-become-concrete-ok/problems.txt @@ -1 +1 @@ -synthetic method A$_setter_$foo_=(Int)Unit in trait A does not have a correspondent in old version \ No newline at end of file +synthetic method A$_setter_$foo_=(Int)Unit in trait A is present only in new version diff --git a/reporter/functional-tests/src/test/trait-added-method-in-new-version-nok/problems.txt b/reporter/functional-tests/src/test/trait-added-method-in-new-version-nok/problems.txt index e84b2da0..a0f46e67 100644 --- a/reporter/functional-tests/src/test/trait-added-method-in-new-version-nok/problems.txt +++ b/reporter/functional-tests/src/test/trait-added-method-in-new-version-nok/problems.txt @@ -1 +1 @@ -method bar()Int in trait A does not have a correspondent in old version \ No newline at end of file +method bar()Int in trait A is present only in new version diff --git a/reporter/functional-tests/src/test/trait-added-val-in-new-version-nok/problems.txt b/reporter/functional-tests/src/test/trait-added-val-in-new-version-nok/problems.txt index b9f26f53..4e2d072d 100644 --- a/reporter/functional-tests/src/test/trait-added-val-in-new-version-nok/problems.txt +++ b/reporter/functional-tests/src/test/trait-added-val-in-new-version-nok/problems.txt @@ -1,2 +1,2 @@ -synthetic method A$_setter_|_=(Int)Unit in trait A does not have a correspondent in old version -method bar()Int in trait A does not have a correspondent in old version \ No newline at end of file +synthetic method A$_setter_|_=(Int)Unit in trait A is present only in new version +method bar()Int in trait A is present only in new version diff --git a/reporter/functional-tests/src/test/trait-added-var-in-new-version-nok/problems.txt b/reporter/functional-tests/src/test/trait-added-var-in-new-version-nok/problems.txt index e816bf06..df963db6 100644 --- a/reporter/functional-tests/src/test/trait-added-var-in-new-version-nok/problems.txt +++ b/reporter/functional-tests/src/test/trait-added-var-in-new-version-nok/problems.txt @@ -1,2 +1,2 @@ -method bar_=(Int)Unit in trait A does not have a correspondent in old version -method bar()Int in trait A does not have a correspondent in old version \ No newline at end of file +method bar_=(Int)Unit in trait A is present only in new version +method bar()Int in trait A is present only in new version diff --git a/reporter/functional-tests/src/test/trait-deleting-concrete-methods-is-nok/problems.txt b/reporter/functional-tests/src/test/trait-deleting-concrete-methods-is-nok/problems.txt index 2a359255..d0972080 100644 --- a/reporter/functional-tests/src/test/trait-deleting-concrete-methods-is-nok/problems.txt +++ b/reporter/functional-tests/src/test/trait-deleting-concrete-methods-is-nok/problems.txt @@ -1 +1 @@ -classes mixing B needs to update body of method foo()Int \ No newline at end of file +in new version, classes mixing B needs to update body of method foo()Int diff --git a/reporter/functional-tests/src/test/trait-method-overloading-is-ok/problems.txt b/reporter/functional-tests/src/test/trait-method-overloading-is-ok/problems.txt index a23ed469..89ceb3d4 100644 --- a/reporter/functional-tests/src/test/trait-method-overloading-is-ok/problems.txt +++ b/reporter/functional-tests/src/test/trait-method-overloading-is-ok/problems.txt @@ -1 +1 @@ -method foo(java.lang.Object)Int in trait A does not have a correspondent in old version \ No newline at end of file +method foo(java.lang.Object)Int in trait A is present only in new version diff --git a/reporter/functional-tests/src/test/trait-moving-methods-nok/problems.txt b/reporter/functional-tests/src/test/trait-moving-methods-nok/problems.txt index af463915..f26c16e7 100644 --- a/reporter/functional-tests/src/test/trait-moving-methods-nok/problems.txt +++ b/reporter/functional-tests/src/test/trait-moving-methods-nok/problems.txt @@ -1,2 +1,2 @@ method foo()Int in trait A does not have a correspondent in new version -method foo()Int in trait B does not have a correspondent in old version \ No newline at end of file +method foo()Int in trait B is present only in new version diff --git a/reporter/functional-tests/src/test/trait-pushing-up-abstract-methods-is-nok/problems.txt b/reporter/functional-tests/src/test/trait-pushing-up-abstract-methods-is-nok/problems.txt index 3db647a0..f69d8cde 100644 --- a/reporter/functional-tests/src/test/trait-pushing-up-abstract-methods-is-nok/problems.txt +++ b/reporter/functional-tests/src/test/trait-pushing-up-abstract-methods-is-nok/problems.txt @@ -1 +1 @@ -abstract method foo()Int in interface A does not have a correspondent in old version \ No newline at end of file +abstract method foo()Int in interface A is present only in new version diff --git a/reporter/functional-tests/src/test/trait-pushing-up-concrete-methods-is-nok/problems.txt b/reporter/functional-tests/src/test/trait-pushing-up-concrete-methods-is-nok/problems.txt index d4670241..77b28e44 100644 --- a/reporter/functional-tests/src/test/trait-pushing-up-concrete-methods-is-nok/problems.txt +++ b/reporter/functional-tests/src/test/trait-pushing-up-concrete-methods-is-nok/problems.txt @@ -1,2 +1,2 @@ -method foo()Int in trait A does not have a correspondent in old version -classes mixing B needs to update body of method foo()Int \ No newline at end of file +method foo()Int in trait A is present only in new version +in new version, classes mixing B needs to update body of method foo()Int diff --git a/reporter/functional-tests/src/test/type-parameters-change-breaks-method-signature-nok/problems.txt b/reporter/functional-tests/src/test/type-parameters-change-breaks-method-signature-nok/problems.txt index dcf17611..63465e12 100644 --- a/reporter/functional-tests/src/test/type-parameters-change-breaks-method-signature-nok/problems.txt +++ b/reporter/functional-tests/src/test/type-parameters-change-breaks-method-signature-nok/problems.txt @@ -1 +1 @@ -method contains(NodeImpl)Boolean in class Tree's type has changed; was (NodeImpl)Boolean, is now: (Node)Boolean \ No newline at end of file +method contains(NodeImpl)Boolean in class Tree's type is different in new version, where it is (Node)Boolean instead of (NodeImpl)Boolean diff --git a/reporter/src/main/scala/com/typesafe/tools/mima/cli/Main.scala b/reporter/src/main/scala/com/typesafe/tools/mima/cli/Main.scala index f4b7f57a..dd0a5315 100644 --- a/reporter/src/main/scala/com/typesafe/tools/mima/cli/Main.scala +++ b/reporter/src/main/scala/com/typesafe/tools/mima/cli/Main.scala @@ -20,8 +20,11 @@ trait MimaSpec extends Spec with Meta.StdOpts with Interpolation { val currentfile = "curr" / "Current classpath/jar for binary compatibility testing." defaultTo "" heading("optional settings:") val classpath = "classpath" / "an optional classpath setting" defaultTo System.getProperty("java.class.path") - val problemFilters = "filters" / "an optional problem filters configuration file" --| + val problemFilters = "filters" / "an optional problem filters configuration file, applied to both backward and forward checking" --| + val backwardFilters = "backward-filters" / "an optional problem filters configuration file, only applied to backward checking" --| + val forwardFilters = "forward-filters" / "an optional problem filters configuration file, only applied to forward checking" --| val generateFilters = "generate-filters" / "generate filters definition for displayed problems" --? + val direction = "direction" / "check direction, default is \"backward\", but can also be \"forward\" or \"both\"" --| } object MimaSpec extends MimaSpec with Property { @@ -63,7 +66,7 @@ class Main(args: List[String]) extends { } /** Converts a problem to a human-readable mapped string. */ - private def printProblem(p: core.Problem): String = { + private def printProblem(p: core.Problem, affected: String): String = { def wrap(words: Seq[String], result: List[String] = Nil): Seq[String] = if(words.isEmpty) result.reverse else { @@ -78,7 +81,7 @@ class Main(args: List[String]) extends { wrap(rest, line :: result) } def wrapString(s: String) = wrap(s split "\\s") - wrapString(" * " + p.description) mkString "\n " + wrapString(" * " + p.description(affected)) mkString "\n " } private def loadFilters(configFile: File): Seq[ProblemFilter] = { @@ -98,9 +101,9 @@ class Main(args: List[String]) extends { } } - private def printGeneratedFilters(errors: Seq[core.Problem]): Unit = { + private def printGeneratedFilters(errors: Seq[core.Problem], direction: String): Unit = { val errorsFilterConfig = ProblemFiltersConfig.problemsToProblemFilterConfig(errors) - val header = "Generated filter config definition" + val header = "\nGenerated " + direction + " filter config definition" println(header) println(Seq.fill(header.length)("=") mkString "") import com.typesafe.config._ @@ -108,22 +111,38 @@ class Main(args: List[String]) extends { println(errorsFilterConfig.root.render(renderOptions)) } + def parseFilters(os:Option[String]) = os.toSeq.map(filePath => loadFilters(new File(filePath))).flatten + def run(): Int = { val mima = makeMima - val foundProblems = mima.collectProblems(prevfile, currentfile) - val filters = problemFilters.toSeq.map(filePath => loadFilters(new File(filePath))).flatten - def isReported(problem: core.Problem) = filters.forall(filter => filter(problem)) - val errors = foundProblems.filter(isReported) - val header = "Found " + errors.size + " binary incompatibilities" + { - val filteredOutSize = foundProblems.size - errors.size + val backwardProblems = (direction getOrElse Nil) match { + case "backward" | "backwards" | "both" => mima.collectProblems(prevfile, currentfile) + case _ => Nil + } + val forwardProblems = (direction getOrElse Nil) match { + case "forward" | "forwards" | "both" => mima.collectProblems(currentfile, prevfile) + case _ => Nil + } + val bothFilters = parseFilters(problemFilters) + val backFilters = bothFilters ++ parseFilters(backwardFilters) + val forwFilters = bothFilters ++ parseFilters(forwardFilters) + def isReported(problem: core.Problem, filters: Seq[core.Problem => Boolean]) = filters.forall(filter => filter(problem)) + val backErrors = backwardProblems.filter(isReported(_,backFilters)) + val forwErrors = forwardProblems.filter(isReported(_,forwFilters)) + val errorsSize = backErrors.size + forwErrors.size + val header = "Found " + errorsSize + " binary incompatibilities" + { + val filteredOutSize = backwardProblems.size + forwardProblems.size - errorsSize if (filteredOutSize > 0) " (" + filteredOutSize + " were filtered out)" else "" } println(header) println(Seq.fill(header.length)("=") mkString "") - errors map printProblem foreach println - if (generateFilters) - printGeneratedFilters(errors) - errors.size + backErrors map {p:core.Problem => printProblem(p,"current")} foreach println + forwErrors map {p:core.Problem => printProblem(p,"other")} foreach println + if (generateFilters) { + printGeneratedFilters(backErrors, "backward") + printGeneratedFilters(forwErrors, "forward") + } + errorsSize } } diff --git a/reporter/src/main/scala/com/typesafe/tools/mima/lib/analyze/Analyzer.scala b/reporter/src/main/scala/com/typesafe/tools/mima/lib/analyze/Analyzer.scala index 0d8fd0cd..65e5a77c 100644 --- a/reporter/src/main/scala/com/typesafe/tools/mima/lib/analyze/Analyzer.scala +++ b/reporter/src/main/scala/com/typesafe/tools/mima/lib/analyze/Analyzer.scala @@ -81,14 +81,10 @@ private[analyze] class ClassAnalyzer extends Analyzer { for (newAbstrMeth <- newclazz.deferredMethods) yield { oldclazz.lookupMethods(newAbstrMeth.bytecodeName).find(_.sig == newAbstrMeth.sig) match { case None => - val p = MissingMethodProblem(newAbstrMeth) - p.affectedVersion = Problem.ClassVersion.Old - Some(p) + Some(ReversedMissingMethodProblem(newAbstrMeth)) case Some(found) => if(found.isConcrete) { - val p = AbstractMethodProblem(newAbstrMeth) - p.affectedVersion = Problem.ClassVersion.Old - Some(p) + Some(ReversedAbstractMethodProblem(newAbstrMeth)) } else None @@ -111,8 +107,7 @@ private[analyze] class TraitAnalyzer extends Analyzer { if (!oldclazz.lookupMethods(newmeth.bytecodeName).exists(_.sig == newmeth.sig)) { // this means that the method is brand new and therefore the implementation // has to be injected - val problem = MissingMethodProblem(newmeth) - problem.affectedVersion = Problem.ClassVersion.Old + val problem = ReversedMissingMethodProblem(newmeth) res += problem } // else a static implementation for the same method existed already, therefore @@ -126,8 +121,7 @@ private[analyze] class TraitAnalyzer extends Analyzer { oldmeths find (_.sig == newmeth.sig) match { case Some(oldmeth) => () case _ => - val problem = MissingMethodProblem(newmeth) - problem.affectedVersion = Problem.ClassVersion.Old + val problem = ReversedMissingMethodProblem(newmeth) res += problem } } diff --git a/sbtplugin/src/main/scala/com/typesafe/tools/mima/plugin/Keys.scala b/sbtplugin/src/main/scala/com/typesafe/tools/mima/plugin/Keys.scala index fcc6c2e9..73662cba 100644 --- a/sbtplugin/src/main/scala/com/typesafe/tools/mima/plugin/Keys.scala +++ b/sbtplugin/src/main/scala/com/typesafe/tools/mima/plugin/Keys.scala @@ -14,9 +14,14 @@ class BaseMimaKeys { final val mimaPreviousArtifacts = settingKey[Set[ModuleID]]("Previous released artifacts used to test binary compatibility.") final val mimaPreviousClassfiles = taskKey[Set[File]]("Directories or jars containing the previous class files used to test compatibility.") final val mimaCurrentClassfiles = taskKey[File]("Directory or jar containing the current class files used to test compatibility.") - final val mimaFindBinaryIssues = taskKey[List[(File, List[core.Problem])]]("A list of all binary incompatibilities between two files.") + final val mimaFindBinaryIssues = taskKey[List[(File, List[core.Problem], List[core.Problem])]]("A list of all backward and forward binary incompatibilities between two files.") final val mimaReportBinaryIssues = taskKey[Unit]("Logs all binary incompatibilities to the sbt console/logs.") - final val mimaBinaryIssueFilters = settingKey[Seq[core.ProblemFilter]]("A list of filters to apply to binary issues found.") + + final val mimaBinaryIssueFilters = settingKey[Seq[core.ProblemFilter]]("A list of filters to apply to binary issues found. Applies both to backward and forward binary compatibility checking.") + final val mimaBackwardIssueFilters = settingKey[Seq[core.ProblemFilter]]("A list of filters to apply to binary issues found. These filters only apply to backward compatibility checking.") + final val mimaForwardIssueFilters = settingKey[Seq[core.ProblemFilter]]("A list of filters to apply to binary issues found. These filters only apply to forward compatibility checking.") + + final val mimaCheckDirection = settingKey[String]("Compatibility checking direction; default is \"backward\", but can also be \"forward\" or \"both\".") @deprecated("Use mimaFailOnProblem", "0.1.9") final val failOnProblem = mimaFailOnProblem @deprecated("Use mimaPreviousArtifacts", "0.1.9") final val previousArtifacts = mimaPreviousArtifacts diff --git a/sbtplugin/src/main/scala/com/typesafe/tools/mima/plugin/MimaPlugin.scala b/sbtplugin/src/main/scala/com/typesafe/tools/mima/plugin/MimaPlugin.scala index 02d6f671..fc2b2cb1 100644 --- a/sbtplugin/src/main/scala/com/typesafe/tools/mima/plugin/MimaPlugin.scala +++ b/sbtplugin/src/main/scala/com/typesafe/tools/mima/plugin/MimaPlugin.scala @@ -17,22 +17,30 @@ object MimaPlugin extends AutoPlugin { /** Just configures MiMa to compare previous/current classfiles.*/ def mimaReportSettings: Seq[Setting[_]] = Seq( binaryIssueFilters := Nil, + mimaBackwardIssueFilters := Nil, + mimaForwardIssueFilters := Nil, findBinaryIssues := { if (previousClassfiles.value.isEmpty) { streams.value.log.info(s"${name.value}: previous-artifact not set, not analyzing binary compatibility") - List.empty[(File, List[core.Problem])] + List.empty[(File, List[core.Problem], List[core.Problem])] } else { - previousClassfiles.value.map(previous => (previous, SbtMima.runMima( - previous, - currentClassfiles.value, - (fullClasspath in findBinaryIssues).value, - streams.value - )))(scala.collection.breakOut) + previousClassfiles.value.map { previous => + val problems = SbtMima.runMima( + previous, + currentClassfiles.value, + (fullClasspath in findBinaryIssues).value, + mimaCheckDirection.value, + streams.value + ) + (previous, problems._1, problems._2) + }(scala.collection.breakOut) } }, - reportBinaryIssues <<= (findBinaryIssues, failOnProblem, binaryIssueFilters, streams, name) map SbtMima.reportErrors) - + reportBinaryIssues <<= (findBinaryIssues, failOnProblem, binaryIssueFilters, mimaBackwardIssueFilters, + mimaForwardIssueFilters, streams, name) map { (find, fail, bin, back, forw, s, n) => + SbtMima.reportErrors(find, fail, bin ++ back, bin ++ forw, s, n) + }) /** Setup mima with default settings, applicable for most projects. */ def mimaDefaultSettings: Seq[Setting[_]] = Seq( failOnProblem := true, diff --git a/sbtplugin/src/main/scala/com/typesafe/tools/mima/plugin/SbtMima.scala b/sbtplugin/src/main/scala/com/typesafe/tools/mima/plugin/SbtMima.scala index c791a6dc..271a3ef2 100644 --- a/sbtplugin/src/main/scala/com/typesafe/tools/mima/plugin/SbtMima.scala +++ b/sbtplugin/src/main/scala/com/typesafe/tools/mima/plugin/SbtMima.scala @@ -27,18 +27,30 @@ object SbtMima { new lib.MiMaLib(classpath, new SbtLogger(s)) } - /** Runs MiMa and returns a list of potential binary incompatibilities. */ - def runMima(prev: File, curr: File, cp: sbt.Keys.Classpath, s: TaskStreams): List[core.Problem] = - makeMima(cp, s).collectProblems(prev.getAbsolutePath, curr.getAbsolutePath) + /** Runs MiMa and returns a two lists of potential binary incompatibilities, + the first for backward compatibility checking, and the second for forward checking. */ + def runMima(prev: File, curr: File, cp: sbt.Keys.Classpath, + dir: String, s: TaskStreams): (List[core.Problem],List[core.Problem]) = { + val mimaLib = makeMima(cp, s) + (dir match { + case "backward" | "backwards" | "both" => mimaLib.collectProblems(prev.getAbsolutePath, curr.getAbsolutePath) + case _ => Nil + }, + dir match { + case "forward" | "forwards" | "both" => mimaLib.collectProblems(curr.getAbsolutePath, prev.getAbsolutePath) + case _ => Nil + }) + } /** Reports binary compatibility errors. * @param failOnProblem if true, fails the build on binary compatibility errors. */ - def reportErrors(problemsInFiles: List[(File, List[core.Problem])], failOnProblem: Boolean, filters: Seq[core.ProblemFilter], s: TaskStreams, projectName: String): Unit = { + def reportErrors(problemsInFiles: List[(File, List[core.Problem], List[core.Problem])], failOnProblem: Boolean, + backwardFilters: Seq[core.ProblemFilter], forwardFilters: Seq[core.ProblemFilter], s: TaskStreams, projectName: String): Unit = { // filters * found is n-squared, it's fixable in principle by special-casing known // filter types or something, not worth it most likely... - def isReported(problem: core.Problem) = filters forall { f => + def isReported(problem: core.Problem, filters: Seq[core.ProblemFilter]) = filters forall { f => if (f(problem)) { true } else { @@ -47,22 +59,24 @@ object SbtMima { } } - problemsInFiles foreach { case (file, found) => - val errors = found filter isReported + problemsInFiles foreach { case (file, backward, forward) => + val backErrors = backward filter (isReported(_, backwardFilters)) + val forwErrors = forward filter (isReported(_, forwardFilters)) - val filteredCount = found.size - errors.size + val filteredCount = backward.size + forward.size - backErrors.size - forwErrors.size val filteredNote = if (filteredCount > 0) " (filtered " + filteredCount + ")" else "" // TODO - Line wrapping an other magikz - def prettyPrint(p: core.Problem): String = { - " * " + p.description + p.howToFilter.map("\n filter with: " + _).getOrElse("") + def prettyPrint(p: core.Problem, affected: String): String = { + " * " + p.description(affected) + p.howToFilter.map("\n filter with: " + _).getOrElse("") } - s.log.info(s"$projectName: found ${errors.size} potential binary incompatibilities while checking against $file $filteredNote") - errors map prettyPrint foreach { p => - if (failOnProblem) s.log.error(p) - else s.log.warn(p) + s.log.info(s"$projectName: found ${backErrors.size+forwErrors.size} potential binary incompatibilities while checking against $file $filteredNote") + ((backErrors map {p: core.Problem => prettyPrint(p,"current")}) ++ + (forwErrors map {p: core.Problem => prettyPrint(p,"other")})) foreach { ps => + if (failOnProblem) s.log.error(ps) + else s.log.warn(ps) } - if (failOnProblem && !errors.isEmpty) sys.error(projectName + ": Binary compatibility check failed!") + if (failOnProblem && !backErrors.isEmpty && !forwErrors.isEmpty) sys.error(projectName + ": Binary compatibility check failed!") } } /** Resolves an artifact representing the previous abstract binary interface