Utility to check that same version of all modules is used
* Stolen from Cinnamon * Can be used from outside of Akka, e.g. Akka HTTP or Lagom
This commit is contained in:
parent
91240880b8
commit
e20b0287fd
4 changed files with 254 additions and 0 deletions
|
|
@ -0,0 +1,38 @@
|
||||||
|
/**
|
||||||
|
* Copyright (C) 2015–2018 Lightbend Inc. <https://www.lightbend.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
package akka.util
|
||||||
|
|
||||||
|
import org.scalatest.Matchers
|
||||||
|
import org.scalatest.WordSpec
|
||||||
|
import akka.util.ManifestInfo.Version
|
||||||
|
|
||||||
|
class ManifestInfoVersionSpec extends WordSpec with Matchers {
|
||||||
|
|
||||||
|
"Version" should {
|
||||||
|
|
||||||
|
"compare full version" in {
|
||||||
|
new Version("1.2.3") should ===(new Version("1.2.3"))
|
||||||
|
new Version("1.2.3") should !==(new Version("1.2.4"))
|
||||||
|
new Version("1.2.4") should be > new Version("1.2.3")
|
||||||
|
new Version("3.2.1") should be > new Version("1.2.3")
|
||||||
|
new Version("3.2.1") should be < new Version("3.3.1")
|
||||||
|
}
|
||||||
|
|
||||||
|
"compare partial version" in {
|
||||||
|
new Version("1.2") should ===(new Version("1.2"))
|
||||||
|
new Version("1.2") should !==(new Version("1.3"))
|
||||||
|
new Version("1.2.1") should be > new Version("1.2")
|
||||||
|
new Version("2.4") should be > new Version("2.3")
|
||||||
|
new Version("3.2") should be < new Version("3.2.7")
|
||||||
|
}
|
||||||
|
|
||||||
|
"compare extra" in {
|
||||||
|
new Version("1.2.3-foo") should ===(new Version("1.2.3-foo"))
|
||||||
|
new Version("1.2.3-foo") should !==(new Version("1.2.3-bar"))
|
||||||
|
new Version("1.2-foo") should be > new Version("1.2.3")
|
||||||
|
new Version("1.2.3-foo") should be > new Version("1.2.3-bar")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -822,6 +822,33 @@ private[akka] class ActorSystemImpl(
|
||||||
def /(actorName: String): ActorPath = guardian.path / actorName
|
def /(actorName: String): ActorPath = guardian.path / actorName
|
||||||
def /(path: Iterable[String]): ActorPath = guardian.path / path
|
def /(path: Iterable[String]): ActorPath = guardian.path / path
|
||||||
|
|
||||||
|
// Used for ManifestInfo.checkSameVersion
|
||||||
|
private def allModules: List[String] = List(
|
||||||
|
"akka-actor",
|
||||||
|
"akka-actor-testkit-typed",
|
||||||
|
"akka-actor-typed",
|
||||||
|
"akka-agent",
|
||||||
|
"akka-camel",
|
||||||
|
"akka-cluster",
|
||||||
|
"akka-cluster-metrics",
|
||||||
|
"akka-cluster-sharding",
|
||||||
|
"akka-cluster-sharding-typed",
|
||||||
|
"akka-cluster-tools",
|
||||||
|
"akka-cluster-typed",
|
||||||
|
"akka-distributed-data",
|
||||||
|
"akka-multi-node-testkit",
|
||||||
|
"akka-osgi",
|
||||||
|
"akka-persistence",
|
||||||
|
"akka-persistence-query",
|
||||||
|
"akka-persistence-shared",
|
||||||
|
"akka-persistence-typed",
|
||||||
|
"akka-protobuf",
|
||||||
|
"akka-remote",
|
||||||
|
"akka-slf4j",
|
||||||
|
"akka-stream",
|
||||||
|
"akka-stream-testkit",
|
||||||
|
"akka-stream-typed")
|
||||||
|
|
||||||
@volatile private var _initialized = false
|
@volatile private var _initialized = false
|
||||||
/**
|
/**
|
||||||
* Asserts that the ActorSystem has been fully initialized. Can be used to guard code blocks that might accidentally
|
* Asserts that the ActorSystem has been fully initialized. Can be used to guard code blocks that might accidentally
|
||||||
|
|
@ -844,6 +871,7 @@ private[akka] class ActorSystemImpl(
|
||||||
if (settings.LogDeadLetters > 0)
|
if (settings.LogDeadLetters > 0)
|
||||||
logDeadLetterListener = Some(systemActorOf(Props[DeadLetterListener], "deadLetterListener"))
|
logDeadLetterListener = Some(systemActorOf(Props[DeadLetterListener], "deadLetterListener"))
|
||||||
eventStream.startUnsubscriber()
|
eventStream.startUnsubscriber()
|
||||||
|
ManifestInfo(this).checkSameVersion("Akka", allModules, logWarning = true)
|
||||||
loadExtensions()
|
loadExtensions()
|
||||||
if (LogConfigOnStart) logConfiguration()
|
if (LogConfigOnStart) logConfiguration()
|
||||||
this
|
this
|
||||||
|
|
|
||||||
187
akka-actor/src/main/scala/akka/util/ManifestInfo.scala
Normal file
187
akka-actor/src/main/scala/akka/util/ManifestInfo.scala
Normal file
|
|
@ -0,0 +1,187 @@
|
||||||
|
/**
|
||||||
|
* Copyright (C) 2015–2018 Lightbend Inc. <https://www.lightbend.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
package akka.util
|
||||||
|
|
||||||
|
import scala.collection.immutable
|
||||||
|
import java.io.IOException
|
||||||
|
import java.util.Arrays
|
||||||
|
import java.util.jar.Attributes
|
||||||
|
import java.util.jar.Manifest
|
||||||
|
|
||||||
|
import akka.actor.ActorSystem
|
||||||
|
import akka.actor.ExtendedActorSystem
|
||||||
|
import akka.actor.Extension
|
||||||
|
import akka.actor.ExtensionId
|
||||||
|
import akka.actor.ExtensionIdProvider
|
||||||
|
import akka.annotation.InternalApi
|
||||||
|
import akka.event.Logging
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Akka extension that extracts [[ManifestInfo.Version]] information from META-INF/MANIFEST.MF in jar files
|
||||||
|
* on the classpath of the `ClassLoader` of the `ActorSystem`.
|
||||||
|
*/
|
||||||
|
object ManifestInfo extends ExtensionId[ManifestInfo] with ExtensionIdProvider {
|
||||||
|
private val ImplTitle = "Implementation-Title"
|
||||||
|
private val ImplVersion = "Implementation-Version"
|
||||||
|
private val ImplVendor = "Implementation-Vendor-Id"
|
||||||
|
|
||||||
|
private val BundleName = "Bundle-Name"
|
||||||
|
private val BundleVersion = "Bundle-Version"
|
||||||
|
private val BundleVendor = "Bundle-Vendor"
|
||||||
|
|
||||||
|
private val knownVendors = Set(
|
||||||
|
"com.typesafe.akka",
|
||||||
|
"com.lightbend.akka",
|
||||||
|
"Lightbend Inc.",
|
||||||
|
"Lightbend",
|
||||||
|
"com.lightbend.lagom",
|
||||||
|
"com.typesafe.play"
|
||||||
|
)
|
||||||
|
|
||||||
|
override def get(system: ActorSystem): ManifestInfo = super.get(system)
|
||||||
|
|
||||||
|
override def lookup(): ManifestInfo.type = ManifestInfo
|
||||||
|
|
||||||
|
override def createExtension(system: ExtendedActorSystem): ManifestInfo = new ManifestInfo(system)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Comparable version information
|
||||||
|
*/
|
||||||
|
final class Version(val version: String) extends Comparable[Version] {
|
||||||
|
private val (numbers: Array[Int], rest: String) = {
|
||||||
|
val numbers = new Array[Int](3)
|
||||||
|
val segments: Array[String] = version.split("[.-]")
|
||||||
|
var segmentPos = 0
|
||||||
|
var numbersPos = 0
|
||||||
|
while (numbersPos < 3) {
|
||||||
|
if (segmentPos < segments.length) try {
|
||||||
|
numbers(numbersPos) = segments(segmentPos).toInt
|
||||||
|
segmentPos += 1
|
||||||
|
} catch {
|
||||||
|
case e: NumberFormatException ⇒
|
||||||
|
// This means that we have a trailing part on the version string and
|
||||||
|
// less than 3 numbers, so we assume that this is a "newer" version
|
||||||
|
numbers(numbersPos) = Integer.MAX_VALUE
|
||||||
|
}
|
||||||
|
numbersPos += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
val rest: String =
|
||||||
|
if (segmentPos >= segments.length) ""
|
||||||
|
else String.join("-", Arrays.asList(Arrays.copyOfRange(segments, segmentPos, segments.length): _*))
|
||||||
|
|
||||||
|
(numbers, rest)
|
||||||
|
}
|
||||||
|
|
||||||
|
override def compareTo(other: Version): Int = {
|
||||||
|
var diff = 0
|
||||||
|
diff = numbers(0) - other.numbers(0)
|
||||||
|
if (diff == 0) {
|
||||||
|
diff = numbers(1) - other.numbers(1)
|
||||||
|
if (diff == 0) {
|
||||||
|
diff = numbers(2) - other.numbers(2)
|
||||||
|
if (diff == 0) {
|
||||||
|
diff = rest.compareTo(other.rest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
diff
|
||||||
|
}
|
||||||
|
|
||||||
|
override def equals(o: Any): Boolean = o match {
|
||||||
|
case v: Version ⇒ compareTo(v) == 0
|
||||||
|
case _ ⇒ false
|
||||||
|
}
|
||||||
|
|
||||||
|
override def hashCode(): Int = {
|
||||||
|
var result = HashCode.SEED
|
||||||
|
result = HashCode.hash(result, numbers(0))
|
||||||
|
result = HashCode.hash(result, numbers(1))
|
||||||
|
result = HashCode.hash(result, numbers(2))
|
||||||
|
result = HashCode.hash(result, rest)
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
override def toString: String = version
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility that extracts [[ManifestInfo#Version]] information from META-INF/MANIFEST.MF in jar files on the classpath.
|
||||||
|
* Note that versions can only be found in ordinary jar files, for example not in "fat jars' assembled from
|
||||||
|
* many jar files.
|
||||||
|
*/
|
||||||
|
final class ManifestInfo(val system: ExtendedActorSystem) extends Extension {
|
||||||
|
import ManifestInfo._
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Versions of artifacts from known vendors.
|
||||||
|
*/
|
||||||
|
val versions: Map[String, Version] = {
|
||||||
|
|
||||||
|
var manifests = Map.empty[String, Version]
|
||||||
|
|
||||||
|
try {
|
||||||
|
val resources = system.dynamicAccess.classLoader.getResources("META-INF/MANIFEST.MF")
|
||||||
|
while (resources.hasMoreElements()) {
|
||||||
|
val ios = resources.nextElement().openStream()
|
||||||
|
try {
|
||||||
|
val manifest = new Manifest(ios)
|
||||||
|
val attributes = manifest.getMainAttributes
|
||||||
|
val title = attributes.getValue(new Attributes.Name(ImplTitle)) match {
|
||||||
|
case null ⇒ attributes.getValue(new Attributes.Name(BundleName))
|
||||||
|
case t ⇒ t
|
||||||
|
}
|
||||||
|
val version = attributes.getValue(new Attributes.Name(ImplVersion)) match {
|
||||||
|
case null ⇒ attributes.getValue(new Attributes.Name(BundleVersion))
|
||||||
|
case v ⇒ v
|
||||||
|
}
|
||||||
|
val vendor = attributes.getValue(new Attributes.Name(ImplVendor)) match {
|
||||||
|
case null ⇒ attributes.getValue(new Attributes.Name(BundleVendor))
|
||||||
|
case v ⇒ v
|
||||||
|
}
|
||||||
|
|
||||||
|
if (title != null
|
||||||
|
&& version != null
|
||||||
|
&& vendor != null
|
||||||
|
&& knownVendors(vendor)) {
|
||||||
|
manifests = manifests.updated(title, new Version(version))
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
ios.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
case ioe: IOException ⇒
|
||||||
|
Logging(system, getClass).warning("Could not read manifest information. {}", ioe)
|
||||||
|
}
|
||||||
|
manifests
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify that the version is the same for all given artifacts.
|
||||||
|
*/
|
||||||
|
def checkSameVersion(productName: String, dependencies: immutable.Seq[String], logWarning: Boolean): Boolean = {
|
||||||
|
val filteredVersions = versions.filterKeys(dependencies.toSet)
|
||||||
|
val values = filteredVersions.values.toSet
|
||||||
|
if (values.size > 1) {
|
||||||
|
if (logWarning) {
|
||||||
|
val conflictingVersions = values.mkString(", ")
|
||||||
|
val fullInfo = filteredVersions.map { case (k, v) ⇒ s"$k:$v" }.mkString(", ")
|
||||||
|
val highestVersion = values.max
|
||||||
|
Logging(system, getClass).warning(
|
||||||
|
"Detected possible incompatible versions on the classpath. " +
|
||||||
|
s"Please note that a given $productName version MUST be the same across all modules of $productName " +
|
||||||
|
"that you are using, e.g. if you use [{}] all other modules that are released together MUST be of the " +
|
||||||
|
"same version. Make sure you're using a compatible set of libraries." +
|
||||||
|
"Possibly conflicting versions [{}] in libraries [{}]",
|
||||||
|
highestVersion, conflictingVersions, fullInfo)
|
||||||
|
}
|
||||||
|
false
|
||||||
|
} else
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -20,6 +20,7 @@ akka.AkkaBuild.buildSettings
|
||||||
shellPrompt := { s => Project.extract(s).currentProject.id + " > " }
|
shellPrompt := { s => Project.extract(s).currentProject.id + " > " }
|
||||||
resolverSettings
|
resolverSettings
|
||||||
|
|
||||||
|
// When this is updated the set of modules in ActorSystem.allModules should also be updated
|
||||||
lazy val aggregatedProjects: Seq[ProjectReference] = Seq(
|
lazy val aggregatedProjects: Seq[ProjectReference] = Seq(
|
||||||
actor, actorTests,
|
actor, actorTests,
|
||||||
agent,
|
agent,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue