Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 18 additions & 20 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ name: ci
on:
pull_request:
push:
branches: ['main']
tags: ['[0-9]']

jobs:
build:
Expand All @@ -14,15 +12,15 @@ jobs:
java: [8, 11, 17]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-java@v2
- uses: actions/checkout@v5
- uses: actions/setup-java@v5
with:
distribution: temurin
java-version: ${{matrix.java}}
- uses: coursier/cache-action@v6
cache: sbt
- uses: sbt/setup-sbt@v1
# note use of an old sbt version, as a smoke test
- run: "sbt +test mimaReportBinaryIssues 'set sbtplugin/scriptedSbt := \"1.3.13\"' 'scripted sbt-mima-plugin/minimal'"
- run: "sbt +test mimaReportBinaryIssues 'set sbtplugin/scriptedSbt := \"1.5.8\"' 'scripted sbt-mima-plugin/minimal'"
testFunctional:
needs: build
strategy:
Expand All @@ -31,39 +29,39 @@ jobs:
scala: [2.11, 2.12, 2.13, 3]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-java@v2
- uses: actions/checkout@v5
- uses: actions/setup-java@v5
with:
distribution: temurin
distribution: zulu
java-version: 8
cache: sbt
- uses: sbt/setup-sbt@v1
- uses: coursier/cache-action@v6
- run: sbt "functional-tests/runMain com.typesafe.tools.mima.lib.UnitTests -${{ matrix.scala }}"
testScripted:
needs: build
strategy:
fail-fast: false
matrix:
scripted: [1of2, 2of2]
scala: [2.12.x, 3.x]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-java@v2
- uses: actions/checkout@v5
- uses: actions/setup-java@v5
with:
distribution: temurin
distribution: zulu
java-version: 8
- uses: coursier/cache-action@v6
cache: sbt
- uses: sbt/setup-sbt@v1
- run: sbt "scripted sbt-mima-plugin/*${{ matrix.scripted }}"
- run: sbt "++${{ matrix.scala }} sbtplugin/scripted"
testIntegration:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-java@v2
- uses: actions/checkout@v5
- uses: actions/setup-java@v5
with:
distribution: temurin
distribution: zulu
java-version: 8
- uses: coursier/cache-action@v6
cache: sbt
- uses: sbt/setup-sbt@v1
- run: sbt IntegrationTest/test
16 changes: 14 additions & 2 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import mimabuild._

