-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 555af0a
Showing
15 changed files
with
984 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
name: CI | ||
on: | ||
push: | ||
branches: | ||
- master | ||
tags: | ||
- "v*" | ||
pull_request: | ||
|
||
jobs: | ||
test: | ||
runs-on: ubuntu-latest | ||
|
||
steps: | ||
|
||
- uses: actions/checkout@v1 | ||
|
||
- name: coursier cache | ||
uses: actions/cache@v1 | ||
if: runner.OS == 'Linux' | ||
with: | ||
path: ~/.cache/coursier | ||
key: ${{ runner.OS }}-coursier-cache-${{ hashFiles('**/*.sbt') }} # -${{ hashFiles('project/**.scala') }} (fails for now) | ||
restore-keys: | | ||
${{ runner.OS }}-coursier-cache-${{ hashFiles('**/*.sbt') }}- | ||
${{ runner.OS }}-coursier-cache- | ||
- uses: olafurpg/setup-scala@v7 | ||
with: | ||
java-version: adopt@1.8.0-232 | ||
|
||
- name: Compile | ||
run: csbt test compatibilityCheck | ||
|
||
publish: | ||
needs: test | ||
if: github.event_name == 'push' | ||
runs-on: ubuntu-latest | ||
|
||
steps: | ||
|
||
- uses: actions/checkout@v1 | ||
|
||
- name: coursier cache (Linux) | ||
uses: actions/cache@v1 | ||
if: runner.OS == 'Linux' | ||
with: | ||
path: ~/.cache/coursier | ||
key: ${{ runner.OS }}-coursier-cache-${{ hashFiles('**/*.sbt') }} # -${{ hashFiles('project/**.scala') }} (fails for now) | ||
restore-keys: | | ||
${{ runner.OS }}-coursier-cache-${{ hashFiles('**/*.sbt') }}- | ||
${{ runner.OS }}-coursier-cache- | ||
- uses: olafurpg/setup-scala@v7 | ||
|
||
- uses: olafurpg/setup-gpg@v2 | ||
|
||
- name: Release | ||
run: csbt ci-release | ||
env: | ||
PGP_PASSPHRASE: ${{ secrets.PGP_PASSPHRASE }} | ||
PGP_SECRET: ${{ secrets.PGP_SECRET }} | ||
SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} | ||
SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
target/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
|
||
inThisBuild(List( | ||
organization := "io.get-coursier", | ||
homepage := Some(url("https://github.com/coursier/versions")), | ||
licenses := List("Apache-2.0" -> url("http://www.apache.org/licenses/LICENSE-2.0")), | ||
developers := List( | ||
Developer( | ||
"alexarchambault", | ||
"Alexandre Archambault", | ||
"", | ||
url("https://github.com/alexarchambault") | ||
) | ||
) | ||
)) | ||
|
||
lazy val shared = Def.settings( | ||
scalaVersion := "2.13.2", | ||
crossScalaVersions := Seq("2.13.2", "2.12.11"), | ||
libraryDependencies ++= { | ||
if (isAtLeastScala213.value) Nil | ||
else Seq(compilerPlugin("org.scalamacros" % "paradise" % "2.1.1" cross CrossVersion.full)) | ||
}, | ||
scalacOptions ++= { | ||
if (isAtLeastScala213.value) Seq("-Ymacro-annotations") | ||
else Nil | ||
}, | ||
scalacOptions += "-deprecation" | ||
) | ||
|
||
lazy val isAtLeastScala213 = Def.setting { | ||
import Ordering.Implicits._ | ||
CrossVersion.partialVersion(scalaVersion.value).exists(_ >= (2, 13)) | ||
} | ||
|
||
|
||
lazy val versions = crossProject(JVMPlatform, JSPlatform) | ||
.settings( | ||
shared, | ||
libraryDependencies += "io.github.alexarchambault" %% "data-class" % "0.2.3" % Provided | ||
) | ||
|
||
lazy val versionsJVM = versions.jvm | ||
lazy val versionsJS = versions.js | ||
|
||
crossScalaVersions := Nil | ||
skip.in(publish) := true | ||
disablePlugins(MimaPlugin) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
sbt.version=1.3.10 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
addSbtPlugin("com.geirsson" % "sbt-ci-release" % "1.5.3") | ||
addSbtPlugin(("io.github.alexarchambault.sbt" % "sbt-compatibility" % "0.0.4").exclude("com.typesafe", "sbt-mima-plugin")) | ||
addSbtPlugin("com.github.alexarchambault.tmp" % "sbt-mima-plugin" % "0.7.1-SNAPSHOT") | ||
addSbtPlugin("org.xerial.sbt" % "sbt-pack" % "0.12") | ||
addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.0.1") | ||
addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.0.0") | ||
|
||
resolvers += Resolver.sonatypeRepo("snapshots") |
14 changes: 14 additions & 0 deletions
14
versions/js/src/main/scala/coursier/version/internal/Compatibility.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
package coursier.version.internal | ||
|
||
object Compatibility { | ||
|
||
private def between(c: Char, lower: Char, upper: Char) = lower <= c && c <= upper | ||
|
||
implicit class RichChar(private val c: Char) extends AnyVal { | ||
def letter: Boolean = between(c, 'a', 'z') || between(c, 'A', 'Z') | ||
def letterOrDigit: Boolean = between(c, '0', '9') || letter | ||
} | ||
|
||
def regexLookbehind: String = ":" | ||
|
||
} |
12 changes: 12 additions & 0 deletions
12
versions/jvm/src/main/scala/coursier/version/internal/Compatibility.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package coursier.version.internal | ||
|
||
object Compatibility { | ||
|
||
implicit class RichChar(private val c: Char) extends AnyVal { | ||
def letter = c.isLetter | ||
def letterOrDigit = c.isLetterOrDigit | ||
} | ||
|
||
def regexLookbehind: String = "<=" | ||
|
||
} |
132 changes: 132 additions & 0 deletions
132
versions/shared/src/main/scala/coursier/version/ConstraintReconciliation.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
package coursier.version | ||
|
||
/** | ||
* Reconciles a set of version constraints (version intervals, specific versions, …). | ||
* | ||
* To be used mainly during resolution. | ||
*/ | ||
sealed abstract class ConstraintReconciliation extends Product with Serializable { | ||
def reconcile(versions: Seq[String]): Option[String] | ||
} | ||
|
||
object ConstraintReconciliation { | ||
|
||
private final val LatestIntegration = "latest.integration" | ||
private final val LatestRelease = "latest.release" | ||
private final val LatestStable = "latest.stable" | ||
|
||
private def splitStandard(versions: Seq[String]): (Seq[String], Seq[String]) = | ||
versions.distinct.partition { | ||
case LatestIntegration => false | ||
case LatestRelease => false | ||
case LatestStable => false | ||
case _ => true | ||
} | ||
|
||
private def retainLatestOpt(latests: Seq[String]): Option[String] = | ||
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 | ||
else { | ||
// at least two distinct latest.* means we shouldn't even reach this else block anyway | ||
assert(set(LatestStable)) | ||
LatestStable | ||
} | ||
Some(retained) | ||
} | ||
|
||
|
||
/** | ||
* Keeps the intersection of intervals, retains the latest version, etc. as described in the coursier documentation | ||
* | ||
* Fails when passed version intervals that don't overlap. | ||
*/ | ||
case object Default extends ConstraintReconciliation { | ||
def reconcile(versions: Seq[String]): Option[String] = | ||
if (versions.isEmpty) | ||
None | ||
else if (versions.lengthCompare(1) == 0) | ||
Some(versions.head) | ||
else { | ||
val (standard, latests) = splitStandard(versions) | ||
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) | ||
} | ||
val retainedLatestOpt = retainLatestOpt(latests) | ||
|
||
if (standard.isEmpty) | ||
retainedLatestOpt | ||
else if (latests.isEmpty) | ||
retainedStandard | ||
else { | ||
val parsedIntervals = standard.map(VersionParse.versionConstraint) | ||
.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("&")) | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* Always succeeds | ||
* | ||
* 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] = | ||
if (versions.isEmpty) | ||
None | ||
else if (versions.lengthCompare(1) == 0) | ||
Some(versions.head) | ||
else { | ||
val (standard, latests) = splitStandard(versions) | ||
val retainedStandard = | ||
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 retainedLatestOpt = retainLatestOpt(latests) | ||
if (latests.isEmpty) | ||
retainedStandard | ||
else | ||
retainedLatestOpt | ||
} | ||
} | ||
|
||
/** | ||
* The [[ConstraintReconciliation]] to be used for this [[VersionCompatibility]] | ||
* | ||
* The `Always` version compatibility corresponds to `Relaxed` constraint reconciliation (never fail to reconcile | ||
* versions during resolution). | ||
* | ||
* The other version compatibilities use `Default` as constraint reconciliation (may fail to reconcile versions during | ||
* resolution). | ||
*/ | ||
def apply(compatibility: VersionCompatibility): ConstraintReconciliation = | ||
compatibility match { | ||
case VersionCompatibility.Always => Relaxed | ||
case _ => Default | ||
} | ||
|
||
} |
62 changes: 62 additions & 0 deletions
62
versions/shared/src/main/scala/coursier/version/ModuleMatcher.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
package coursier.version | ||
|
||
import java.util.regex.Pattern | ||
|
||
import dataclass.data | ||
|
||
import scala.annotation.tailrec | ||
import scala.util.matching.Regex | ||
|
||
// Adapted from https://github.com/coursier/coursier/blob/876a6604d0cd0c3783ed729f5399549f52a3a385/modules/coursier/shared/src/main/scala/coursier/util/ModuleMatcher.scala | ||
|
||
@data class ModuleMatcher( | ||
organizationMatcher: String, | ||
nameMatcher: String, | ||
attributeMatchers: Map[String, String] = Map.empty | ||
) { | ||
|
||
import ModuleMatcher.blobToPattern | ||
|
||
lazy val orgPattern = blobToPattern(organizationMatcher) | ||
lazy val namePattern = blobToPattern(nameMatcher) | ||
lazy val attributesPattern = attributeMatchers | ||
.mapValues(blobToPattern(_)) | ||
.toMap | ||
|
||
def matches(organization: String, name: String): Boolean = | ||
matches(organization, name, Map.empty) | ||
|
||
def matches(organization: String, name: String, attributes: Map[String, String]): Boolean = | ||
orgPattern.pattern.matcher(organization).matches() && | ||
namePattern.pattern.matcher(name).matches() && | ||
attributes.keySet == attributesPattern.keySet && | ||
attributesPattern.forall { | ||
case (k, p) => | ||
attributes.get(k).exists(p.pattern.matcher(_).matches()) | ||
} | ||
|
||
} | ||
|
||
object ModuleMatcher { | ||
|
||
def all: ModuleMatcher = | ||
ModuleMatcher("*", "*") | ||
|
||
@tailrec | ||
private def blobToPattern(s: String, b: StringBuilder = new StringBuilder): Regex = | ||
if (s.isEmpty) | ||
b.result().r | ||
else { | ||
val idx = s.indexOf('*') | ||
if (idx < 0) { | ||
b ++= Pattern.quote(s) | ||
b.result().r | ||
} else { | ||
if (idx > 0) | ||
b ++= Pattern.quote(s.substring(0, idx)) | ||
b ++= ".*" | ||
blobToPattern(s.substring(idx + 1), b) | ||
} | ||
} | ||
|
||
} |
39 changes: 39 additions & 0 deletions
39
versions/shared/src/main/scala/coursier/version/ModuleMatchers.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
package coursier.version | ||
|
||
import dataclass._ | ||
|
||
// Adapted from https://github.com/coursier/coursier/blob/f0b10fb1744e5bdf94bf17857dfb3cb19fda2e5b/modules/coursier/shared/src/main/scala/coursier/util/ModuleMatchers.scala | ||
|
||
@data class ModuleMatchers( | ||
exclude: Set[ModuleMatcher], | ||
include: Set[ModuleMatcher] = Set(), | ||
@since | ||
includeByDefault: Boolean = true | ||
) { | ||
|
||
// If modules are included by default: | ||
// Those matched by anything in exclude are excluded, but for those also matched by something in include. | ||
// If modules are excluded by default: | ||
// Those matched by anything in include are included, but for those also matched by something in exclude. | ||
|
||
def matches(organization: String, name: String): Boolean = | ||
matches(organization, name, Map.empty) | ||
|
||
def matches(organization: String, name: String, attributes: Map[String, String]): Boolean = | ||
if (includeByDefault) | ||
!exclude.exists(_.matches(organization, name, attributes)) || | ||
include.exists(_.matches(organization, name, attributes)) | ||
else | ||
include.exists(_.matches(organization, name, attributes)) && | ||
!exclude.exists(_.matches(organization, name, attributes)) | ||
|
||
} | ||
|
||
object ModuleMatchers { | ||
def all: ModuleMatchers = | ||
ModuleMatchers(Set.empty, Set.empty) | ||
def only(organization: String, name: String): ModuleMatchers = | ||
only(organization, name, Map.empty) | ||
def only(organization: String, name: String, attributes: Map[String, String]): ModuleMatchers = | ||
ModuleMatchers(Set.empty, Set(ModuleMatcher(organization, name, attributes)), includeByDefault = false) | ||
} |
Oops, something went wrong.