From 71949680635eb37c8e43e348287386862d847255 Mon Sep 17 00:00:00 2001 From: Matthew de Detrich Date: Thu, 3 Aug 2023 16:40:04 +0200 Subject: [PATCH] Automatically handle MiMa for all versions --- project/MiMa.scala | 93 +++++++++++++++++++++++++++++++++++++--------- 1 file changed, 75 insertions(+), 18 deletions(-) diff --git a/project/MiMa.scala b/project/MiMa.scala index 4bdbb9f573d..2c1a5060110 100644 --- a/project/MiMa.scala +++ b/project/MiMa.scala @@ -12,15 +12,19 @@ */ import scala.collection.immutable +import scala.concurrent.Await +import scala.concurrent.duration._ +import scala.util.matching.Regex.Groups + import sbt._ import sbt.Keys._ +import sbt.librarymanagement.Http.http +import gigahorse.GigahorseSupport.url import com.typesafe.tools.mima.plugin.MimaPlugin import com.typesafe.tools.mima.plugin.MimaPlugin.autoImport._ object MiMa extends AutoPlugin { - private val latestPatchOf10 = 0 - override def requires = MimaPlugin override def trigger = allRequirements @@ -29,35 +33,88 @@ object MiMa extends AutoPlugin { override val projectSettings = Seq( mimaReportSignatureProblems := true, - mimaPreviousArtifacts := pekkoPreviousArtifacts(name.value, organization.value), - checkMimaFilterDirectories := checkFilterDirectories(baseDirectory.value)) + mimaPreviousArtifacts := pekkoPreviousArtifacts(name.value, organization.value, version.value, + scalaBinaryVersion.value), + checkMimaFilterDirectories := checkFilterDirectories(baseDirectory.value, version.value)) - def checkFilterDirectories(moduleRoot: File): Unit = { + def checkFilterDirectories(moduleRoot: File, version: String): Unit = { + val patchVersion = patchFromVersion(version).toInt val nextVersionFilterDir = - moduleRoot / "src" / "main" / "mima-filters" / s"1.0.${latestPatchOf10 + 1}.backwards.excludes" + moduleRoot / "src" / "main" / "mima-filters" / s"1.0.${patchVersion + 1}.backwards.excludes" if (nextVersionFilterDir.exists()) { throw new IllegalArgumentException(s"Incorrect mima filter directory exists: '$nextVersionFilterDir' " + - s"should be with number from current release '${moduleRoot / "src" / "main" / "mima-filters" / s"1.0.$latestPatchOf10.backwards.excludes"}") + s"should be with number from current release '${moduleRoot / "src" / "main" / "mima-filters" / s"1.0.$patchVersion.backwards.excludes"}") } } + private def patchFromVersion(version: String): String = + version.split("\\.").last + + private def latestPatchOfReleasedVersion(projectName: String, organization: String, scalaBinaryVersion: String, + major: Long, + minor: Long): Option[Int] = { + val orgWithSlashes = organization.replaceAll("\\.", "/") + + val baseUrl = s"${Resolver.DefaultMavenRepositoryRoot}$orgWithSlashes/${projectName}_$scalaBinaryVersion/" + + val fullResponse = Await.result(http.processFull(url(baseUrl)), 10.seconds) + + fullResponse.status match { + case s if s / 100 == 2 => + val versionR = s"""href="$major.$minor.(\\d+)""".r + + versionR.findAllMatchIn(fullResponse.bodyAsString).map { + case Groups(patchVersion) => + patchVersion + }.toArray.lastOption.map(_.toInt) + case s if s == 404 => + // Its possible for us to get non deployed artifacts as an input + // to this function which results in 404 + None + case s => + throw new IllegalArgumentException(s"Unexpected status code: $s for url: $baseUrl") + } + } + + def allArtifactsInPreviousMinorRelease(minor: Long, projectName: String, organization: String, + scalaBinaryVersion: String, major: Long): Set[ModuleID] = { + val previousMinor = minor - 1 + latestPatchOfReleasedVersion(projectName, organization, scalaBinaryVersion, major, previousMinor).map( + previousLatestPatchVersion => + expandVersions(major, previousMinor, 0 to previousLatestPatchVersion).map(v => + organization %% projectName % v).toSet).getOrElse(Set.empty) + } + def pekkoPreviousArtifacts( projectName: String, - organization: String): Set[sbt.ModuleID] = { - val versions: Seq[String] = { - val firstPatchOf10 = 0 + organization: String, + version: String, + scalaBinaryVersion: String): Set[sbt.ModuleID] = { + val Some((major, minor)) = CrossVersion.partialVersion(version) + val patchVersion = patchFromVersion(version) - val pekko10Previous = expandVersions(1, 0, 0 to latestPatchOf10) + val milestoneStartingPatchRegex = """(\d+)-M0(.*)""".r + val firstPatchThatIsRc = """0-RC(\d+)$""".r + val strippedPatchRegex = """(\d+)(.*)""".r - pekko10Previous + patchVersion match { + case milestoneStartingPatchRegex(_, _) => + // We use M0 to signal that a bump in minor has happened however that minor + // has not been released yet, so lets compare to the entire previous minor series + allArtifactsInPreviousMinorRelease(minor, projectName, organization, scalaBinaryVersion, major) + case "0" | firstPatchThatIsRc(_) => + // This case occurs when we are right at the point of a new release for a new version, + // including release candidates + allArtifactsInPreviousMinorRelease(minor, projectName, organization, scalaBinaryVersion, major) + case strippedPatchRegex(v, _) if v.toInt == 0 => + // This case is when we currently have a snapshot with first version of minor being released + Set(organization %% projectName % s"$major.$minor.0") + case strippedPatchRegex(v, _) => + // Standard case, lets check against all versions up until this one + expandVersions(major, minor, 0 until v.toInt).map(v => organization %% projectName % v).toSet } - - // check against all binary compatible artifacts - versions.map { v => - organization %% projectName % v - }.toSet } - private def expandVersions(major: Int, minor: Int, patches: immutable.Seq[Int]): immutable.Seq[String] = + private def expandVersions(major: Long, minor: Long, patches: immutable.Seq[Int]): immutable.Seq[String] = patches.map(patch => s"$major.$minor.$patch") }