Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Keep parsed string in VersionConstraint, parse it lazily #72

Merged
merged 6 commits into from
Jan 20, 2025
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
15 changes: 14 additions & 1 deletion build.sc
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,29 @@ trait VersionsMima extends Mima {
val current = os.proc("git", "describe", "--tags", "--match", "v*")
.call(cwd = T.workspace)
.out.trim()
val cutOff = coursier.core.Version("0.3.3")
os.proc("git", "tag", "-l")
.call(cwd = T.workspace)
.out.lines()
.filter(_ != current)
.filter(_.startsWith("v"))
.map(_.stripPrefix("v"))
.map(coursier.core.Version(_))
.filter(_ > cutOff)
.sorted
.map(_.repr)
}
// required if mimaPreviousVersions is empty
def mimaPreviousArtifacts = T {
val versions = mimaPreviousVersions().distinct
mill.api.Result.Success(
Agg.from(
versions.map(version =>
ivy"${pomSettings().organization}:${artifactId()}:$version"
)
)
)
}
}

trait VersionsPublishModule extends PublishModule with VersionsMima {
Expand Down Expand Up @@ -106,7 +119,7 @@ trait Versions extends Cross.Module[String] with ScalaModule with VersionsPublis
}

def compileIvyDeps = Agg(
ivy"io.github.alexarchambault::data-class:0.2.6"
ivy"io.github.alexarchambault::data-class:0.2.7"
)

