Skip to content

WIP: scaladays/scala-spree #21

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

Closed
wants to merge 4 commits into from
Closed
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
4 changes: 2 additions & 2 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,16 @@ jobs:
uses: VirtusLab/scala-cli-setup@main

- name: Run test examples
working-directory: examples
run: |
cd examples
for file in *.sc
do
scala-cli "$file"
done

- name: Run examples of tests
working-directory: examples
run: |
cd examples
for file in *.test.scala
do
scala-cli test "$file"
Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@
.vscode/
.bsp/
*.asc
*SECRET
*SECRET
2 changes: 1 addition & 1 deletion Toolkit.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@
//> using lib "com.softwaremill.sttp.client4::core::4.0.0-M1"
//> using lib "com.softwaremill.sttp.client4::upickle::4.0.0-M1"
//> using lib "com.lihaoyi::upickle::3.1.0"
//> using lib "com.lihaoyi::os-lib::0.9.1"
//> using lib "com.lihaoyi::os-lib::0.9.1"
4 changes: 4 additions & 0 deletions scripts/Config.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
object Config:
val organization = "org.scala-lang"
val name = "toolkit"
val crossVersions = List("2.13", "3")
69 changes: 69 additions & 0 deletions scripts/Dependencies.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import scala.util.Try
import scala.util.matching.Regex
import upickle.default.*
import coursier.graph.DependencyTree
import Dependencies.*

object Dependencies:
case class Version(major: Int, minor: Int, patch: Int, suffix: Option[String]) extends Ordered[Version] derives ReadWriter:
def compare(that: Version): Int =
Ordering[(Int, Int, Int, Option[String])].compare((this.major, this.minor, this.patch, this.suffix), (that.major, that.minor, that.patch, that.suffix))
override def toString: String = s"$major.$minor.$patch"
def getDiff(that: Version): VersionDiff = Version.compareVersions(this, that)

object Version:
/**
* Compares two versions and returns the type of update that is required to go from oldVersion to newVersion.
* Takes into account that versions may be rolled back. For example:
* - 1.0.1 -> 1.0.0 is a patch update
* - 1.1.0 -> 1.0.0 is a major update, as it may break backwards compatibility
* pre-release suffixes are also taken into account. The required version update is as great as the version bump
* for which the pre-release is published. For example
* - 1.0.0-M1 -> 1.0.0-M2 is a major update. The same as 1.0.0-M2 -> 1.0.0
* - 1.1.2-M2 -> 1.1.2-M3 is a patch update
*/
def compareVersions(oldVersion: Version, newVersion: Version): VersionDiff =
if oldVersion.major != newVersion.major then MajorUpdate
else if oldVersion.minor != newVersion.minor then
if oldVersion.minor < newVersion.minor then MinorUpdate
else MajorUpdate
else if oldVersion.patch != newVersion.patch then PatchUpdate
else if oldVersion.suffix != newVersion.suffix then
oldVersion match
case Version(_, 0, 0, _) => MajorUpdate
case Version(_, _, 0, _) => MinorUpdate
case _ => PatchUpdate
else throw new IllegalArgumentException("Versions are the same")


sealed abstract class VersionDiff(val order: Int) extends Ordered[VersionDiff]:
def compare(that: VersionDiff): Int = order compare that.order
case object PatchUpdate extends VersionDiff(0)
case object MinorUpdate extends VersionDiff(1)
case object MajorUpdate extends VersionDiff(2)

object VersionString:
def unapply(s: String): Option[Version] =
val regex = """(\d+)\.(\d+)\.(\d+)(-[a-zA-Z\d\.]+)?""".r
s match
case regex(major, minor, patch, suffix) => Some(Version(major.toInt, minor.toInt, patch.toInt, Option(suffix).map(_.drop(1))))
case _ => None

case class Dep(id: String, version: Version, deps: List[Dep]) derives ReadWriter:
override def toString: String = s"$id:$version"


def makeDepTree(tree: DependencyTree): Dep =
val dep = tree.dependency
val depId = s"${dep.module.organization.value}:${dep.module.name.value}"
val versionParsed = VersionString.unapply(dep.version)
versionParsed match
case Some(version) => Dep(dep.module.name.value, version, tree.children.map(makeDepTree).toList)
case None => throw new Exception(s"Could not parse version from $depId:${dep.version}")


object Utility:
def requireCmd(cmd: String): Unit =
if Try(os.proc("which", cmd).call()).isFailure then
println(s"Please install $cmd")
sys.exit(1)
112 changes: 112 additions & 0 deletions scripts/deptree.sc
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
#!/usr/bin/env -S scala-cli shebang
//> using toolkit 0.1.7
//> using scala 3.3
//> using dep io.get-coursier:coursier_2.13:2.1.4
//> using file Dependencies.scala
//> using file Config.scala

import scala.util.Try
import coursier.*
import coursier.given
import coursier.graph.DependencyTree
import scala.util.matching.Regex
import upickle.default.*

import Dependencies.*

