-
Notifications
You must be signed in to change notification settings - Fork 71
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
3 changed files
with
251 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
133 changes: 133 additions & 0 deletions
133
cli/src/main/scala/com/typesafe/tools/mima/cli/MimaCli.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
package com.typesafe.tools.mima.cli | ||
|
||
import com.typesafe.tools.mima.lib.MiMaLib | ||
|
||
import java.io.File | ||
import scala.annotation.tailrec | ||
|
||
case class Main( | ||
classpath: Seq[File] = Nil, | ||
oldBinOpt: Option[File] = None, | ||
newBinOpt: Option[File] = None, | ||
formatter: ProblemFormatter = ProblemFormatter() | ||
) { | ||
|
||
def run(): Int = { | ||
val oldBin = oldBinOpt.getOrElse( | ||
throw new IllegalArgumentException("Old binary was not specified") | ||
) | ||
val newBin = newBinOpt.getOrElse( | ||
throw new IllegalArgumentException("New binary was not specified") | ||
) | ||
// TODO: should have some machine-readable output here, as an option | ||
val problems = new MiMaLib(classpath) | ||
.collectProblems(oldBin, newBin, Nil) | ||
.flatMap(formatter.formatProblem) | ||
problems.foreach(println) | ||
problems.size | ||
} | ||
|
||
} | ||
|
||
object Main { | ||
|
||
def main(args: Array[String]): Unit = | ||
try System.exit(parseArgs(args.toList, Main()).run()) | ||
catch { | ||
case err: IllegalArgumentException => | ||
println(err.getMessage()) | ||
printUsage() | ||
} | ||
|
||
def printUsage(): Unit = println( | ||
s"""Usage: | ||
| | ||
|mima [OPTIONS] oldfile newfile | ||
| | ||
| oldfile: Old (or, previous) files - a JAR or a directory containing classfiles | ||
| newfile: New (or, current) files - a JAR or a directory containing classfiles | ||
| | ||
|Options: | ||
| -cp CLASSPATH: | ||
| Specify Java classpath, separated by '${File.pathSeparatorChar}' | ||
| | ||
| -v, --verbose: | ||
| Show a human-readable description of each problem | ||
| | ||
| -f, --forward-only: | ||
| Show only forward-binary-compatibility problems | ||
| | ||
| -b, --backward-only: | ||
| Show only backward-binary-compatibility problems | ||
| | ||
| -g, --include-generics: | ||
| Include generic signature problems, which may not directly cause bincompat | ||
| problems and are hidden by default. Has no effect if using --forward-only. | ||
| | ||
| -j, --bytecode-names: | ||
| Show bytecode names of fields and methods, rather than human-readable names | ||
| | ||
|""".stripMargin | ||
) | ||
|
||
@tailrec | ||
private def parseArgs(remaining: List[String], current: Main): Main = | ||
remaining match { | ||
case Nil => current | ||
case ("-cp" | "--classpath") :: cpStr :: rest => | ||
parseArgs( | ||
rest, | ||
current.copy(classpath = | ||
cpStr.split(File.pathSeparatorChar).toSeq.map(new File(_)) | ||
) | ||
) | ||
|
||
case ("-f" | "--forward-only") :: rest => | ||
parseArgs( | ||
rest, | ||
current.copy(formatter = | ||
current.formatter.copy(showForward = true, showBackward = false) | ||
) | ||
) | ||
|
||
case ("-b" | "--backward-only") :: rest => | ||
parseArgs( | ||
rest, | ||
current.copy(formatter = | ||
current.formatter.copy(showForward = false, showBackward = true) | ||
) | ||
) | ||
|
||
case ("-j" | "--bytecode-names") :: rest => | ||
parseArgs( | ||
rest, | ||
current.copy(formatter = | ||
current.formatter.copy(useBytecodeNames = true) | ||
) | ||
) | ||
|
||
case ("-v" | "--verbose") :: rest => | ||
parseArgs( | ||
rest, | ||
current.copy(formatter = | ||
current.formatter.copy(showDescriptions = true) | ||
) | ||
) | ||
|
||
case ("-g" | "--include-generics") :: rest => | ||
parseArgs( | ||
rest, | ||
current.copy(formatter = | ||
current.formatter.copy(showIncompatibleSignature = true) | ||
) | ||
) | ||
|
||
case filename :: rest if current.oldBinOpt.isEmpty => | ||
parseArgs(rest, current.copy(oldBinOpt = Some(new File(filename)))) | ||
case filename :: rest if current.newBinOpt.isEmpty => | ||
parseArgs(rest, current.copy(newBinOpt = Some(new File(filename)))) | ||
case wut :: _ => | ||
throw new IllegalArgumentException(s"Unknown argument $wut") | ||
} | ||
|
||
} |
103 changes: 103 additions & 0 deletions
103
cli/src/main/scala/com/typesafe/tools/mima/cli/ProblemFormatter.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
package com.typesafe.tools.mima.cli | ||
|
||
import com.typesafe.tools.mima.core.AbstractMethodProblem | ||
import com.typesafe.tools.mima.core.DirectMissingMethodProblem | ||
import com.typesafe.tools.mima.core.FinalMethodProblem | ||
import com.typesafe.tools.mima.core.InaccessibleFieldProblem | ||
import com.typesafe.tools.mima.core.InaccessibleMethodProblem | ||
import com.typesafe.tools.mima.core.IncompatibleFieldTypeProblem | ||
import com.typesafe.tools.mima.core.IncompatibleMethTypeProblem | ||
import com.typesafe.tools.mima.core.IncompatibleResultTypeProblem | ||
import com.typesafe.tools.mima.core.IncompatibleSignatureProblem | ||
import com.typesafe.tools.mima.core.MemberInfo | ||
import com.typesafe.tools.mima.core.MemberProblem | ||
import com.typesafe.tools.mima.core.MissingFieldProblem | ||
import com.typesafe.tools.mima.core.MissingMethodProblem | ||
import com.typesafe.tools.mima.core.NewMixinForwarderProblem | ||
import com.typesafe.tools.mima.core.Problem | ||
import com.typesafe.tools.mima.core.ReversedAbstractMethodProblem | ||
import com.typesafe.tools.mima.core.ReversedMissingMethodProblem | ||
import com.typesafe.tools.mima.core.TemplateProblem | ||
import com.typesafe.tools.mima.core.UpdateForwarderBodyProblem | ||
|
||
case class ProblemFormatter( | ||
showForward: Boolean = true, | ||
showBackward: Boolean = true, | ||
showIncompatibleSignature: Boolean = false, | ||
useBytecodeNames: Boolean = false, | ||
showDescriptions: Boolean = false | ||
) { | ||
|
||
private def str(problem: TemplateProblem): String = | ||
s"${if (useBytecodeNames) problem.ref.bytecodeName | ||
else problem.ref.fullName}: ${problem.getClass.getSimpleName.stripSuffix("Problem")}${description(problem)}" | ||
|
||
private def str(problem: MemberProblem): String = | ||
s"${memberName(problem.ref)}: ${problem.getClass.getSimpleName.stripSuffix("Problem")}${description(problem)}" | ||
|
||
private def description(problem: Problem): String = | ||
if (showDescriptions) ": " + problem.description("new") else "" | ||
|
||
private def memberName(info: MemberInfo): String = | ||
if (useBytecodeNames) | ||
bytecodeFullName(info) | ||
else | ||
info.fullName | ||
|
||
private def bytecodeFullName(info: MemberInfo): String = { | ||
val pkg = info.owner.owner.fullName.replace('.', '/') | ||
val clsName = info.owner.bytecodeName | ||
val memberName = info.bytecodeName match { | ||
case "<init>" => "\"<init>\"" | ||
case name => name | ||
} | ||
val sig = info.descriptor | ||
|
||
s"$pkg/$clsName.$memberName$sig" | ||
} | ||
|
||
// format: off | ||
def formatProblem(problem: Problem): Option[String] = problem match { | ||
case prob: TemplateProblem if showBackward => Some(str(prob)) | ||
case _: TemplateProblem => None | ||
|
||
case problem: MemberProblem => problem match { | ||
case prob: AbstractMethodProblem if showBackward => Some(str(prob)) | ||
case _: AbstractMethodProblem => None | ||
|
||
case problem: MissingMethodProblem => problem match { | ||
case prob: DirectMissingMethodProblem if showBackward => Some(str(prob)) | ||
case _: DirectMissingMethodProblem => None | ||
case prob: ReversedMissingMethodProblem if showForward => Some(str(prob)) | ||
case _: ReversedMissingMethodProblem => None | ||
} | ||
|
||
case prob: ReversedAbstractMethodProblem if showForward => Some(str(prob)) | ||
case _: ReversedAbstractMethodProblem => None | ||
case prob: MissingFieldProblem if showBackward => Some(str(prob)) | ||
case _: MissingFieldProblem => None | ||
case prob: InaccessibleFieldProblem if showBackward => Some(str(prob)) | ||
case _: InaccessibleFieldProblem => None | ||
case prob: IncompatibleFieldTypeProblem if showBackward => Some(str(prob)) | ||
case _: IncompatibleFieldTypeProblem => None | ||
case prob: InaccessibleMethodProblem if showBackward => Some(str(prob)) | ||
case _: InaccessibleMethodProblem => None | ||
case prob: IncompatibleMethTypeProblem if showBackward => Some(str(prob)) | ||
case _: IncompatibleMethTypeProblem => None | ||
case prob: IncompatibleResultTypeProblem if showBackward => Some(str(prob)) | ||
case _: IncompatibleResultTypeProblem => None | ||
case prob: FinalMethodProblem if showBackward => Some(str(prob)) | ||
case _: FinalMethodProblem => None | ||
case prob: UpdateForwarderBodyProblem if showBackward => Some(str(prob)) | ||
case _: UpdateForwarderBodyProblem => None | ||
case prob: NewMixinForwarderProblem if showBackward => Some(str(prob)) | ||
case _: NewMixinForwarderProblem => None | ||
|
||
case prob: IncompatibleSignatureProblem | ||
if showBackward && showIncompatibleSignature => Some(str(prob)) | ||
case _: IncompatibleSignatureProblem => None | ||
} | ||
} | ||
// format: on | ||
|
||
} |