inThisBuild(Seq(
organization := "com.typesafe",
licenses := Seq("Apache License v2.0" -> url("http://www.apache.org/licenses/LICENSE-2.0")),
licenses := Seq(License.Apache2),
homepage := Some(url("http://github.com/lightbend-labs/mima")),
developers := List(
Developer("mdotta", "Mirco Dotta", "@dotta", url("https://github.com/dotta")),
Expand All @@ -14,7 +14,11 @@ inThisBuild(Seq(
versionScheme := Some("early-semver"),
scalaVersion := scala212,
resolvers ++= (if (isStaging) List(stagingResolver) else Nil),
publishTo := Some(if (isSnapshot.value) Opts.resolver.sonatypeOssSnapshots.head else Opts.resolver.sonatypeStaging),
publishTo := {
val centralSnapshots = "https://central.sonatype.com/repository/maven-snapshots/"
if (isSnapshot.value) Some("central-snapshots" at centralSnapshots)
else localStaging.value
},
))

def compilerOptions(scalaVersion: String): Seq[String] =
Expand Down Expand Up @@ -49,6 +53,7 @@ commands += Command.command("testStaging") { state =>
val scala212 = "2.12.20"
val scala213 = "2.13.16"
val scala3 = "3.3.6"
val scala3_7 = "3.7.2"

val root = project.in(file(".")).settings(
name := "mima",
Expand Down Expand Up @@ -94,6 +99,13 @@ val cli = crossProject(JVMPlatform)

val sbtplugin = project.enablePlugins(SbtPlugin).dependsOn(core.jvm).settings(
name := "sbt-mima-plugin",
crossScalaVersions ++= Seq(scala3_7),
(pluginCrossBuild / sbtVersion) := {
scalaBinaryVersion.value match {
case "2.12" => "1.5.8"
case _ => "2.0.0-RC3"
}
},
scalacOptions ++= compilerOptions(scalaVersion.value),
// drop the previous value to drop running Test/compile
scriptedDependencies := Def.task(()).dependsOn(publishLocal, core.jvm / publishLocal).value,
Expand Down
4 changes: 4 additions & 0 deletions project/MimaSettings.scala
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ object MimaSettings {
// * com.typesafe.tools.mima.core.ProblemFilters
// * com.typesafe.tools.mima.core.*Problem
// * com.typesafe.tools.mima.core.util.log.Logging
exclude[MissingClassProblem]("com.typesafe.tools.mima.plugin.MimaPlugin$EmptyMap"),
exclude[MissingClassProblem]("com.typesafe.tools.mima.plugin.MimaPlugin$EmptySet"),
exclude[MissingClassProblem]("com.typesafe.tools.mima.plugin.MimaPlugin$NoPreviousArtifacts$"),
exclude[MissingClassProblem]("com.typesafe.tools.mima.plugin.MimaPlugin$NoPreviousClassfiles$"),
),
)
}
5 changes: 0 additions & 5 deletions project/plugins.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,6 @@ scalacOptions ++= Seq(
"-Ywarn-unused:_,-imports",
)

// Useful to self-test releases
val stagingResolver = "Sonatype OSS Staging" at "https://oss.sonatype.org/content/repositories/staging"
def isStaging = sys.props.contains("mimabuild.staging")
resolvers ++= (if (isStaging) List(stagingResolver) else Nil)

addSbtPlugin("com.github.sbt" % "sbt-dynver" % "5.1.1")
addSbtPlugin("com.github.sbt" % "sbt-pgp" % "2.3.1")
addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "1.1.4")
Expand Down
41 changes: 41 additions & 0 deletions sbtplugin/src/main/scala-2.12/PluginCompat.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.typesafe.tools.mima
package plugin

import sbt.*

object PluginCompat {
def toOldClasspath(cp: Seq[Attributed[File]]): Seq[Attributed[File]] = cp

// This adds `Def.uncached(...)`
implicit class DefOp(singleton: Def.type) {
def uncached[A1](a: A1): A1 = a
}

// Used to differentiate unset mimaPreviousArtifacts from empty mimaPreviousArtifacts
private[plugin] object NoPreviousArtifacts extends EmptySet[ModuleID]
private[plugin] object NoPreviousClassfiles extends EmptyMap[ModuleID, File]

private[plugin] sealed class EmptySet[A] extends Set[A] {
def iterator = Iterator.empty
def contains(elem: A) = false
def + (elem: A) = Set(elem)
def - (elem: A) = this
override def size = 0
override def foreach[U](f: A => U) = ()
override def toSet[B >: A]: Set[B] = this.asInstanceOf[Set[B]]
}

private[plugin] sealed class EmptyMap[K, V] extends Map[K, V] {
def get(key: K) = None
def iterator = Iterator.empty
def + [V1 >: V](kv: (K, V1)) = updated(kv._1, kv._2)
def - (key: K) = this

override def size = 0
override def contains(key: K) = false
override def getOrElse[V1 >: V](key: K, default: => V1) = default
override def updated[V1 >: V](key: K, value: V1) = Map(key -> value)

override def apply(key: K) = throw new NoSuchElementException(s"key not found: $key")
}
}
36 changes: 36 additions & 0 deletions sbtplugin/src/main/scala-3/PluginCompat.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.typesafe.tools.mima
package plugin

import sbt.*
import xsbti.{ FileConverter, HashedVirtualFileRef }

object PluginCompat:
inline def toOldClasspath(cp: Seq[Attributed[HashedVirtualFileRef]])(using conv: FileConverter): Seq[Attributed[File]] =
cp.map(_.map(x => conv.toPath(x).toFile))

// Used to differentiate unset mimaPreviousArtifacts from empty mimaPreviousArtifacts
private[plugin] object NoPreviousArtifacts extends EmptySet[ModuleID]
private[plugin] object NoPreviousClassfiles extends EmptyMap[ModuleID, File]

private[plugin] sealed class EmptySet[A] extends Set[A]:
def iterator = Iterator.empty
def contains(elem: A) = false
def excl(elem: A) = this
def incl(elem: A) = Set(elem)

override def size = 0
override def foreach[U](f: A => U) = ()
override def toSet[B >: A]: Set[B] = this.asInstanceOf[Set[B]]

private[plugin] sealed class EmptyMap[K, V] extends Map[K, V]:
def get(key: K) = None
def iterator = Iterator.empty
def removed(key: K) = this

override def size = 0
override def contains(key: K) = false
override def getOrElse[V1 >: V](key: K, default: => V1) = default
override def updated[V1 >: V](key: K, value: V1) = Map(key -> value)

override def apply(key: K) = throw new NoSuchElementException(s"key not found: $key")
end PluginCompat
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,39 @@ object MimaKeys extends MimaKeys
class MimaKeys {

final val mimaPreviousArtifacts = settingKey[Set[ModuleID]]("Previous released artifacts used to test binary compatibility.")

@transient
final val mimaReportBinaryIssues = taskKey[Unit]("Logs all binary incompatibilities to the sbt console/logs.")
final val mimaExcludeAnnotations = settingKey[Seq[String]]("The fully-qualified class names of annotations that exclude problems")

@transient
final val mimaBinaryIssueFilters = taskKey[Seq[ProblemFilter]]("Filters to apply to binary issues found. Applies both to backward and forward binary compatibility checking.")
final val mimaFailOnProblem = settingKey[Boolean]("if true, fail the build on binary incompatibility detection.")
final val mimaFailOnNoPrevious = settingKey[Boolean]("if true, fail the build if no previous artifacts are set.")
final val mimaReportSignatureProblems = settingKey[Boolean]("if true, report `IncompatibleSignatureProblem`s.")

@transient
final val mimaDependencyResolution = taskKey[DependencyResolution]("DependencyResolution to use to fetch previous artifacts.")

@transient
final val mimaPreviousClassfiles = taskKey[Map[ModuleID, File]]("Directories or jars containing the previous class files used to test compatibility with a given module.")

@transient
final val mimaCurrentClassfiles = taskKey[File]("Directory or jar containing the current class files used to test compatibility.")

@transient
final val mimaCheckDirection = settingKey[String]("Compatibility checking direction; default is \"backward\", but can also be \"forward\" or \"both\".")

@transient
final val mimaFindBinaryIssues = taskKey[Map[ModuleID, (List[Problem], List[Problem])]]("All backward and forward binary incompatibilities between a given module and current project.")

@transient
final val mimaBackwardIssueFilters = taskKey[Map[String, Seq[ProblemFilter]]]("Filters to apply to binary issues found grouped by version of a module checked against. These filters only apply to backward compatibility checking.")

@transient
final val mimaForwardIssueFilters = taskKey[Map[String, Seq[ProblemFilter]]]("Filters to apply to binary issues found grouped by version of a module checked against. These filters only apply to forward compatibility checking.")

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

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

import sbt.*, Keys.*
import core.*
import PluginCompat.*
import xsbti.FileConverter
import scala.annotation.nowarn

/** MiMa's sbt plugin. */
object MimaPlugin extends AutoPlugin {
override def trigger = allRequirements

private val NoPreviousArtifacts = PluginCompat.NoPreviousArtifacts
private val NoPreviousClassfiles = PluginCompat.NoPreviousClassfiles

object autoImport extends MimaKeys
import autoImport.*

override def globalSettings: Seq[Def.Setting[_]] = Seq(
override def globalSettings: Seq[Def.Setting[?]] = Seq(
mimaPreviousArtifacts := NoPreviousArtifacts,
mimaExcludeAnnotations := Nil,
mimaBinaryIssueFilters := Nil,
Expand All @@ -21,7 +27,7 @@ object MimaPlugin extends AutoPlugin {
mimaCheckDirection := "backward",
)

override def projectSettings: Seq[Def.Setting[_]] = Seq(
override def projectSettings: Seq[Def.Setting[?]] = Seq(
mimaReportBinaryIssues := {
binaryIssuesIterator.value.foreach { case (moduleId, problems) =>
SbtMima.reportModuleErrors(
Expand All @@ -43,14 +49,14 @@ object MimaPlugin extends AutoPlugin {
},
mimaCurrentClassfiles := (Compile / classDirectory).value,
mimaFindBinaryIssues := binaryIssuesIterator.value.toMap,
mimaFindBinaryIssues / fullClasspath := (Compile / fullClasspath).value,
mimaFindBinaryIssues / fullClasspath := Def.uncached((Compile / fullClasspath).value),
mimaBackwardIssueFilters := SbtMima.issueFiltersFromFiles(mimaFiltersDirectory.value, "\\.(?:backward[s]?|both)\\.excludes".r, streams.value),
mimaForwardIssueFilters := SbtMima.issueFiltersFromFiles(mimaFiltersDirectory.value, "\\.(?:forward[s]?|both)\\.excludes".r, streams.value),
mimaFiltersDirectory := (Compile / sourceDirectory).value / "mima-filters",
)

@deprecated("Switch to enablePlugins(MimaPlugin)", "0.7.0")
def mimaDefaultSettings: Seq[Setting[_]] = globalSettings ++ buildSettings ++ projectSettings
def mimaDefaultSettings: Seq[Setting[?]] = globalSettings ++ buildSettings ++ projectSettings

trait ArtifactsToClassfiles {
def toClassfiles(previousArtifacts: Set[ModuleID]): Map[ModuleID, File]
Expand Down Expand Up @@ -86,6 +92,9 @@ object MimaPlugin extends AutoPlugin {
val excludeAnnots = mimaExcludeAnnotations.value.toList
val failOnNoPrevious = mimaFailOnNoPrevious.value
val projName = name.value
val conv0 = fileConverter.value
@nowarn
implicit val conv: FileConverter = conv0

(prevClassfiles, checkDirection) => {
if (prevClassfiles eq NoPreviousClassfiles) {
Expand All @@ -96,7 +105,15 @@ object MimaPlugin extends AutoPlugin {
}

prevClassfiles.iterator.map { case (moduleId, prevClassfiles) =>
moduleId -> SbtMima.runMima(prevClassfiles, currClassfiles, cp, checkDirection, sv, log, excludeAnnots)
moduleId -> SbtMima.runMima(
prevClassfiles,
currClassfiles,
toOldClasspath(cp),
checkDirection,
sv,
log,
excludeAnnots
)
}
}
}
Expand All @@ -111,33 +128,4 @@ object MimaPlugin extends AutoPlugin {
private val binaryIssuesIterator = Def.task {
binaryIssuesFinder.value.runMima(mimaPreviousClassfiles.value, mimaCheckDirection.value)
}

// Used to differentiate unset mimaPreviousArtifacts from empty mimaPreviousArtifacts
private object NoPreviousArtifacts extends EmptySet[ModuleID]
private object NoPreviousClassfiles extends EmptyMap[ModuleID, File]

private sealed class EmptySet[A] extends Set[A] {
def iterator = Iterator.empty
def contains(elem: A) = false
def + (elem: A) = Set(elem)
def - (elem: A) = this

override def size = 0
override def foreach[U](f: A => U) = ()
override def toSet[B >: A]: Set[B] = this.asInstanceOf[Set[B]]
}

private sealed class EmptyMap[K, V] extends Map[K, V] {
def get(key: K) = None
def iterator = Iterator.empty
def + [V1 >: V](kv: (K, V1)) = updated(kv._1, kv._2)
def - (key: K) = this

override def size = 0
override def contains(key: K) = false
override def getOrElse[V1 >: V](key: K, default: => V1) = default
override def updated[V1 >: V](key: K, value: V1) = Map(key -> value)

override def apply(key: K) = throw new NoSuchElementException(s"key not found: $key")
}
}
Loading