Utility.requireCmd("scala-cli")

case class DiffSummary(updateType: VersionDiff, diffs: List[Diff], illegalDiffs: List[IllegalDiff])
sealed trait Diff
case class Added(newDep: Dep, under: Option[Dep]) extends Diff
case class Removed(oldDep: Dep, under: Option[Dep]) extends Diff
case class DepUpdated(oldDep: Dep, newDep: Dep, under: Option[Dep]) extends Diff

case class IllegalDiff(diff: Diff, leastOrderLegalUpdate: VersionDiff):
override def toString: String = s"$diff (required at least: $leastOrderLegalUpdate)"

case class Params(file: String, version: Version, overwrite: Boolean)

val params = args match
case Array(file, VersionString(version)) => Params(file, version, false)
case Array(file, VersionString(version), "--overwrite") => Params(file, version, true)
case _ => throw new Exception("Usage: ./scripts/deptree.sc <file> <version> [overwrite]")

println("Publishing locally to validate the dependency tree...")
os.proc("scala-cli", "--power", "publish", "local", "--cross", "--organization", Config.organization, "--version", params.version.toString, params.file).call()

Config.crossVersions.foreach(checkTree)

def checkTree(crossVersions: String) =
val resolution = Resolve()
.addDependencies(Dependency(Module(Organization(Config.organization), ModuleName(Config.name + "_" + crossVersions)), params.version.toString))
.run()

val head = DependencyTree(resolution).head
val depTree = makeDepTree(head)

val previousSnapshot = read[Dep](os.read(os.pwd / "scripts" / "expected" / s"deptree_${crossVersions}.json"))
val currentSnapshot = Dep(depTree.id, params.version, depTree.deps)

val summary = SnapshotDiffValidator.summarize(previousSnapshot, currentSnapshot)
if summary.illegalDiffs.nonEmpty then
println("Found diffs illegal to introduce on this update type: " + summary.updateType)
println(s"Illegal diffs: \n ${summary.illegalDiffs.mkString(" - ", "\n - ", "\n")}")
if params.overwrite then
println("Could not overwrite the expected snapshot due to illegal diffs")
sys.exit(1)

val diffsFound = summary.diffs.nonEmpty

if diffsFound then
println("Found diffs: \n" + summary.diffs.mkString(" - ", "\n - ", "\n"))
else
println("No diffs found")

if !params.overwrite && diffsFound then
println("Exiting with failure status (1). Please run with --overwrite to overwrite the expected snapshot")
sys.exit(1)
else if diffsFound then
println("Overwrite symbols? (y/N)")
val input = scala.io.StdIn.readLine()
if input == "y" || input == "Y" then
os.write.over(os.pwd / "scripts" / "expected" / s"deptree_${crossVersions}.json", write(currentSnapshot))
println("Overwritten")
else
println("Changes rejected")
sys.exit(1)


object SnapshotDiffValidator:

def summarize(previous: Dep, current: Dep): DiffSummary =
val versionDiff = Version.compareVersions(previous.version, current.version)

def traverseDep(oldDep: Option[Dep], newDep: Option[Dep], enteredFrom: Option[Dep] = None): List[Diff] =
def traverseDepTupled(enteredFrom: Dep)(pair: (Option[Dep], Option[Dep])) = traverseDep(pair._1, pair._2, Some(enteredFrom))
(oldDep, newDep) match
case (Some(oldDep), Some(newDep)) =>
if oldDep.version == newDep.version then Nil
else
val diff = DepUpdated(oldDep, newDep, enteredFrom)
val pairs = oldDep.deps.map(dep => (Some(dep), newDep.deps.find(_.id == dep.id)))
val newDeps = newDep.deps.filterNot(dep => oldDep.deps.exists(_.id == dep.id)).map(dep => (None, Some(dep)))
val diffs = pairs.flatMap(traverseDepTupled(newDep)) ++ newDeps.flatMap(traverseDepTupled(newDep))
diff :: diffs
case (Some(oldDep), None) => Removed(oldDep, enteredFrom) :: oldDep.deps.flatMap(dep => traverseDep(Some(dep), None, Some(oldDep))).toList
case (None, Some(newDep)) => Added(newDep, enteredFrom) :: newDep.deps.flatMap(dep => traverseDep(None, Some(dep), Some(newDep))).toList
case _ => Nil

val diffs: List[Diff] = traverseDep(Some(previous), Some(current))
val illegalDiffs: List[IllegalDiff] = diffs
.map(diff => IllegalDiff(diff, getLeastOrderLegalUpdate(versionDiff, diff)))
.filter(_.leastOrderLegalUpdate > versionDiff)
DiffSummary(versionDiff, diffs, illegalDiffs)


def getLeastOrderLegalUpdate(versionDiff: VersionDiff, diff: Diff): VersionDiff =
diff match
case _: Removed => MajorUpdate
case _: Added => MinorUpdate
case DepUpdated(oldDep, newDep, _) =>
Version.compareVersions(oldDep.version, newDep.version)
Loading