mirror of https://github.com/sbt/sbt.git
Merge pull request #1467 from sbt/wip/eviction-warning
Eviction warnings (Fixes #1200)
This commit is contained in:
commit
984bcf589c
|
|
@ -2,6 +2,11 @@ package sbt
|
|||
|
||||
import DependencyFilter._
|
||||
|
||||
/**
|
||||
* Provide warnings for cross version conflicts.
|
||||
* A library foo_2.10 and foo_2.11 can potentially be both included on the
|
||||
* library dependency graph by mistake, but it won't be caught by eviction.
|
||||
*/
|
||||
final case class ConflictWarning(label: String, level: Level.Value, failOnConflict: Boolean) {
|
||||
@deprecated("`filter` is no longer used", "0.13.0")
|
||||
val filter: ModuleFilter = (_: ModuleID) => false
|
||||
|
|
|
|||
|
|
@ -0,0 +1,200 @@
|
|||
package sbt
|
||||
|
||||
import collection.mutable
|
||||
import Configurations.Compile
|
||||
|
||||
final class EvictionWarningOptions private[sbt] (
|
||||
val configurations: Seq[Configuration],
|
||||
val warnScalaVersionEviction: Boolean,
|
||||
val warnDirectEvictions: Boolean,
|
||||
val warnTransitiveEvictions: Boolean,
|
||||
val showCallers: Boolean,
|
||||
val guessCompatible: Function1[(ModuleID, Option[ModuleID], Option[IvyScala]), Boolean]) {
|
||||
private[sbt] def configStrings = configurations map { _.name }
|
||||
|
||||
def withConfigurations(configurations: Seq[Configuration]): EvictionWarningOptions =
|
||||
copy(configurations = configurations)
|
||||
def withWarnScalaVersionEviction(warnScalaVersionEviction: Boolean): EvictionWarningOptions =
|
||||
copy(warnScalaVersionEviction = warnScalaVersionEviction)
|
||||
def withWarnDirectEvictions(warnDirectEvictions: Boolean): EvictionWarningOptions =
|
||||
copy(warnDirectEvictions = warnDirectEvictions)
|
||||
def withWarnTransitiveEvictions(warnTransitiveEvictions: Boolean): EvictionWarningOptions =
|
||||
copy(warnTransitiveEvictions = warnTransitiveEvictions)
|
||||
def withShowCallers(showCallers: Boolean): EvictionWarningOptions =
|
||||
copy(showCallers = showCallers)
|
||||
def withGuessCompatible(guessCompatible: Function1[(ModuleID, Option[ModuleID], Option[IvyScala]), Boolean]): EvictionWarningOptions =
|
||||
copy(guessCompatible = guessCompatible)
|
||||
|
||||
private[sbt] def copy(configurations: Seq[Configuration] = configurations,
|
||||
warnScalaVersionEviction: Boolean = warnScalaVersionEviction,
|
||||
warnDirectEvictions: Boolean = warnDirectEvictions,
|
||||
warnTransitiveEvictions: Boolean = warnTransitiveEvictions,
|
||||
showCallers: Boolean = showCallers,
|
||||
guessCompatible: Function1[(ModuleID, Option[ModuleID], Option[IvyScala]), Boolean] = guessCompatible): EvictionWarningOptions =
|
||||
new EvictionWarningOptions(configurations = configurations,
|
||||
warnScalaVersionEviction = warnScalaVersionEviction,
|
||||
warnDirectEvictions = warnDirectEvictions,
|
||||
warnTransitiveEvictions = warnTransitiveEvictions,
|
||||
showCallers = showCallers,
|
||||
guessCompatible = guessCompatible)
|
||||
}
|
||||
|
||||
object EvictionWarningOptions {
|
||||
def default: EvictionWarningOptions =
|
||||
new EvictionWarningOptions(Vector(Compile), true, true, false, false, defaultGuess)
|
||||
def full: EvictionWarningOptions =
|
||||
new EvictionWarningOptions(Vector(Compile), true, true, true, true, defaultGuess)
|
||||
|
||||
lazy val defaultGuess: Function1[(ModuleID, Option[ModuleID], Option[IvyScala]), Boolean] =
|
||||
guessSecondSegment orElse guessSemVer orElse guessFalse
|
||||
lazy val guessSecondSegment: PartialFunction[(ModuleID, Option[ModuleID], Option[IvyScala]), Boolean] = {
|
||||
case (m1, Some(m2), Some(ivyScala)) if m2.name.endsWith("_" + ivyScala.scalaFullVersion) || m2.name.endsWith("_" + ivyScala.scalaBinaryVersion) =>
|
||||
(m1.revision, m2.revision) match {
|
||||
case (VersionNumber(ns1, ts1, es1), VersionNumber(ns2, ts2, es2)) =>
|
||||
VersionNumber.SecondSegment.isCompatible(VersionNumber(ns1, ts1, es1), VersionNumber(ns2, ts2, es2))
|
||||
case _ => false
|
||||
}
|
||||
}
|
||||
lazy val guessSemVer: PartialFunction[(ModuleID, Option[ModuleID], Option[IvyScala]), Boolean] = {
|
||||
case (m1, Some(m2), _) =>
|
||||
(m1.revision, m2.revision) match {
|
||||
case (VersionNumber(ns1, ts1, es1), VersionNumber(ns2, ts2, es2)) =>
|
||||
VersionNumber.SemVer.isCompatible(VersionNumber(ns1, ts1, es1), VersionNumber(ns2, ts2, es2))
|
||||
case _ => false
|
||||
}
|
||||
}
|
||||
lazy val guessFalse: PartialFunction[(ModuleID, Option[ModuleID], Option[IvyScala]), Boolean] = {
|
||||
case (_, _, _) => false
|
||||
}
|
||||
}
|
||||
|
||||
final class EvictionPair private[sbt] (
|
||||
val organization: String,
|
||||
val name: String,
|
||||
val winner: Option[ModuleReport],
|
||||
val evicteds: Seq[ModuleReport],
|
||||
val includesDirect: Boolean,
|
||||
val showCallers: Boolean) {
|
||||
override def toString: String =
|
||||
EvictionPair.evictionPairLines.showLines(this).mkString
|
||||
}
|
||||
|
||||
object EvictionPair {
|
||||
implicit val evictionPairLines: ShowLines[EvictionPair] = ShowLines { a: EvictionPair =>
|
||||
val revs = a.evicteds map { _.module.revision }
|
||||
val revsStr = if (revs.size <= 1) revs.mkString else "(" + revs.mkString(", ") + ")"
|
||||
val winnerRev = (a.winner map { r =>
|
||||
val callers: String =
|
||||
if (a.showCallers)
|
||||
r.callers match {
|
||||
case Seq() => ""
|
||||
case cs => (cs map { _.caller.toString }).mkString(" (caller: ", ", ", ")")
|
||||
}
|
||||
else ""
|
||||
r.module.revision + callers
|
||||
}).headOption map { " -> " + _ } getOrElse ""
|
||||
Seq(s"\t* ${a.organization}:${a.name}:${revsStr}$winnerRev")
|
||||
}
|
||||
}
|
||||
|
||||
final class EvictionWarning private[sbt] (
|
||||
val options: EvictionWarningOptions,
|
||||
val scalaEvictions: Seq[EvictionPair],
|
||||
val directEvictions: Seq[EvictionPair],
|
||||
val transitiveEvictions: Seq[EvictionPair],
|
||||
val allEvictions: Seq[EvictionPair]) {
|
||||
def reportedEvictions: Seq[EvictionPair] = scalaEvictions ++ directEvictions ++ transitiveEvictions
|
||||
}
|
||||
|
||||
object EvictionWarning {
|
||||
def apply(module: IvySbt#Module, options: EvictionWarningOptions, report: UpdateReport, log: Logger): EvictionWarning = {
|
||||
val evictions = buildEvictions(options, report)
|
||||
processEvictions(module, options, evictions)
|
||||
}
|
||||
|
||||
private[sbt] def buildEvictions(options: EvictionWarningOptions, report: UpdateReport): Seq[ModuleDetailReport] = {
|
||||
val buffer: mutable.ListBuffer[ModuleDetailReport] = mutable.ListBuffer()
|
||||
val confs = report.configurations filter { x => options.configStrings contains x.configuration }
|
||||
confs flatMap { confReport =>
|
||||
confReport.details map { detail =>
|
||||
if ((detail.modules exists { _.evicted }) &&
|
||||
!(buffer exists { x => (x.organization == detail.organization) && (x.name == detail.name) })) {
|
||||
buffer += detail
|
||||
}
|
||||
}
|
||||
}
|
||||
buffer.toList.toVector
|
||||
}
|
||||
|
||||
private[sbt] def isScalaArtifact(module: IvySbt#Module, organization: String, name: String): Boolean =
|
||||
module.moduleSettings.ivyScala match {
|
||||
case Some(s) =>
|
||||
organization == s.scalaOrganization &&
|
||||
(name == "scala-library") || (name == "scala-compiler")
|
||||
case _ => false
|
||||
}
|
||||
|
||||
private[sbt] def processEvictions(module: IvySbt#Module, options: EvictionWarningOptions, reports: Seq[ModuleDetailReport]): EvictionWarning = {
|
||||
val directDependencies = module.moduleSettings match {
|
||||
case x: InlineConfiguration => x.dependencies
|
||||
case _ => Seq()
|
||||
}
|
||||
val pairs = reports map { detail =>
|
||||
val evicteds = detail.modules filter { _.evicted }
|
||||
val winner = (detail.modules filterNot { _.evicted }).headOption
|
||||
val includesDirect: Boolean =
|
||||
options.warnDirectEvictions &&
|
||||
(directDependencies exists { dep =>
|
||||
(detail.organization == dep.organization) && (detail.name == dep.name)
|
||||
})
|
||||
new EvictionPair(detail.organization, detail.name, winner, evicteds, includesDirect, options.showCallers)
|
||||
}
|
||||
val scalaEvictions: mutable.ListBuffer[EvictionPair] = mutable.ListBuffer()
|
||||
val directEvictions: mutable.ListBuffer[EvictionPair] = mutable.ListBuffer()
|
||||
val transitiveEvictions: mutable.ListBuffer[EvictionPair] = mutable.ListBuffer()
|
||||
def guessCompatible(p: EvictionPair): Boolean =
|
||||
p.evicteds forall { r =>
|
||||
options.guessCompatible(r.module, p.winner map { _.module }, module.moduleSettings.ivyScala)
|
||||
}
|
||||
pairs foreach {
|
||||
case p if isScalaArtifact(module, p.organization, p.name) =>
|
||||
(module.moduleSettings.ivyScala, p.winner) match {
|
||||
case (Some(s), Some(winner)) if ((s.scalaFullVersion != winner.module.revision) && options.warnScalaVersionEviction) =>
|
||||
scalaEvictions += p
|
||||
case _ =>
|
||||
}
|
||||
case p if p.includesDirect =>
|
||||
if (!guessCompatible(p) && options.warnDirectEvictions) {
|
||||
directEvictions += p
|
||||
}
|
||||
case p =>
|
||||
if (!guessCompatible(p) && options.warnTransitiveEvictions) {
|
||||
transitiveEvictions += p
|
||||
}
|
||||
}
|
||||
new EvictionWarning(options, scalaEvictions.toList,
|
||||
directEvictions.toList, transitiveEvictions.toList, pairs)
|
||||
}
|
||||
|
||||
implicit val evictionWarningLines: ShowLines[EvictionWarning] = ShowLines { a: EvictionWarning =>
|
||||
import ShowLines._
|
||||
val out: mutable.ListBuffer[String] = mutable.ListBuffer()
|
||||
if (!a.scalaEvictions.isEmpty) {
|
||||
out += "Scala version was updated by one of library dependencies:"
|
||||
out ++= (a.scalaEvictions flatMap { _.lines })
|
||||
}
|
||||
|
||||
if (!a.directEvictions.isEmpty || !a.transitiveEvictions.isEmpty) {
|
||||
out += "There may be incompatibilities among your library dependencies."
|
||||
out += "Here are some of the libraries that were evicted:"
|
||||
out ++= (a.directEvictions flatMap { _.lines })
|
||||
out ++= (a.transitiveEvictions flatMap { _.lines })
|
||||
}
|
||||
|
||||
if (!a.allEvictions.isEmpty && !a.reportedEvictions.isEmpty && !a.options.showCallers) {
|
||||
out += "Run 'evicted' to see detailed eviction warnings"
|
||||
}
|
||||
|
||||
out.toList
|
||||
}
|
||||
}
|
||||
|
|
@ -4,35 +4,124 @@
|
|||
package sbt
|
||||
|
||||
import java.io.File
|
||||
import java.{ util => ju }
|
||||
import collection.mutable
|
||||
import java.net.URL
|
||||
import org.apache.ivy.core.{ module, report, resolve }
|
||||
import module.descriptor.{ Artifact => IvyArtifact }
|
||||
import module.id.ModuleRevisionId
|
||||
import resolve.IvyNode
|
||||
import module.descriptor.{ Artifact => IvyArtifact, License => IvyLicense }
|
||||
import module.id.{ ModuleRevisionId, ModuleId => IvyModuleId }
|
||||
import report.{ ArtifactDownloadReport, ConfigurationResolveReport, ResolveReport }
|
||||
import resolve.{ IvyNode, IvyNodeCallers }
|
||||
import IvyNodeCallers.{ Caller => IvyCaller }
|
||||
|
||||
object IvyRetrieve {
|
||||
def reports(report: ResolveReport): Seq[ConfigurationResolveReport] =
|
||||
report.getConfigurations map report.getConfigurationReport
|
||||
|
||||
def moduleReports(confReport: ConfigurationResolveReport): Seq[ModuleReport] =
|
||||
for (revId <- confReport.getModuleRevisionIds.toArray collect { case revId: ModuleRevisionId => revId }) yield artifactReports(toModuleID(revId), confReport getDownloadReports revId)
|
||||
for {
|
||||
revId <- confReport.getModuleRevisionIds.toArray.toVector collect { case revId: ModuleRevisionId => revId }
|
||||
} yield moduleRevisionDetail(confReport, confReport.getDependency(revId))
|
||||
|
||||
@deprecated("Internal only. No longer in use.", "0.13.6")
|
||||
def artifactReports(mid: ModuleID, artReport: Seq[ArtifactDownloadReport]): ModuleReport =
|
||||
{
|
||||
val (resolved, missing) = artifacts(mid, artReport)
|
||||
ModuleReport(mid, resolved, missing)
|
||||
}
|
||||
|
||||
private[sbt] def artifacts(mid: ModuleID, artReport: Seq[ArtifactDownloadReport]): (Seq[(Artifact, File)], Seq[Artifact]) =
|
||||
{
|
||||
val missing = new mutable.ListBuffer[Artifact]
|
||||
val resolved = new mutable.ListBuffer[(Artifact, File)]
|
||||
for (r <- artReport) {
|
||||
val file = r.getLocalFile
|
||||
val fileOpt = Option(r.getLocalFile)
|
||||
val art = toArtifact(r.getArtifact)
|
||||
if (file eq null)
|
||||
missing += art
|
||||
else
|
||||
resolved += ((art, file))
|
||||
fileOpt match {
|
||||
case Some(file) => resolved += ((art, file))
|
||||
case None => missing += art
|
||||
}
|
||||
}
|
||||
new ModuleReport(mid, resolved.toSeq, missing.toSeq)
|
||||
(resolved.toSeq, missing.toSeq)
|
||||
}
|
||||
|
||||
// We need this because current module report used as part of UpdateReport/ConfigurationReport contains
|
||||
// only the revolved modules.
|
||||
// Sometimes the entire module can be excluded via rules etc.
|
||||
private[sbt] def details(confReport: ConfigurationResolveReport): Seq[ModuleDetailReport] = {
|
||||
val dependencies = confReport.getModuleRevisionIds.toArray.toVector collect { case revId: ModuleRevisionId => revId }
|
||||
val moduleIds = confReport.getModuleIds.toArray.toVector collect { case mId: IvyModuleId => mId }
|
||||
def moduleDetail(mid: IvyModuleId): ModuleDetailReport = {
|
||||
val deps = confReport.getNodes(mid).toArray.toVector collect { case node: IvyNode => node }
|
||||
new ModuleDetailReport(mid.getOrganisation, mid.getName, deps map { moduleRevisionDetail(confReport, _) })
|
||||
}
|
||||
moduleIds map { moduleDetail }
|
||||
}
|
||||
|
||||
private[sbt] def moduleRevisionDetail(confReport: ConfigurationResolveReport, dep: IvyNode): ModuleReport = {
|
||||
def toExtraAttributes(ea: ju.Map[_, _]): Map[String, String] =
|
||||
Map(ea.entrySet.toArray collect {
|
||||
case entry: ju.Map.Entry[_, _] => (entry.getKey.toString, entry.getValue.toString)
|
||||
}: _*)
|
||||
def toCaller(caller: IvyCaller): Caller = {
|
||||
val m = toModuleID(caller.getModuleRevisionId)
|
||||
val callerConfigurations = caller.getCallerConfigurations.toArray.toVector
|
||||
val extraAttributes = toExtraAttributes(caller.getDependencyDescriptor.getExtraAttributes)
|
||||
new Caller(m, callerConfigurations, extraAttributes)
|
||||
}
|
||||
val revId = dep.getResolvedId
|
||||
val moduleId = toModuleID(revId)
|
||||
val branch = Option(revId.getBranch)
|
||||
val (status, publicationDate, resolver, artifactResolver) = dep.isLoaded match {
|
||||
case true =>
|
||||
(Option(dep.getDescriptor.getStatus),
|
||||
Some(new ju.Date(dep.getPublication)),
|
||||
Option(dep.getModuleRevision.getResolver.getName),
|
||||
Option(dep.getModuleRevision.getArtifactResolver.getName))
|
||||
case _ => (None, None, None, None)
|
||||
}
|
||||
val (evicted, evictedData, evictedReason) = dep.isEvicted(confReport.getConfiguration) match {
|
||||
case true =>
|
||||
val ed = dep.getEvictedData(confReport.getConfiguration)
|
||||
(true,
|
||||
Some(Option(ed.getConflictManager) map { _.toString } getOrElse { "transitive" }),
|
||||
Option(ed.getDetail))
|
||||
case _ => (false, None, None)
|
||||
}
|
||||
val problem = dep.hasProblem match {
|
||||
case true => Option(dep.getProblem.getMessage)
|
||||
case _ => None
|
||||
}
|
||||
val mdOpt = for {
|
||||
mr <- Option(dep.getModuleRevision)
|
||||
md <- Option(mr.getDescriptor)
|
||||
} yield md
|
||||
val homepage = mdOpt match {
|
||||
case Some(md) =>
|
||||
Option(md.getHomePage)
|
||||
case _ => None
|
||||
}
|
||||
val extraAttributes: Map[String, String] = toExtraAttributes(mdOpt match {
|
||||
case Some(md) => md.getExtraAttributes
|
||||
case _ => dep.getResolvedId.getExtraAttributes
|
||||
})
|
||||
val isDefault = Option(dep.getDescriptor) map { _.isDefault }
|
||||
val configurations = dep.getConfigurations(confReport.getConfiguration).toArray.toList
|
||||
val licenses: Seq[(String, Option[String])] = mdOpt match {
|
||||
case Some(md) => md.getLicenses.toArray.toVector collect {
|
||||
case lic: IvyLicense =>
|
||||
(lic.getName, Option(lic.getUrl))
|
||||
}
|
||||
case _ => Nil
|
||||
}
|
||||
val callers = dep.getCallers(confReport.getConfiguration).toArray.toVector map { toCaller }
|
||||
val (resolved, missing) = artifacts(moduleId, confReport getDownloadReports revId)
|
||||
|
||||
new ModuleReport(moduleId, resolved, missing, status, publicationDate, resolver, artifactResolver,
|
||||
evicted, evictedData, evictedReason, problem, homepage, extraAttributes, isDefault, branch,
|
||||
configurations, licenses, callers)
|
||||
}
|
||||
|
||||
def evicted(confReport: ConfigurationResolveReport): Seq[ModuleID] =
|
||||
confReport.getEvictedNodes.map(node => toModuleID(node.getId))
|
||||
|
||||
|
|
@ -50,7 +139,7 @@ object IvyRetrieve {
|
|||
def updateStats(report: ResolveReport): UpdateStats =
|
||||
new UpdateStats(report.getResolveTime, report.getDownloadTime, report.getDownloadSize, false)
|
||||
def configurationReport(confReport: ConfigurationResolveReport): ConfigurationReport =
|
||||
new ConfigurationReport(confReport.getConfiguration, moduleReports(confReport), evicted(confReport))
|
||||
new ConfigurationReport(confReport.getConfiguration, moduleReports(confReport), details(confReport), evicted(confReport))
|
||||
|
||||
/**
|
||||
* Tries to find Ivy graph path the from node to target.
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@
|
|||
package sbt
|
||||
|
||||
import java.io.File
|
||||
import java.net.URL
|
||||
import java.{ util => ju }
|
||||
|
||||
/**
|
||||
* Provides information about dependency resolution.
|
||||
|
|
@ -37,10 +39,22 @@ final class UpdateReport(val cachedDescriptor: File, val configurations: Seq[Con
|
|||
/**
|
||||
* Provides information about resolution of a single configuration.
|
||||
* @param configuration the configuration this report is for.
|
||||
* @param modules a seqeuence containing one report for each module resolved for this configuration.
|
||||
* @param modules a sequence containing one report for each module resolved for this configuration.
|
||||
* @param details a sequence containing one report for each org/name, which may or may not be part of the final resolution.
|
||||
* @param evicted a sequence of evicted modules
|
||||
*/
|
||||
final class ConfigurationReport(val configuration: String, val modules: Seq[ModuleReport], val evicted: Seq[ModuleID]) {
|
||||
override def toString = "\t" + configuration + ":\n" + modules.mkString + evicted.map("\t\t(EVICTED) " + _ + "\n").mkString
|
||||
final class ConfigurationReport(
|
||||
val configuration: String,
|
||||
val modules: Seq[ModuleReport],
|
||||
val details: Seq[ModuleDetailReport],
|
||||
@deprecated("Use details instead to get better eviction info.", "0.13.6")
|
||||
val evicted: Seq[ModuleID]) {
|
||||
def this(configuration: String, modules: Seq[ModuleReport], evicted: Seq[ModuleID]) =
|
||||
this(configuration, modules, Nil, evicted)
|
||||
|
||||
override def toString = s"\t$configuration:\n" +
|
||||
(if (details.isEmpty) modules.mkString + evicted.map("\t\t(EVICTED) " + _ + "\n").mkString
|
||||
else details.mkString)
|
||||
|
||||
/**
|
||||
* All resolved modules for this configuration.
|
||||
|
|
@ -50,25 +64,131 @@ final class ConfigurationReport(val configuration: String, val modules: Seq[Modu
|
|||
private[this] def addConfiguration(mod: ModuleID): ModuleID = if (mod.configurations.isEmpty) mod.copy(configurations = Some(configuration)) else mod
|
||||
|
||||
def retrieve(f: (String, ModuleID, Artifact, File) => File): ConfigurationReport =
|
||||
new ConfigurationReport(configuration, modules map { _.retrieve((mid, art, file) => f(configuration, mid, art, file)) }, evicted)
|
||||
new ConfigurationReport(configuration, modules map { _.retrieve((mid, art, file) => f(configuration, mid, art, file)) }, details, evicted)
|
||||
}
|
||||
|
||||
/**
|
||||
* ModuleDetailReport represents an organization+name entry in Ivy resolution report.
|
||||
* In sbt's terminology, "module" consists of organization, name, and version.
|
||||
* In Ivy's, "module" means just organization and name, and the one including version numbers
|
||||
* are called revisions.
|
||||
*
|
||||
* A sequence of ModuleDetailReport called details is newly added to ConfigurationReport, replacing evicted.
|
||||
* (Note old evicted was just a seq of ModuleIDs).
|
||||
* ModuleDetailReport groups the ModuleReport of both winners and evicted reports by their organization and name,
|
||||
* which can be used to calculate detailed evction warning etc.
|
||||
*/
|
||||
final class ModuleDetailReport(
|
||||
val organization: String,
|
||||
val name: String,
|
||||
val modules: Seq[ModuleReport]) {
|
||||
override def toString: String = {
|
||||
val details = modules map { _.detailReport }
|
||||
s"\t$organization:$name\n${details.mkString}\n"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides information about the resolution of a module.
|
||||
* This information is in the context of a specific configuration.
|
||||
* @param module the `ModuleID` this report is for.
|
||||
* @param artifacts the resolved artifacts for this module, paired with the File the artifact was retrieved to. This File may be in the
|
||||
* @param artifacts the resolved artifacts for this module, paired with the File the artifact was retrieved to.
|
||||
* @param missingArtifacts the missing artifacts for this module.
|
||||
*/
|
||||
final class ModuleReport(val module: ModuleID, val artifacts: Seq[(Artifact, File)], val missingArtifacts: Seq[Artifact]) {
|
||||
override def toString =
|
||||
{
|
||||
val arts = artifacts.map(_.toString) ++ missingArtifacts.map(art => "(MISSING) " + art)
|
||||
"\t\t" + module + ": " +
|
||||
(if (arts.size <= 1) "" else "\n\t\t\t") + arts.mkString("\n\t\t\t") + "\n"
|
||||
}
|
||||
final class ModuleReport(
|
||||
val module: ModuleID,
|
||||
val artifacts: Seq[(Artifact, File)],
|
||||
val missingArtifacts: Seq[Artifact],
|
||||
val status: Option[String],
|
||||
val publicationDate: Option[ju.Date],
|
||||
val resolver: Option[String],
|
||||
val artifactResolver: Option[String],
|
||||
val evicted: Boolean,
|
||||
val evictedData: Option[String],
|
||||
val evictedReason: Option[String],
|
||||
val problem: Option[String],
|
||||
val homepage: Option[String],
|
||||
val extraAttributes: Map[String, String],
|
||||
val isDefault: Option[Boolean],
|
||||
val branch: Option[String],
|
||||
val configurations: Seq[String],
|
||||
val licenses: Seq[(String, Option[String])],
|
||||
val callers: Seq[Caller]) {
|
||||
|
||||
private[this] lazy val arts: Seq[String] = artifacts.map(_.toString) ++ missingArtifacts.map(art => "(MISSING) " + art)
|
||||
override def toString: String = {
|
||||
s"\t\t$module: " +
|
||||
(if (arts.size <= 1) "" else "\n\t\t\t") + arts.mkString("\n\t\t\t") + "\n"
|
||||
}
|
||||
private[sbt] def detailReport: String =
|
||||
s"\t\t- ${module.revision}\n" +
|
||||
(if (arts.size <= 1) "" else arts.mkString("\t\t\t", "\n\t\t\t", "\n")) +
|
||||
reportStr("status", status) +
|
||||
reportStr("publicationDate", publicationDate map { _.toString }) +
|
||||
reportStr("resolver", resolver) +
|
||||
reportStr("artifactResolver", artifactResolver) +
|
||||
reportStr("evicted", Some(evicted.toString)) +
|
||||
reportStr("evictedData", evictedData) +
|
||||
reportStr("evictedReason", evictedReason) +
|
||||
reportStr("problem", problem) +
|
||||
reportStr("homepage", homepage) +
|
||||
reportStr("textraAttributes",
|
||||
if (extraAttributes.isEmpty) None
|
||||
else { Some(extraAttributes.toString) }) +
|
||||
reportStr("isDefault", isDefault map { _.toString }) +
|
||||
reportStr("branch", branch) +
|
||||
reportStr("configurations",
|
||||
if (configurations.isEmpty) None
|
||||
else { Some(configurations.mkString(", ")) }) +
|
||||
reportStr("licenses",
|
||||
if (licenses.isEmpty) None
|
||||
else { Some(licenses.mkString(", ")) }) +
|
||||
reportStr("callers",
|
||||
if (callers.isEmpty) None
|
||||
else { Some(callers.mkString(", ")) })
|
||||
private[sbt] def reportStr(key: String, value: Option[String]): String =
|
||||
value map { x => s"\t\t\t$key: $x\n" } getOrElse ""
|
||||
|
||||
def retrieve(f: (ModuleID, Artifact, File) => File): ModuleReport =
|
||||
new ModuleReport(module, artifacts.map { case (art, file) => (art, f(module, art, file)) }, missingArtifacts)
|
||||
copy(artifacts = artifacts.map { case (art, file) => (art, f(module, art, file)) })
|
||||
|
||||
private[sbt] def copy(
|
||||
module: ModuleID = module,
|
||||
artifacts: Seq[(Artifact, File)] = artifacts,
|
||||
missingArtifacts: Seq[Artifact] = missingArtifacts,
|
||||
status: Option[String] = status,
|
||||
publicationDate: Option[ju.Date] = publicationDate,
|
||||
resolver: Option[String] = resolver,
|
||||
artifactResolver: Option[String] = artifactResolver,
|
||||
evicted: Boolean = evicted,
|
||||
evictedData: Option[String] = evictedData,
|
||||
evictedReason: Option[String] = evictedReason,
|
||||
problem: Option[String] = problem,
|
||||
homepage: Option[String] = homepage,
|
||||
extraAttributes: Map[String, String] = extraAttributes,
|
||||
isDefault: Option[Boolean] = isDefault,
|
||||
branch: Option[String] = branch,
|
||||
configurations: Seq[String] = configurations,
|
||||
licenses: Seq[(String, Option[String])] = licenses,
|
||||
callers: Seq[Caller] = callers): ModuleReport =
|
||||
new ModuleReport(module, artifacts, missingArtifacts, status, publicationDate, resolver, artifactResolver,
|
||||
evicted, evictedData, evictedReason, problem, homepage, extraAttributes, isDefault, branch, configurations, licenses, callers)
|
||||
}
|
||||
|
||||
object ModuleReport {
|
||||
def apply(module: ModuleID, artifacts: Seq[(Artifact, File)], missingArtifacts: Seq[Artifact]): ModuleReport =
|
||||
new ModuleReport(module, artifacts, missingArtifacts, None, None, None, None,
|
||||
false, None, None, None, None, Map(), None, None, Nil, Nil, Nil)
|
||||
}
|
||||
|
||||
final class Caller(
|
||||
val caller: ModuleID,
|
||||
val callerConfigurations: Seq[String],
|
||||
val callerExtraAttributes: Map[String, String]) {
|
||||
override def toString: String =
|
||||
s"$caller"
|
||||
}
|
||||
|
||||
object UpdateReport {
|
||||
implicit def richUpdateReport(report: UpdateReport): RichUpdateReport = new RichUpdateReport(report)
|
||||
|
||||
|
|
@ -101,15 +221,18 @@ object UpdateReport {
|
|||
/** Constructs a new report that only contains files matching the specified filter.*/
|
||||
def filter(f: DependencyFilter): UpdateReport =
|
||||
moduleReportMap { (configuration, modReport) =>
|
||||
import modReport._
|
||||
val newArtifacts = artifacts filter { case (art, file) => f(configuration, module, art) }
|
||||
val newMissing = missingArtifacts filter { art => f(configuration, module, art) }
|
||||
new ModuleReport(module, newArtifacts, newMissing)
|
||||
modReport.copy(
|
||||
artifacts = modReport.artifacts filter { case (art, file) => f(configuration, modReport.module, art) },
|
||||
missingArtifacts = modReport.missingArtifacts filter { art => f(configuration, modReport.module, art) }
|
||||
)
|
||||
}
|
||||
def substitute(f: (String, ModuleID, Seq[(Artifact, File)]) => Seq[(Artifact, File)]): UpdateReport =
|
||||
moduleReportMap { (configuration, modReport) =>
|
||||
val newArtifacts = f(configuration, modReport.module, modReport.artifacts)
|
||||
new ModuleReport(modReport.module, newArtifacts, Nil)
|
||||
modReport.copy(
|
||||
artifacts = f(configuration, modReport.module, modReport.artifacts),
|
||||
missingArtifacts = Nil
|
||||
)
|
||||
}
|
||||
|
||||
def toSeq: Seq[(String, ModuleID, Artifact, File)] =
|
||||
|
|
@ -120,8 +243,9 @@ object UpdateReport {
|
|||
|
||||
def addMissing(f: ModuleID => Seq[Artifact]): UpdateReport =
|
||||
moduleReportMap { (configuration, modReport) =>
|
||||
import modReport._
|
||||
new ModuleReport(module, artifacts, (missingArtifacts ++ f(module)).distinct)
|
||||
modReport.copy(
|
||||
missingArtifacts = (modReport.missingArtifacts ++ f(modReport.module)).distinct
|
||||
)
|
||||
}
|
||||
|
||||
def moduleReportMap(f: (String, ModuleReport) => ModuleReport): UpdateReport =
|
||||
|
|
@ -129,7 +253,7 @@ object UpdateReport {
|
|||
val newConfigurations = report.configurations.map { confReport =>
|
||||
import confReport._
|
||||
val newModules = modules map { modReport => f(configuration, modReport) }
|
||||
new ConfigurationReport(configuration, newModules, evicted)
|
||||
new ConfigurationReport(configuration, newModules, details, evicted)
|
||||
}
|
||||
new UpdateReport(report.cachedDescriptor, newConfigurations, report.stats, report.stamps)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,147 @@
|
|||
package sbt
|
||||
|
||||
final class VersionNumber private[sbt] (
|
||||
val numbers: Seq[Long],
|
||||
val tags: Seq[String],
|
||||
val extras: Seq[String]) {
|
||||
def _1: Option[Long] = get(0)
|
||||
def _2: Option[Long] = get(1)
|
||||
def _3: Option[Long] = get(2)
|
||||
def _4: Option[Long] = get(3)
|
||||
def get(idx: Int): Option[Long] =
|
||||
if (size <= idx) None
|
||||
else Some(numbers(idx))
|
||||
def size: Int = numbers.size
|
||||
|
||||
private[this] val versionStr: String =
|
||||
numbers.mkString(".") +
|
||||
(tags match {
|
||||
case Seq() => ""
|
||||
case ts => "-" + ts.mkString("-")
|
||||
}) +
|
||||
extras.mkString("")
|
||||
override def toString: String = versionStr
|
||||
override def hashCode: Int =
|
||||
numbers.hashCode * 41 * 41 +
|
||||
tags.hashCode * 41 +
|
||||
extras.hashCode
|
||||
override def equals(o: Any): Boolean =
|
||||
o match {
|
||||
case v: VersionNumber => (this.numbers == v.numbers) && (this.tags == v.tags) && (this.extras == v.extras)
|
||||
case _ => false
|
||||
}
|
||||
}
|
||||
|
||||
object VersionNumber {
|
||||
/**
|
||||
* @param numbers numbers delimited by a dot.
|
||||
* @param tags string prefixed by a dash.
|
||||
* @param any other strings at the end.
|
||||
*/
|
||||
def apply(numbers: Seq[Long], tags: Seq[String], extras: Seq[String]): VersionNumber =
|
||||
new VersionNumber(numbers, tags, extras)
|
||||
def apply(v: String): VersionNumber =
|
||||
unapply(v) match {
|
||||
case Some((ns, ts, es)) => VersionNumber(ns, ts, es)
|
||||
case _ => sys.error(s"Invalid version number: $v")
|
||||
}
|
||||
|
||||
def unapply(v: VersionNumber): Option[(Seq[Long], Seq[String], Seq[String])] =
|
||||
Some((v.numbers, v.tags, v.extras))
|
||||
|
||||
def unapply(v: String): Option[(Seq[Long], Seq[String], Seq[String])] = {
|
||||
def splitDot(s: String): Vector[Long] =
|
||||
Option(s) match {
|
||||
case Some(x) => x.split('.').toVector.filterNot(_ == "").map(_.toLong)
|
||||
case _ => Vector()
|
||||
}
|
||||
def splitDash(s: String): Vector[String] =
|
||||
Option(s) match {
|
||||
case Some(x) => x.split('-').toVector.filterNot(_ == "")
|
||||
case _ => Vector()
|
||||
}
|
||||
def splitPlus(s: String): Vector[String] =
|
||||
Option(s) match {
|
||||
case Some(x) => x.split('+').toVector.filterNot(_ == "").map("+" + _)
|
||||
case _ => Vector()
|
||||
}
|
||||
val TaggedVersion = """(\d{1,14})([\.\d{1,14}]*)((?:-\w+)*)((?:\+.+)*)""".r
|
||||
val NonSpaceString = """(\S+)""".r
|
||||
v match {
|
||||
case TaggedVersion(m, ns, ts, es) => Some((Vector(m.toLong) ++ splitDot(ns), splitDash(ts), splitPlus(es)))
|
||||
case "" => None
|
||||
case NonSpaceString(s) => Some((Vector(), Vector(), Vector(s)))
|
||||
case _ => None
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Strict. Checks everythig.
|
||||
*/
|
||||
object Strict extends VersionNumberCompatibility {
|
||||
def name: String = "Strict"
|
||||
def isCompatible(v1: VersionNumber, v2: VersionNumber): Boolean = v1 == v2
|
||||
}
|
||||
|
||||
/**
|
||||
* Semantic versioning. See http://semver.org/spec/v2.0.0.html
|
||||
*/
|
||||
object SemVer extends VersionNumberCompatibility {
|
||||
def name: String = "Semantic Versioning"
|
||||
def isCompatible(v1: VersionNumber, v2: VersionNumber): Boolean =
|
||||
doIsCompat(v1, v2) || doIsCompat(v2, v1)
|
||||
private[this] def doIsCompat(v1: VersionNumber, v2: VersionNumber): Boolean =
|
||||
(v1, v2) match {
|
||||
case (v1, v2) if (v1.size >= 2) && (v2.size >= 2) => // A normal version number MUST take the form X.Y.Z
|
||||
(v1._1.get, v1._2.get, v1._3.getOrElse(0), v1.tags, v2._1.get, v2._2.get, v2._3.getOrElse(0), v2.tags) match {
|
||||
case (0L, _, _, _, 0L, _, _, _) =>
|
||||
// Major version zero (0.y.z) is for initial development. Anything may change at any time. The public API should not be considered stable.
|
||||
equalsIgnoreExtra(v1, v2)
|
||||
case (_, 0, 0, ts1, _, 0, 0, ts2) if (!ts1.isEmpty) || (!ts2.isEmpty) =>
|
||||
// A pre-release version MAY be denoted by appending a hyphen and a series of dot separated identifiers
|
||||
equalsIgnoreExtra(v1, v2)
|
||||
case (x1, _, _, _, x2, _, _, _) =>
|
||||
// Patch version Z (x.y.Z | x > 0) MUST be incremented if only backwards compatible bug fixes are introduced.
|
||||
// Minor version Y (x.Y.z | x > 0) MUST be incremented if new, backwards compatible functionality is introduced
|
||||
x1 == x2
|
||||
case _ => equalsIgnoreExtra(v1, v2)
|
||||
}
|
||||
case _ => false
|
||||
}
|
||||
// Build metadata SHOULD be ignored when determining version precedence.
|
||||
private[this] def equalsIgnoreExtra(v1: VersionNumber, v2: VersionNumber): Boolean =
|
||||
(v1.numbers == v2.numbers) && (v1.tags == v2.tags)
|
||||
}
|
||||
|
||||
/* A variant of SemVar that seems to be common among the Scala libraries.
|
||||
* The second segment (y in x.y.z) increments breaks the binary compatibility even when x > 0.
|
||||
* Also API comatibility is expected even when the first segment is zero.
|
||||
*/
|
||||
object SecondSegment extends VersionNumberCompatibility {
|
||||
def name: String = "Second Segment Variant"
|
||||
def isCompatible(v1: VersionNumber, v2: VersionNumber): Boolean =
|
||||
doIsCompat(v1, v2) || doIsCompat(v2, v1)
|
||||
private[this] def doIsCompat(v1: VersionNumber, v2: VersionNumber): Boolean =
|
||||
(v1, v2) match {
|
||||
case (v1, v2) if (v1.size >= 3) && (v2.size >= 3) => // A normal version number MUST take the form X.Y.Z
|
||||
(v1._1.get, v1._2.get, v1._3.get, v1.tags, v2._1.get, v2._2.get, v2._3.get, v2.tags) match {
|
||||
case (x1, y1, 0, ts1, x2, y2, 0, ts2) if (!ts1.isEmpty) || (!ts2.isEmpty) =>
|
||||
// A pre-release version MAY be denoted by appending a hyphen and a series of dot separated identifiers
|
||||
equalsIgnoreExtra(v1, v2)
|
||||
case (x1, y1, _, _, x2, y2, _, _) =>
|
||||
// Patch version Z (x.y.Z | x > 0) MUST be incremented if only backwards compatible changes are introduced.
|
||||
(x1 == x2) && (y1 == y2)
|
||||
case _ => equalsIgnoreExtra(v1, v2)
|
||||
}
|
||||
case _ => false
|
||||
}
|
||||
// Build metadata SHOULD be ignored when determining version precedence.
|
||||
private[this] def equalsIgnoreExtra(v1: VersionNumber, v2: VersionNumber): Boolean =
|
||||
(v1.numbers == v2.numbers) && (v1.tags == v2.tags)
|
||||
}
|
||||
}
|
||||
|
||||
trait VersionNumberCompatibility {
|
||||
def name: String
|
||||
def isCompatible(v1: VersionNumber, v2: VersionNumber): Boolean
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
package sbt
|
||||
|
||||
import Path._, Configurations._
|
||||
import java.io.File
|
||||
import org.specs2._
|
||||
import cross.CrossVersionUtil
|
||||
|
||||
trait BaseIvySpecification extends Specification {
|
||||
def currentBase: File = new File(".")
|
||||
def currentTarget: File = currentBase / "target" / "ivyhome"
|
||||
def defaultModuleId: ModuleID = ModuleID("com.example", "foo", "0.1.0", Some("compile"))
|
||||
lazy val ivySbt = new IvySbt(mkIvyConfiguration)
|
||||
lazy val log = Logger.Null
|
||||
def module(moduleId: ModuleID, deps: Seq[ModuleID], scalaFullVersion: Option[String]): IvySbt#Module = {
|
||||
val ivyScala = scalaFullVersion map { fv =>
|
||||
new IvyScala(
|
||||
scalaFullVersion = fv,
|
||||
scalaBinaryVersion = CrossVersionUtil.binaryScalaVersion(fv),
|
||||
configurations = Nil,
|
||||
checkExplicit = true,
|
||||
filterImplicit = false,
|
||||
overrideScalaVersion = false)
|
||||
}
|
||||
|
||||
val moduleSetting: ModuleSettings = InlineConfiguration(
|
||||
module = moduleId,
|
||||
moduleInfo = ModuleInfo("foo"),
|
||||
dependencies = deps,
|
||||
configurations = Seq(Compile, Test, Runtime),
|
||||
ivyScala = ivyScala)
|
||||
new ivySbt.Module(moduleSetting)
|
||||
}
|
||||
|
||||
def mkIvyConfiguration: IvyConfiguration = {
|
||||
val paths = new IvyPaths(currentBase, Some(currentTarget))
|
||||
val rs = Seq(DefaultMavenRepository)
|
||||
val other = Nil
|
||||
val moduleConfs = Seq(ModuleConfiguration("*", DefaultMavenRepository))
|
||||
val off = false
|
||||
val check = Nil
|
||||
val resCacheDir = currentTarget / "resolution-cache"
|
||||
val uo = UpdateOptions()
|
||||
new InlineIvyConfiguration(paths, rs, other, moduleConfs, off, None, check, Some(resCacheDir), uo, log)
|
||||
}
|
||||
|
||||
def ivyUpdate(module: IvySbt#Module) = {
|
||||
// IO.delete(currentTarget)
|
||||
val config = new UpdateConfiguration(None, false, UpdateLogging.Full)
|
||||
IvyActions.update(module, config, log)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,224 @@
|
|||
package sbt
|
||||
|
||||
import org.specs2._
|
||||
|
||||
class EvictionWarningSpec extends BaseIvySpecification {
|
||||
def is = s2"""
|
||||
|
||||
This is a specification to check the eviction warnings
|
||||
|
||||
Eviction of scala-library whose scalaVersion should
|
||||
be detected $scalaVersionWarn1
|
||||
not be detected if it's diabled $scalaVersionWarn2
|
||||
print out message about the eviction $scalaVersionWarn3
|
||||
print out message about the eviction with callers $scalaVersionWarn4
|
||||
|
||||
Including two (suspect) binary incompatible Java libraries to
|
||||
direct dependencies should
|
||||
be detected as eviction $javaLibWarn1
|
||||
not be detected if it's disabled $javaLibWarn2
|
||||
print out message about the eviction $javaLibWarn3
|
||||
print out message about the eviction with callers $javaLibWarn4
|
||||
|
||||
Including two (suspect) binary compatible Java libraries to
|
||||
direct dependencies should
|
||||
not be detected as eviction $javaLibNoWarn1
|
||||
print out message about the eviction $javaLibNoWarn2
|
||||
|
||||
Including two (suspect) transitively binary incompatible Java libraries to
|
||||
direct dependencies should
|
||||
be not detected as eviction $javaLibTransitiveWarn1
|
||||
be detected if it's enabled $javaLibTransitiveWarn2
|
||||
print out message about the eviction if it's enabled $javaLibTransitiveWarn3
|
||||
|
||||
Including two (suspect) binary incompatible Scala libraries to
|
||||
direct dependencies should
|
||||
be detected as eviction $scalaLibWarn1
|
||||
print out message about the eviction $scalaLibWarn2
|
||||
|
||||
Including two (suspect) binary compatible Scala libraries to
|
||||
direct dependencies should
|
||||
not be detected as eviction $scalaLibNoWarn1
|
||||
print out message about the eviction $scalaLibNoWarn2
|
||||
|
||||
Including two (suspect) transitively binary incompatible Scala libraries to
|
||||
direct dependencies should
|
||||
be not detected as eviction $scalaLibTransitiveWarn1
|
||||
be detected if it's enabled $scalaLibTransitiveWarn2
|
||||
print out message about the eviction if it's enabled $scalaLibTransitiveWarn3
|
||||
"""
|
||||
|
||||
def akkaActor214 = ModuleID("com.typesafe.akka", "akka-actor", "2.1.4", Some("compile")) cross CrossVersion.binary
|
||||
def akkaActor230 = ModuleID("com.typesafe.akka", "akka-actor", "2.3.0", Some("compile")) cross CrossVersion.binary
|
||||
def akkaActor234 = ModuleID("com.typesafe.akka", "akka-actor", "2.3.4", Some("compile")) cross CrossVersion.binary
|
||||
def scala2102 = ModuleID("org.scala-lang", "scala-library", "2.10.2", Some("compile"))
|
||||
def scala2103 = ModuleID("org.scala-lang", "scala-library", "2.10.3", Some("compile"))
|
||||
def scala2104 = ModuleID("org.scala-lang", "scala-library", "2.10.4", Some("compile"))
|
||||
def commonsIo13 = ModuleID("commons-io", "commons-io", "1.3", Some("compile"))
|
||||
def commonsIo14 = ModuleID("commons-io", "commons-io", "1.4", Some("compile"))
|
||||
def commonsIo24 = ModuleID("commons-io", "commons-io", "2.4", Some("compile"))
|
||||
def bnfparser10 = ModuleID("ca.gobits.bnf", "bnfparser", "1.0", Some("compile")) // uses commons-io 2.4
|
||||
def unfilteredUploads080 = ModuleID("net.databinder", "unfiltered-uploads", "0.8.0", Some("compile")) cross CrossVersion.binary // uses commons-io 1.4
|
||||
def bananaSesame04 = ModuleID("org.w3", "banana-sesame", "0.4", Some("compile")) cross CrossVersion.binary // uses akka-actor 2.1.4
|
||||
def akkaRemote234 = ModuleID("com.typesafe.akka", "akka-remote", "2.3.4", Some("compile")) cross CrossVersion.binary // uses akka-actor 2.3.4
|
||||
|
||||
def defaultOptions = EvictionWarningOptions.default
|
||||
|
||||
import ShowLines._
|
||||
|
||||
def scalaVersionDeps = Seq(scala2102, akkaActor230)
|
||||
|
||||
def scalaVersionWarn1 = {
|
||||
val m = module(defaultModuleId, scalaVersionDeps, Some("2.10.2"))
|
||||
val report = ivyUpdate(m)
|
||||
EvictionWarning(m, defaultOptions, report, log).scalaEvictions must have size (1)
|
||||
}
|
||||
|
||||
def scalaVersionWarn2 = {
|
||||
val m = module(defaultModuleId, scalaVersionDeps, Some("2.10.2"))
|
||||
val report = ivyUpdate(m)
|
||||
EvictionWarning(m, defaultOptions.withWarnScalaVersionEviction(false), report, log).scalaEvictions must have size (0)
|
||||
}
|
||||
|
||||
def scalaVersionWarn3 = {
|
||||
val m = module(defaultModuleId, scalaVersionDeps, Some("2.10.2"))
|
||||
val report = ivyUpdate(m)
|
||||
EvictionWarning(m, defaultOptions, report, log).lines must_==
|
||||
List("Scala version was updated by one of library dependencies:",
|
||||
"\t* org.scala-lang:scala-library:2.10.2 -> 2.10.3",
|
||||
"Run 'evicted' to see detailed eviction warnings")
|
||||
}
|
||||
|
||||
def scalaVersionWarn4 = {
|
||||
val m = module(defaultModuleId, scalaVersionDeps, Some("2.10.2"))
|
||||
val report = ivyUpdate(m)
|
||||
EvictionWarning(m, defaultOptions.withShowCallers(true), report, log).lines must_==
|
||||
List("Scala version was updated by one of library dependencies:",
|
||||
"\t* org.scala-lang:scala-library:2.10.2 -> 2.10.3 (caller: com.typesafe.akka:akka-actor_2.10:2.3.0, com.example:foo:0.1.0)")
|
||||
}
|
||||
|
||||
def javaLibDirectDeps = Seq(commonsIo14, commonsIo24)
|
||||
|
||||
def javaLibWarn1 = {
|
||||
val m = module(defaultModuleId, javaLibDirectDeps, Some("2.10.3"))
|
||||
val report = ivyUpdate(m)
|
||||
EvictionWarning(m, defaultOptions, report, log).reportedEvictions must have size (1)
|
||||
}
|
||||
|
||||
def javaLibWarn2 = {
|
||||
val m = module(defaultModuleId, javaLibDirectDeps, Some("2.10.3"))
|
||||
val report = ivyUpdate(m)
|
||||
EvictionWarning(m, defaultOptions.withWarnDirectEvictions(false), report, log).reportedEvictions must have size (0)
|
||||
}
|
||||
|
||||
def javaLibWarn3 = {
|
||||
val m = module(defaultModuleId, javaLibDirectDeps, Some("2.10.3"))
|
||||
val report = ivyUpdate(m)
|
||||
EvictionWarning(m, defaultOptions, report, log).lines must_==
|
||||
List("There may be incompatibilities among your library dependencies.",
|
||||
"Here are some of the libraries that were evicted:",
|
||||
"\t* commons-io:commons-io:1.4 -> 2.4",
|
||||
"Run 'evicted' to see detailed eviction warnings")
|
||||
}
|
||||
|
||||
def javaLibWarn4 = {
|
||||
val m = module(defaultModuleId, javaLibDirectDeps, Some("2.10.3"))
|
||||
val report = ivyUpdate(m)
|
||||
EvictionWarning(m, defaultOptions.withShowCallers(true), report, log).lines must_==
|
||||
List("There may be incompatibilities among your library dependencies.",
|
||||
"Here are some of the libraries that were evicted:",
|
||||
"\t* commons-io:commons-io:1.4 -> 2.4 (caller: com.example:foo:0.1.0)")
|
||||
}
|
||||
|
||||
def javaLibNoWarn1 = {
|
||||
val deps = Seq(commonsIo14, commonsIo13)
|
||||
val m = module(defaultModuleId, deps, Some("2.10.3"))
|
||||
val report = ivyUpdate(m)
|
||||
EvictionWarning(m, defaultOptions, report, log).reportedEvictions must have size (0)
|
||||
}
|
||||
|
||||
def javaLibNoWarn2 = {
|
||||
val deps = Seq(commonsIo14, commonsIo13)
|
||||
val m = module(defaultModuleId, deps, Some("2.10.3"))
|
||||
val report = ivyUpdate(m)
|
||||
EvictionWarning(m, defaultOptions, report, log).lines must_== Nil
|
||||
}
|
||||
|
||||
def javaLibTransitiveDeps = Seq(unfilteredUploads080, bnfparser10)
|
||||
|
||||
def javaLibTransitiveWarn1 = {
|
||||
val m = module(defaultModuleId, javaLibTransitiveDeps, Some("2.10.3"))
|
||||
val report = ivyUpdate(m)
|
||||
EvictionWarning(m, defaultOptions, report, log).reportedEvictions must have size (0)
|
||||
}
|
||||
|
||||
def javaLibTransitiveWarn2 = {
|
||||
val m = module(defaultModuleId, javaLibTransitiveDeps, Some("2.10.3"))
|
||||
val report = ivyUpdate(m)
|
||||
EvictionWarning(m, defaultOptions.withWarnTransitiveEvictions(true), report, log).reportedEvictions must have size (1)
|
||||
}
|
||||
|
||||
def javaLibTransitiveWarn3 = {
|
||||
val m = module(defaultModuleId, javaLibTransitiveDeps, Some("2.10.3"))
|
||||
val report = ivyUpdate(m)
|
||||
EvictionWarning(m, defaultOptions.withWarnTransitiveEvictions(true).withShowCallers(true), report, log).lines must_==
|
||||
List("There may be incompatibilities among your library dependencies.",
|
||||
"Here are some of the libraries that were evicted:",
|
||||
"\t* commons-io:commons-io:1.4 -> 2.4 (caller: ca.gobits.bnf:bnfparser:1.0, net.databinder:unfiltered-uploads_2.10:0.8.0)")
|
||||
}
|
||||
|
||||
def scalaLibWarn1 = {
|
||||
val deps = Seq(scala2104, akkaActor214, akkaActor234)
|
||||
val m = module(defaultModuleId, deps, Some("2.10.4"))
|
||||
val report = ivyUpdate(m)
|
||||
EvictionWarning(m, defaultOptions, report, log).reportedEvictions must have size (1)
|
||||
}
|
||||
|
||||
def scalaLibWarn2 = {
|
||||
val deps = Seq(scala2104, akkaActor214, akkaActor234)
|
||||
val m = module(defaultModuleId, deps, Some("2.10.4"))
|
||||
val report = ivyUpdate(m)
|
||||
EvictionWarning(m, defaultOptions, report, log).lines must_==
|
||||
List("There may be incompatibilities among your library dependencies.",
|
||||
"Here are some of the libraries that were evicted:",
|
||||
"\t* com.typesafe.akka:akka-actor_2.10:2.1.4 -> 2.3.4",
|
||||
"Run 'evicted' to see detailed eviction warnings")
|
||||
}
|
||||
|
||||
def scalaLibNoWarn1 = {
|
||||
val deps = Seq(scala2104, akkaActor230, akkaActor234)
|
||||
val m = module(defaultModuleId, deps, Some("2.10.4"))
|
||||
val report = ivyUpdate(m)
|
||||
EvictionWarning(m, defaultOptions, report, log).reportedEvictions must have size (0)
|
||||
}
|
||||
|
||||
def scalaLibNoWarn2 = {
|
||||
val deps = Seq(scala2104, akkaActor230, akkaActor234)
|
||||
val m = module(defaultModuleId, deps, Some("2.10.4"))
|
||||
val report = ivyUpdate(m)
|
||||
EvictionWarning(m, defaultOptions, report, log).lines must_== Nil
|
||||
}
|
||||
|
||||
def scalaLibTransitiveDeps = Seq(scala2104, bananaSesame04, akkaRemote234)
|
||||
|
||||
def scalaLibTransitiveWarn1 = {
|
||||
val m = module(defaultModuleId, scalaLibTransitiveDeps, Some("2.10.4"))
|
||||
val report = ivyUpdate(m)
|
||||
EvictionWarning(m, defaultOptions, report, log).reportedEvictions must have size (0)
|
||||
}
|
||||
|
||||
def scalaLibTransitiveWarn2 = {
|
||||
val m = module(defaultModuleId, scalaLibTransitiveDeps, Some("2.10.4"))
|
||||
val report = ivyUpdate(m)
|
||||
EvictionWarning(m, defaultOptions.withWarnTransitiveEvictions(true), report, log).reportedEvictions must have size (1)
|
||||
}
|
||||
|
||||
def scalaLibTransitiveWarn3 = {
|
||||
val m = module(defaultModuleId, scalaLibTransitiveDeps, Some("2.10.4"))
|
||||
val report = ivyUpdate(m)
|
||||
EvictionWarning(m, defaultOptions.withWarnTransitiveEvictions(true).withShowCallers(true), report, log).lines must_==
|
||||
List("There may be incompatibilities among your library dependencies.",
|
||||
"Here are some of the libraries that were evicted:",
|
||||
"\t* com.typesafe.akka:akka-actor_2.10:2.1.4 -> 2.3.4 (caller: com.typesafe.akka:akka-remote_2.10:2.3.4, org.w3:banana-sesame_2.10:0.4, org.w3:banana-rdf_2.10:0.4)")
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,122 @@
|
|||
package sbt
|
||||
|
||||
import org.specs2._
|
||||
|
||||
class VersionNumberSpec extends Specification {
|
||||
def is = s2"""
|
||||
|
||||
This is a specification to check the version number parsing.
|
||||
|
||||
1 should
|
||||
${beParsedAs("1", Seq(1), Seq(), Seq())}
|
||||
${breakDownTo("1", Some(1))}
|
||||
|
||||
1.0 should
|
||||
${beParsedAs("1.0", Seq(1, 0), Seq(), Seq())}
|
||||
${breakDownTo("1.0", Some(1), Some(0))}
|
||||
|
||||
1.0.0 should
|
||||
${beParsedAs("1.0.0", Seq(1, 0, 0), Seq(), Seq())}
|
||||
${breakDownTo("1.0.0", Some(1), Some(0), Some(0))}
|
||||
|
||||
${beSemVerCompatWith("1.0.0", "1.0.1")}
|
||||
${beSemVerCompatWith("1.0.0", "1.1.1")}
|
||||
${notBeSemVerCompatWith("1.0.0", "2.0.0")}
|
||||
${notBeSemVerCompatWith("1.0.0", "1.0.0-M1")}
|
||||
|
||||
${beSecSegCompatWith("1.0.0", "1.0.1")}
|
||||
${notBeSecSegCompatWith("1.0.0", "1.1.1")}
|
||||
${notBeSecSegCompatWith("1.0.0", "2.0.0")}
|
||||
${notBeSecSegCompatWith("1.0.0", "1.0.0-M1")}
|
||||
|
||||
1.0.0.0 should
|
||||
${beParsedAs("1.0.0.0", Seq(1, 0, 0, 0), Seq(), Seq())}
|
||||
${breakDownTo("1.0.0.0", Some(1), Some(0), Some(0), Some(0))}
|
||||
|
||||
0.12.0 should
|
||||
${beParsedAs("0.12.0", Seq(0, 12, 0), Seq(), Seq())}
|
||||
${breakDownTo("0.12.0", Some(0), Some(12), Some(0))}
|
||||
|
||||
${notBeSemVerCompatWith("0.12.0", "0.12.0-RC1")}
|
||||
${notBeSemVerCompatWith("0.12.0", "0.12.1")}
|
||||
${notBeSemVerCompatWith("0.12.0", "0.12.1-M1")}
|
||||
|
||||
${notBeSecSegCompatWith("0.12.0", "0.12.0-RC1")}
|
||||
${beSecSegCompatWith("0.12.0", "0.12.1")}
|
||||
${beSecSegCompatWith("0.12.0", "0.12.1-M1")}
|
||||
|
||||
0.1.0-SNAPSHOT should
|
||||
${beParsedAs("0.1.0-SNAPSHOT", Seq(0, 1, 0), Seq("SNAPSHOT"), Seq())}
|
||||
|
||||
${beSemVerCompatWith("0.1.0-SNAPSHOT", "0.1.0-SNAPSHOT")}
|
||||
${notBeSemVerCompatWith("0.1.0-SNAPSHOT", "0.1.0")}
|
||||
${beSemVerCompatWith("0.1.0-SNAPSHOT", "0.1.0-SNAPSHOT+001")}
|
||||
|
||||
${beSecSegCompatWith("0.1.0-SNAPSHOT", "0.1.0-SNAPSHOT")}
|
||||
${notBeSecSegCompatWith("0.1.0-SNAPSHOT", "0.1.0")}
|
||||
${beSecSegCompatWith("0.1.0-SNAPSHOT", "0.1.0-SNAPSHOT+001")}
|
||||
|
||||
0.1.0-M1 should
|
||||
${beParsedAs("0.1.0-M1", Seq(0, 1, 0), Seq("M1"), Seq())}
|
||||
|
||||
0.1.0-RC1 should
|
||||
${beParsedAs("0.1.0-RC1", Seq(0, 1, 0), Seq("RC1"), Seq())}
|
||||
|
||||
0.1.0-MSERVER-1 should
|
||||
${beParsedAs("0.1.0-MSERVER-1", Seq(0, 1, 0), Seq("MSERVER", "1"), Seq())}
|
||||
|
||||
2.10.4-20140115-000117-b3a-sources should
|
||||
${beParsedAs("2.10.4-20140115-000117-b3a-sources", Seq(2, 10, 4), Seq("20140115", "000117", "b3a", "sources"), Seq())}
|
||||
|
||||
${beSemVerCompatWith("2.10.4-20140115-000117-b3a-sources", "2.0.0")}
|
||||
|
||||
${notBeSecSegCompatWith("2.10.4-20140115-000117-b3a-sources", "2.0.0")}
|
||||
|
||||
20140115000117-b3a-sources should
|
||||
${beParsedAs("20140115000117-b3a-sources", Seq(20140115000117L), Seq("b3a", "sources"), Seq())}
|
||||
|
||||
1.0.0-alpha+001+002 should
|
||||
${beParsedAs("1.0.0-alpha+001+002", Seq(1, 0, 0), Seq("alpha"), Seq("+001", "+002"))}
|
||||
|
||||
non.space.!?string should
|
||||
${beParsedAs("non.space.!?string", Seq(), Seq(), Seq("non.space.!?string"))}
|
||||
|
||||
space !?string should
|
||||
${beParsedAsError("space !?string")}
|
||||
|
||||
blank string should
|
||||
${beParsedAsError("")}
|
||||
"""
|
||||
|
||||
def beParsedAs(s: String, ns: Seq[Long], ts: Seq[String], es: Seq[String]) =
|
||||
s match {
|
||||
case VersionNumber(ns1, ts1, es1) if (ns1 == ns && ts1 == ts && es1 == es) =>
|
||||
(VersionNumber(ns, ts, es).toString must_== s) and
|
||||
(VersionNumber(ns, ts, es) == VersionNumber(ns, ts, es))
|
||||
case VersionNumber(ns1, ts1, es1) =>
|
||||
sys.error(s"$ns1, $ts1, $es1")
|
||||
}
|
||||
def breakDownTo(s: String, major: Option[Long], minor: Option[Long] = None,
|
||||
patch: Option[Long] = None, buildNumber: Option[Long] = None) =
|
||||
s match {
|
||||
case VersionNumber(ns, ts, es) =>
|
||||
val v = VersionNumber(ns, ts, es)
|
||||
(v._1 must_== major) and
|
||||
(v._2 must_== minor) and
|
||||
(v._3 must_== patch) and
|
||||
(v._4 must_== buildNumber)
|
||||
}
|
||||
def beParsedAsError(s: String) =
|
||||
s match {
|
||||
case VersionNumber(ns1, ts1, es1) => failure
|
||||
case _ => success
|
||||
}
|
||||
def beSemVerCompatWith(v1: String, v2: String) =
|
||||
VersionNumber.SemVer.isCompatible(VersionNumber(v1), VersionNumber(v2)) must_== true
|
||||
def notBeSemVerCompatWith(v1: String, v2: String) =
|
||||
VersionNumber.SemVer.isCompatible(VersionNumber(v1), VersionNumber(v2)) must_== false
|
||||
def beSecSegCompatWith(v1: String, v2: String) =
|
||||
VersionNumber.SecondSegment.isCompatible(VersionNumber(v1), VersionNumber(v2)) must_== true
|
||||
def notBeSecSegCompatWith(v1: String, v2: String) =
|
||||
VersionNumber.SecondSegment.isCompatible(VersionNumber(v1), VersionNumber(v2)) must_== false
|
||||
}
|
||||
Loading…
Reference in New Issue