def mimaBinaryIssueFilters = super.mimaBinaryIssueFilters() ++ Seq(
Expand Down
88 changes: 56 additions & 32 deletions versions/shared/src/coursier/version/ConstraintReconciliation.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,33 +6,31 @@ package coursier.version
* To be used mainly during resolution.
*/
sealed abstract class ConstraintReconciliation extends Product with Serializable {
def reconcile(versions: Seq[String]): Option[String]
def reconcile(versions: Seq[VersionConstraint]): Option[VersionConstraint]
}

object ConstraintReconciliation {

private final val LatestIntegration = "latest.integration"
private final val LatestRelease = "latest.release"
private final val LatestStable = "latest.stable"
private final val LatestIntegration = VersionConstraint("latest.integration")
private final val LatestRelease = VersionConstraint("latest.release")
private final val LatestStable = VersionConstraint("latest.stable")

private def splitStandard(versions: Seq[String]): (Seq[String], Seq[String]) =
private def splitStandard(versions: Seq[VersionConstraint]): (Seq[VersionConstraint], Seq[VersionConstraint]) =
versions.distinct.partition {
case LatestIntegration => false
case LatestRelease => false
case LatestStable => false
case _ => true
}

private def retainLatestOpt(latests: Seq[String]): Option[String] =
private def retainLatestOpt(latests: Seq[VersionConstraint]): Option[VersionConstraint] =
if (latests.isEmpty) None
else if (latests.lengthCompare(1) == 0) latests.headOption
else {
val set = latests.toSet
val retained =
if (set(LatestIntegration))
LatestIntegration
else if (set(LatestRelease))
LatestRelease
if (set(LatestIntegration)) LatestIntegration
else if (set(LatestRelease)) LatestRelease
else {
// at least two distinct latest.* means we shouldn't even reach this else block anyway
assert(set(LatestStable))
Expand All @@ -48,7 +46,7 @@ object ConstraintReconciliation {
* Fails when passed version intervals that don't overlap.
*/
case object Default extends ConstraintReconciliation {
def reconcile(versions: Seq[String]): Option[String] =
def reconcile(versions: Seq[VersionConstraint]): Option[VersionConstraint] =
if (versions.isEmpty)
None
else if (versions.lengthCompare(1) == 0)
Expand All @@ -58,28 +56,23 @@ object ConstraintReconciliation {
val retainedStandard =
if (standard.isEmpty) None
else if (standard.lengthCompare(1) == 0) standard.headOption
else {
val parsedConstraints = standard.map(VersionParse.versionConstraint)
VersionConstraint.merge(parsedConstraints: _*)
.flatMap(_.repr)
}
else
VersionConstraint.merge(standard: _*)
.map(_.uniquePreferred.removeUnusedPreferred)
val retainedLatestOpt = retainLatestOpt(latests)

if (standard.isEmpty)
retainedLatestOpt
else if (latests.isEmpty)
retainedStandard
if (standard.isEmpty) retainedLatestOpt
else if (latests.isEmpty) retainedStandard
else {
val parsedIntervals = standard.map(VersionParse.versionConstraint)
val parsedIntervals = standard
.filter(_.preferred.isEmpty) // only keep intervals
.filter(_.interval != VersionInterval.zero) // not interval matching any version

if (parsedIntervals.isEmpty)
retainedLatestOpt
else
VersionConstraint.merge(parsedIntervals: _*)
.flatMap(_.repr)
.map(itv => (itv +: retainedLatestOpt.toSeq).mkString("&"))
.map(_.uniquePreferred.removeUnusedPreferred) // FIXME Add retainedLatestOpt too
}
}
}
Expand All @@ -90,7 +83,7 @@ object ConstraintReconciliation {
* When passed version intervals that don't overlap, the lowest intervals are discarded until the remaining intervals do overlap.
*/
case object Relaxed extends ConstraintReconciliation {
def reconcile(versions: Seq[String]): Option[String] =
def reconcile(versions: Seq[VersionConstraint]): Option[VersionConstraint] =
if (versions.isEmpty)
None
else if (versions.lengthCompare(1) == 0)
Expand All @@ -101,16 +94,15 @@ object ConstraintReconciliation {
if (standard.isEmpty) None
else if (standard.lengthCompare(1) == 0) standard.headOption
else {
val parsedConstraints = standard.map(VersionParse.versionConstraint)
VersionConstraint.merge(parsedConstraints: _*)
.getOrElse(VersionConstraint.relaxedMerge(parsedConstraints: _*))
.repr
val repr = VersionConstraint.merge(standard: _*)
.getOrElse(VersionConstraint.relaxedMerge(standard: _*))
.uniquePreferred
.removeUnusedPreferred
Some(repr)
}
val retainedLatestOpt = retainLatestOpt(latests)
if (latests.isEmpty)
retainedStandard
else
retainedLatestOpt
if (latests.isEmpty) retainedStandard
else retainedLatestOpt
}
}

Expand All @@ -129,4 +121,36 @@ object ConstraintReconciliation {
case _ => Default
}

/** Strict version reconciliation.
*
* This particular instance behaves the same as [[Default]] when used by
* [[coursier.core.Resolution]]. Actual strict conflict manager is handled by
* `coursier.params.rule.Strict`, which is set up by `coursier.Resolve` when a strict
* reconciliation is added to it.
*/
case object Strict extends ConstraintReconciliation {
def reconcile(versions: Seq[VersionConstraint]): Option[VersionConstraint] =
Default.reconcile(versions)
}

/** Semantic versioning version reconciliation.
*
* This particular instance behaves the same as [[Default]] when used by
* [[coursier.core.Resolution]]. Actual semantic versioning checks are handled by
* `coursier.params.rule.Strict` with field `semVer = true`, which is set up by
* `coursier.Resolve` when a SemVer reconciliation is added to it.
*/
case object SemVer extends ConstraintReconciliation {
def reconcile(versions: Seq[VersionConstraint]): Option[VersionConstraint] =
Default.reconcile(versions)
}

def apply(input: String): Option[ConstraintReconciliation] =
input match {
case "default" => Some(Default)
case "relaxed" => Some(Relaxed)
case "strict" => Some(Strict)
case "semver" => Some(SemVer)
case _ => None
}
}
6 changes: 5 additions & 1 deletion versions/shared/src/coursier/version/ModuleMatcher.scala
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,11 @@ import scala.util.matching.Regex
lazy val orgPattern = blobToPattern(organizationMatcher)
lazy val namePattern = blobToPattern(nameMatcher)
lazy val attributesPattern = attributeMatchers
.mapValues(blobToPattern(_))
.iterator
.map {
case (k, v) =>
(k, blobToPattern(v))
}
.toMap

def matches(organization: String, name: String): Boolean =
Expand Down
15 changes: 13 additions & 2 deletions versions/shared/src/coursier/version/Version.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,14 @@ import scala.annotation.tailrec
* Same kind of ordering as aether-util/src/main/java/org/eclipse/aether/util/version/GenericVersion.java
*/
@data class Version(repr: String) extends Ordered[Version] {
lazy val items: Vector[Version.Item] = Version.items(repr)
def asString: String = repr
private var items0: Vector[Version.Item] = null
def items: Vector[Version.Item] = {
// no need to guard against concurrent computations, this is not too expensive to compute
if (items0 == null)
items0 = Version.items(repr)
items0
}
def compare(other: Version) = Version.listCompare(items, other.items)
def isEmpty = items.forall(_.isEmpty)

Expand All @@ -21,11 +28,15 @@ import scala.annotation.tailrec
repr
.split(Array('.', '-'))
.forall(_.lengthCompare(5) <= 0)

override lazy val hashCode = repr.hashCode()
}

object Version {

private[version] val zero = Version("0")
private val zero0 = Version("0")

def zero: Version = Version("0")

sealed abstract class Item extends Ordered[Item] {
def compare(other: Item): Int =
Expand Down
Loading
Loading