diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 790947a..d25a18b 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -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" diff --git a/.gitignore b/.gitignore index b4402ba..582c198 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,4 @@ .vscode/ .bsp/ *.asc -*SECRET \ No newline at end of file +*SECRET diff --git a/Toolkit.scala b/Toolkit.scala index 967f4cc..8f10f18 100644 --- a/Toolkit.scala +++ b/Toolkit.scala @@ -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" \ No newline at end of file diff --git a/scripts/Config.scala b/scripts/Config.scala new file mode 100644 index 0000000..87d758a --- /dev/null +++ b/scripts/Config.scala @@ -0,0 +1,4 @@ +object Config: + val organization = "org.scala-lang" + val name = "toolkit" + val crossVersions = List("2.13", "3") \ No newline at end of file diff --git a/scripts/Dependencies.scala b/scripts/Dependencies.scala new file mode 100644 index 0000000..ae60d68 --- /dev/null +++ b/scripts/Dependencies.scala @@ -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) diff --git a/scripts/deptree.sc b/scripts/deptree.sc new file mode 100755 index 0000000..c075928 --- /dev/null +++ b/scripts/deptree.sc @@ -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 [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) \ No newline at end of file diff --git a/scripts/expected/deptree_2.13.json b/scripts/expected/deptree_2.13.json new file mode 100644 index 0000000..7364bbc --- /dev/null +++ b/scripts/expected/deptree_2.13.json @@ -0,0 +1,1161 @@ +{ + "id": "toolkit_2.13", + "version": { + "major": 0, + "minor": 1, + "patch": 7, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "os-lib_2.13", + "version": { + "major": 0, + "minor": 9, + "patch": 1, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "geny_2.13", + "version": { + "major": 1, + "minor": 0, + "patch": 0, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "scala-library", + "version": { + "major": 2, + "minor": 13, + "patch": 8, + "suffix": [ + + ] + }, + "deps": [ + + ] + } + ] + }, + { + "id": "scala-library", + "version": { + "major": 2, + "minor": 13, + "patch": 10, + "suffix": [ + + ] + }, + "deps": [ + + ] + } + ] + }, + { + "id": "upickle_2.13", + "version": { + "major": 3, + "minor": 1, + "patch": 0, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "ujson_2.13", + "version": { + "major": 3, + "minor": 1, + "patch": 0, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "upickle-core_2.13", + "version": { + "major": 3, + "minor": 1, + "patch": 0, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "geny_2.13", + "version": { + "major": 1, + "minor": 0, + "patch": 0, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "scala-library", + "version": { + "major": 2, + "minor": 13, + "patch": 8, + "suffix": [ + + ] + }, + "deps": [ + + ] + } + ] + }, + { + "id": "scala-library", + "version": { + "major": 2, + "minor": 13, + "patch": 10, + "suffix": [ + + ] + }, + "deps": [ + + ] + } + ] + }, + { + "id": "scala-library", + "version": { + "major": 2, + "minor": 13, + "patch": 10, + "suffix": [ + + ] + }, + "deps": [ + + ] + } + ] + }, + { + "id": "upack_2.13", + "version": { + "major": 3, + "minor": 1, + "patch": 0, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "upickle-core_2.13", + "version": { + "major": 3, + "minor": 1, + "patch": 0, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "geny_2.13", + "version": { + "major": 1, + "minor": 0, + "patch": 0, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "scala-library", + "version": { + "major": 2, + "minor": 13, + "patch": 8, + "suffix": [ + + ] + }, + "deps": [ + + ] + } + ] + }, + { + "id": "scala-library", + "version": { + "major": 2, + "minor": 13, + "patch": 10, + "suffix": [ + + ] + }, + "deps": [ + + ] + } + ] + }, + { + "id": "scala-library", + "version": { + "major": 2, + "minor": 13, + "patch": 10, + "suffix": [ + + ] + }, + "deps": [ + + ] + } + ] + }, + { + "id": "upickle-implicits_2.13", + "version": { + "major": 3, + "minor": 1, + "patch": 0, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "upickle-core_2.13", + "version": { + "major": 3, + "minor": 1, + "patch": 0, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "geny_2.13", + "version": { + "major": 1, + "minor": 0, + "patch": 0, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "scala-library", + "version": { + "major": 2, + "minor": 13, + "patch": 8, + "suffix": [ + + ] + }, + "deps": [ + + ] + } + ] + }, + { + "id": "scala-library", + "version": { + "major": 2, + "minor": 13, + "patch": 10, + "suffix": [ + + ] + }, + "deps": [ + + ] + } + ] + }, + { + "id": "scala-library", + "version": { + "major": 2, + "minor": 13, + "patch": 10, + "suffix": [ + + ] + }, + "deps": [ + + ] + } + ] + }, + { + "id": "scala-library", + "version": { + "major": 2, + "minor": 13, + "patch": 10, + "suffix": [ + + ] + }, + "deps": [ + + ] + } + ] + }, + { + "id": "core_2.13", + "version": { + "major": 4, + "minor": 0, + "patch": 0, + "suffix": [ + "M1" + ] + }, + "deps": [ + { + "id": "core_2.13", + "version": { + "major": 1, + "minor": 5, + "patch": 5, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "scala-library", + "version": { + "major": 2, + "minor": 13, + "patch": 10, + "suffix": [ + + ] + }, + "deps": [ + + ] + } + ] + }, + { + "id": "core_2.13", + "version": { + "major": 1, + "minor": 3, + "patch": 13, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "scala-library", + "version": { + "major": 2, + "minor": 13, + "patch": 10, + "suffix": [ + + ] + }, + "deps": [ + + ] + } + ] + }, + { + "id": "ws_2.13", + "version": { + "major": 1, + "minor": 3, + "patch": 13, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "core_2.13", + "version": { + "major": 1, + "minor": 5, + "patch": 5, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "scala-library", + "version": { + "major": 2, + "minor": 13, + "patch": 10, + "suffix": [ + + ] + }, + "deps": [ + + ] + } + ] + }, + { + "id": "core_2.13", + "version": { + "major": 1, + "minor": 3, + "patch": 13, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "scala-library", + "version": { + "major": 2, + "minor": 13, + "patch": 10, + "suffix": [ + + ] + }, + "deps": [ + + ] + } + ] + }, + { + "id": "scala-library", + "version": { + "major": 2, + "minor": 13, + "patch": 10, + "suffix": [ + + ] + }, + "deps": [ + + ] + } + ] + }, + { + "id": "scala-library", + "version": { + "major": 2, + "minor": 13, + "patch": 10, + "suffix": [ + + ] + }, + "deps": [ + + ] + } + ] + }, + { + "id": "upickle_2.13", + "version": { + "major": 4, + "minor": 0, + "patch": 0, + "suffix": [ + "M1" + ] + }, + "deps": [ + { + "id": "upickle_2.13", + "version": { + "major": 3, + "minor": 0, + "patch": 0, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "ujson_2.13", + "version": { + "major": 3, + "minor": 1, + "patch": 0, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "upickle-core_2.13", + "version": { + "major": 3, + "minor": 1, + "patch": 0, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "geny_2.13", + "version": { + "major": 1, + "minor": 0, + "patch": 0, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "scala-library", + "version": { + "major": 2, + "minor": 13, + "patch": 8, + "suffix": [ + + ] + }, + "deps": [ + + ] + } + ] + }, + { + "id": "scala-library", + "version": { + "major": 2, + "minor": 13, + "patch": 10, + "suffix": [ + + ] + }, + "deps": [ + + ] + } + ] + }, + { + "id": "scala-library", + "version": { + "major": 2, + "minor": 13, + "patch": 10, + "suffix": [ + + ] + }, + "deps": [ + + ] + } + ] + }, + { + "id": "upack_2.13", + "version": { + "major": 3, + "minor": 1, + "patch": 0, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "upickle-core_2.13", + "version": { + "major": 3, + "minor": 1, + "patch": 0, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "geny_2.13", + "version": { + "major": 1, + "minor": 0, + "patch": 0, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "scala-library", + "version": { + "major": 2, + "minor": 13, + "patch": 8, + "suffix": [ + + ] + }, + "deps": [ + + ] + } + ] + }, + { + "id": "scala-library", + "version": { + "major": 2, + "minor": 13, + "patch": 10, + "suffix": [ + + ] + }, + "deps": [ + + ] + } + ] + }, + { + "id": "scala-library", + "version": { + "major": 2, + "minor": 13, + "patch": 10, + "suffix": [ + + ] + }, + "deps": [ + + ] + } + ] + }, + { + "id": "upickle-implicits_2.13", + "version": { + "major": 3, + "minor": 1, + "patch": 0, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "upickle-core_2.13", + "version": { + "major": 3, + "minor": 1, + "patch": 0, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "geny_2.13", + "version": { + "major": 1, + "minor": 0, + "patch": 0, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "scala-library", + "version": { + "major": 2, + "minor": 13, + "patch": 8, + "suffix": [ + + ] + }, + "deps": [ + + ] + } + ] + }, + { + "id": "scala-library", + "version": { + "major": 2, + "minor": 13, + "patch": 10, + "suffix": [ + + ] + }, + "deps": [ + + ] + } + ] + }, + { + "id": "scala-library", + "version": { + "major": 2, + "minor": 13, + "patch": 10, + "suffix": [ + + ] + }, + "deps": [ + + ] + } + ] + }, + { + "id": "scala-library", + "version": { + "major": 2, + "minor": 13, + "patch": 10, + "suffix": [ + + ] + }, + "deps": [ + + ] + } + ] + }, + { + "id": "core_2.13", + "version": { + "major": 4, + "minor": 0, + "patch": 0, + "suffix": [ + "M1" + ] + }, + "deps": [ + { + "id": "core_2.13", + "version": { + "major": 1, + "minor": 5, + "patch": 5, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "scala-library", + "version": { + "major": 2, + "minor": 13, + "patch": 10, + "suffix": [ + + ] + }, + "deps": [ + + ] + } + ] + }, + { + "id": "core_2.13", + "version": { + "major": 1, + "minor": 3, + "patch": 13, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "scala-library", + "version": { + "major": 2, + "minor": 13, + "patch": 10, + "suffix": [ + + ] + }, + "deps": [ + + ] + } + ] + }, + { + "id": "ws_2.13", + "version": { + "major": 1, + "minor": 3, + "patch": 13, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "core_2.13", + "version": { + "major": 1, + "minor": 5, + "patch": 5, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "scala-library", + "version": { + "major": 2, + "minor": 13, + "patch": 10, + "suffix": [ + + ] + }, + "deps": [ + + ] + } + ] + }, + { + "id": "core_2.13", + "version": { + "major": 1, + "minor": 3, + "patch": 13, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "scala-library", + "version": { + "major": 2, + "minor": 13, + "patch": 10, + "suffix": [ + + ] + }, + "deps": [ + + ] + } + ] + }, + { + "id": "scala-library", + "version": { + "major": 2, + "minor": 13, + "patch": 10, + "suffix": [ + + ] + }, + "deps": [ + + ] + } + ] + }, + { + "id": "scala-library", + "version": { + "major": 2, + "minor": 13, + "patch": 10, + "suffix": [ + + ] + }, + "deps": [ + + ] + } + ] + }, + { + "id": "json-common_2.13", + "version": { + "major": 4, + "minor": 0, + "patch": 0, + "suffix": [ + "M1" + ] + }, + "deps": [ + { + "id": "core_2.13", + "version": { + "major": 4, + "minor": 0, + "patch": 0, + "suffix": [ + "M1" + ] + }, + "deps": [ + { + "id": "core_2.13", + "version": { + "major": 1, + "minor": 5, + "patch": 5, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "scala-library", + "version": { + "major": 2, + "minor": 13, + "patch": 10, + "suffix": [ + + ] + }, + "deps": [ + + ] + } + ] + }, + { + "id": "core_2.13", + "version": { + "major": 1, + "minor": 3, + "patch": 13, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "scala-library", + "version": { + "major": 2, + "minor": 13, + "patch": 10, + "suffix": [ + + ] + }, + "deps": [ + + ] + } + ] + }, + { + "id": "ws_2.13", + "version": { + "major": 1, + "minor": 3, + "patch": 13, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "core_2.13", + "version": { + "major": 1, + "minor": 5, + "patch": 5, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "scala-library", + "version": { + "major": 2, + "minor": 13, + "patch": 10, + "suffix": [ + + ] + }, + "deps": [ + + ] + } + ] + }, + { + "id": "core_2.13", + "version": { + "major": 1, + "minor": 3, + "patch": 13, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "scala-library", + "version": { + "major": 2, + "minor": 13, + "patch": 10, + "suffix": [ + + ] + }, + "deps": [ + + ] + } + ] + }, + { + "id": "scala-library", + "version": { + "major": 2, + "minor": 13, + "patch": 10, + "suffix": [ + + ] + }, + "deps": [ + + ] + } + ] + }, + { + "id": "scala-library", + "version": { + "major": 2, + "minor": 13, + "patch": 10, + "suffix": [ + + ] + }, + "deps": [ + + ] + } + ] + }, + { + "id": "scala-library", + "version": { + "major": 2, + "minor": 13, + "patch": 10, + "suffix": [ + + ] + }, + "deps": [ + + ] + } + ] + }, + { + "id": "scala-library", + "version": { + "major": 2, + "minor": 13, + "patch": 10, + "suffix": [ + + ] + }, + "deps": [ + + ] + } + ] + }, + { + "id": "scala-library", + "version": { + "major": 2, + "minor": 13, + "patch": 10, + "suffix": [ + + ] + }, + "deps": [ + + ] + } + ] +} \ No newline at end of file diff --git a/scripts/expected/deptree_3.json b/scripts/expected/deptree_3.json new file mode 100644 index 0000000..4d16522 --- /dev/null +++ b/scripts/expected/deptree_3.json @@ -0,0 +1,1720 @@ +{ + "id": "toolkit_3", + "version": { + "major": 0, + "minor": 1, + "patch": 7, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "os-lib_3", + "version": { + "major": 0, + "minor": 9, + "patch": 1, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "geny_3", + "version": { + "major": 1, + "minor": 0, + "patch": 0, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "scala3-library_3", + "version": { + "major": 3, + "minor": 1, + "patch": 3, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "scala-library", + "version": { + "major": 2, + "minor": 13, + "patch": 10, + "suffix": [ + + ] + }, + "deps": [ + + ] + } + ] + } + ] + }, + { + "id": "scala3-library_3", + "version": { + "major": 3, + "minor": 1, + "patch": 3, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "scala-library", + "version": { + "major": 2, + "minor": 13, + "patch": 10, + "suffix": [ + + ] + }, + "deps": [ + + ] + } + ] + } + ] + }, + { + "id": "upickle_3", + "version": { + "major": 3, + "minor": 1, + "patch": 0, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "ujson_3", + "version": { + "major": 3, + "minor": 1, + "patch": 0, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "upickle-core_3", + "version": { + "major": 3, + "minor": 1, + "patch": 0, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "geny_3", + "version": { + "major": 1, + "minor": 0, + "patch": 0, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "scala3-library_3", + "version": { + "major": 3, + "minor": 1, + "patch": 3, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "scala-library", + "version": { + "major": 2, + "minor": 13, + "patch": 10, + "suffix": [ + + ] + }, + "deps": [ + + ] + } + ] + } + ] + }, + { + "id": "scala3-library_3", + "version": { + "major": 3, + "minor": 2, + "patch": 2, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "scala-library", + "version": { + "major": 2, + "minor": 13, + "patch": 10, + "suffix": [ + + ] + }, + "deps": [ + + ] + } + ] + } + ] + }, + { + "id": "scala3-library_3", + "version": { + "major": 3, + "minor": 2, + "patch": 2, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "scala-library", + "version": { + "major": 2, + "minor": 13, + "patch": 10, + "suffix": [ + + ] + }, + "deps": [ + + ] + } + ] + } + ] + }, + { + "id": "upack_3", + "version": { + "major": 3, + "minor": 1, + "patch": 0, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "upickle-core_3", + "version": { + "major": 3, + "minor": 1, + "patch": 0, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "geny_3", + "version": { + "major": 1, + "minor": 0, + "patch": 0, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "scala3-library_3", + "version": { + "major": 3, + "minor": 1, + "patch": 3, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "scala-library", + "version": { + "major": 2, + "minor": 13, + "patch": 10, + "suffix": [ + + ] + }, + "deps": [ + + ] + } + ] + } + ] + }, + { + "id": "scala3-library_3", + "version": { + "major": 3, + "minor": 2, + "patch": 2, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "scala-library", + "version": { + "major": 2, + "minor": 13, + "patch": 10, + "suffix": [ + + ] + }, + "deps": [ + + ] + } + ] + } + ] + }, + { + "id": "scala3-library_3", + "version": { + "major": 3, + "minor": 2, + "patch": 2, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "scala-library", + "version": { + "major": 2, + "minor": 13, + "patch": 10, + "suffix": [ + + ] + }, + "deps": [ + + ] + } + ] + } + ] + }, + { + "id": "upickle-implicits_3", + "version": { + "major": 3, + "minor": 1, + "patch": 0, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "upickle-core_3", + "version": { + "major": 3, + "minor": 1, + "patch": 0, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "geny_3", + "version": { + "major": 1, + "minor": 0, + "patch": 0, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "scala3-library_3", + "version": { + "major": 3, + "minor": 1, + "patch": 3, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "scala-library", + "version": { + "major": 2, + "minor": 13, + "patch": 10, + "suffix": [ + + ] + }, + "deps": [ + + ] + } + ] + } + ] + }, + { + "id": "scala3-library_3", + "version": { + "major": 3, + "minor": 2, + "patch": 2, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "scala-library", + "version": { + "major": 2, + "minor": 13, + "patch": 10, + "suffix": [ + + ] + }, + "deps": [ + + ] + } + ] + } + ] + }, + { + "id": "scala3-library_3", + "version": { + "major": 3, + "minor": 2, + "patch": 2, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "scala-library", + "version": { + "major": 2, + "minor": 13, + "patch": 10, + "suffix": [ + + ] + }, + "deps": [ + + ] + } + ] + } + ] + }, + { + "id": "scala3-library_3", + "version": { + "major": 3, + "minor": 2, + "patch": 2, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "scala-library", + "version": { + "major": 2, + "minor": 13, + "patch": 10, + "suffix": [ + + ] + }, + "deps": [ + + ] + } + ] + } + ] + }, + { + "id": "core_3", + "version": { + "major": 4, + "minor": 0, + "patch": 0, + "suffix": [ + "M1" + ] + }, + "deps": [ + { + "id": "core_3", + "version": { + "major": 1, + "minor": 5, + "patch": 5, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "scala3-library_3", + "version": { + "major": 3, + "minor": 2, + "patch": 2, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "scala-library", + "version": { + "major": 2, + "minor": 13, + "patch": 10, + "suffix": [ + + ] + }, + "deps": [ + + ] + } + ] + } + ] + }, + { + "id": "core_3", + "version": { + "major": 1, + "minor": 3, + "patch": 13, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "scala3-library_3", + "version": { + "major": 3, + "minor": 2, + "patch": 2, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "scala-library", + "version": { + "major": 2, + "minor": 13, + "patch": 10, + "suffix": [ + + ] + }, + "deps": [ + + ] + } + ] + } + ] + }, + { + "id": "ws_3", + "version": { + "major": 1, + "minor": 3, + "patch": 13, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "core_3", + "version": { + "major": 1, + "minor": 5, + "patch": 5, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "scala3-library_3", + "version": { + "major": 3, + "minor": 2, + "patch": 2, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "scala-library", + "version": { + "major": 2, + "minor": 13, + "patch": 10, + "suffix": [ + + ] + }, + "deps": [ + + ] + } + ] + } + ] + }, + { + "id": "core_3", + "version": { + "major": 1, + "minor": 3, + "patch": 13, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "scala3-library_3", + "version": { + "major": 3, + "minor": 2, + "patch": 2, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "scala-library", + "version": { + "major": 2, + "minor": 13, + "patch": 10, + "suffix": [ + + ] + }, + "deps": [ + + ] + } + ] + } + ] + }, + { + "id": "scala3-library_3", + "version": { + "major": 3, + "minor": 2, + "patch": 2, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "scala-library", + "version": { + "major": 2, + "minor": 13, + "patch": 10, + "suffix": [ + + ] + }, + "deps": [ + + ] + } + ] + } + ] + }, + { + "id": "scala3-library_3", + "version": { + "major": 3, + "minor": 2, + "patch": 2, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "scala-library", + "version": { + "major": 2, + "minor": 13, + "patch": 10, + "suffix": [ + + ] + }, + "deps": [ + + ] + } + ] + } + ] + }, + { + "id": "upickle_3", + "version": { + "major": 4, + "minor": 0, + "patch": 0, + "suffix": [ + "M1" + ] + }, + "deps": [ + { + "id": "upickle_3", + "version": { + "major": 3, + "minor": 0, + "patch": 0, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "ujson_3", + "version": { + "major": 3, + "minor": 1, + "patch": 0, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "upickle-core_3", + "version": { + "major": 3, + "minor": 1, + "patch": 0, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "geny_3", + "version": { + "major": 1, + "minor": 0, + "patch": 0, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "scala3-library_3", + "version": { + "major": 3, + "minor": 1, + "patch": 3, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "scala-library", + "version": { + "major": 2, + "minor": 13, + "patch": 10, + "suffix": [ + + ] + }, + "deps": [ + + ] + } + ] + } + ] + }, + { + "id": "scala3-library_3", + "version": { + "major": 3, + "minor": 2, + "patch": 2, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "scala-library", + "version": { + "major": 2, + "minor": 13, + "patch": 10, + "suffix": [ + + ] + }, + "deps": [ + + ] + } + ] + } + ] + }, + { + "id": "scala3-library_3", + "version": { + "major": 3, + "minor": 2, + "patch": 2, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "scala-library", + "version": { + "major": 2, + "minor": 13, + "patch": 10, + "suffix": [ + + ] + }, + "deps": [ + + ] + } + ] + } + ] + }, + { + "id": "upack_3", + "version": { + "major": 3, + "minor": 1, + "patch": 0, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "upickle-core_3", + "version": { + "major": 3, + "minor": 1, + "patch": 0, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "geny_3", + "version": { + "major": 1, + "minor": 0, + "patch": 0, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "scala3-library_3", + "version": { + "major": 3, + "minor": 1, + "patch": 3, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "scala-library", + "version": { + "major": 2, + "minor": 13, + "patch": 10, + "suffix": [ + + ] + }, + "deps": [ + + ] + } + ] + } + ] + }, + { + "id": "scala3-library_3", + "version": { + "major": 3, + "minor": 2, + "patch": 2, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "scala-library", + "version": { + "major": 2, + "minor": 13, + "patch": 10, + "suffix": [ + + ] + }, + "deps": [ + + ] + } + ] + } + ] + }, + { + "id": "scala3-library_3", + "version": { + "major": 3, + "minor": 2, + "patch": 2, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "scala-library", + "version": { + "major": 2, + "minor": 13, + "patch": 10, + "suffix": [ + + ] + }, + "deps": [ + + ] + } + ] + } + ] + }, + { + "id": "upickle-implicits_3", + "version": { + "major": 3, + "minor": 1, + "patch": 0, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "upickle-core_3", + "version": { + "major": 3, + "minor": 1, + "patch": 0, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "geny_3", + "version": { + "major": 1, + "minor": 0, + "patch": 0, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "scala3-library_3", + "version": { + "major": 3, + "minor": 1, + "patch": 3, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "scala-library", + "version": { + "major": 2, + "minor": 13, + "patch": 10, + "suffix": [ + + ] + }, + "deps": [ + + ] + } + ] + } + ] + }, + { + "id": "scala3-library_3", + "version": { + "major": 3, + "minor": 2, + "patch": 2, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "scala-library", + "version": { + "major": 2, + "minor": 13, + "patch": 10, + "suffix": [ + + ] + }, + "deps": [ + + ] + } + ] + } + ] + }, + { + "id": "scala3-library_3", + "version": { + "major": 3, + "minor": 2, + "patch": 2, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "scala-library", + "version": { + "major": 2, + "minor": 13, + "patch": 10, + "suffix": [ + + ] + }, + "deps": [ + + ] + } + ] + } + ] + }, + { + "id": "scala3-library_3", + "version": { + "major": 3, + "minor": 2, + "patch": 2, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "scala-library", + "version": { + "major": 2, + "minor": 13, + "patch": 10, + "suffix": [ + + ] + }, + "deps": [ + + ] + } + ] + } + ] + }, + { + "id": "core_3", + "version": { + "major": 4, + "minor": 0, + "patch": 0, + "suffix": [ + "M1" + ] + }, + "deps": [ + { + "id": "core_3", + "version": { + "major": 1, + "minor": 5, + "patch": 5, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "scala3-library_3", + "version": { + "major": 3, + "minor": 2, + "patch": 2, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "scala-library", + "version": { + "major": 2, + "minor": 13, + "patch": 10, + "suffix": [ + + ] + }, + "deps": [ + + ] + } + ] + } + ] + }, + { + "id": "core_3", + "version": { + "major": 1, + "minor": 3, + "patch": 13, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "scala3-library_3", + "version": { + "major": 3, + "minor": 2, + "patch": 2, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "scala-library", + "version": { + "major": 2, + "minor": 13, + "patch": 10, + "suffix": [ + + ] + }, + "deps": [ + + ] + } + ] + } + ] + }, + { + "id": "ws_3", + "version": { + "major": 1, + "minor": 3, + "patch": 13, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "core_3", + "version": { + "major": 1, + "minor": 5, + "patch": 5, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "scala3-library_3", + "version": { + "major": 3, + "minor": 2, + "patch": 2, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "scala-library", + "version": { + "major": 2, + "minor": 13, + "patch": 10, + "suffix": [ + + ] + }, + "deps": [ + + ] + } + ] + } + ] + }, + { + "id": "core_3", + "version": { + "major": 1, + "minor": 3, + "patch": 13, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "scala3-library_3", + "version": { + "major": 3, + "minor": 2, + "patch": 2, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "scala-library", + "version": { + "major": 2, + "minor": 13, + "patch": 10, + "suffix": [ + + ] + }, + "deps": [ + + ] + } + ] + } + ] + }, + { + "id": "scala3-library_3", + "version": { + "major": 3, + "minor": 2, + "patch": 2, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "scala-library", + "version": { + "major": 2, + "minor": 13, + "patch": 10, + "suffix": [ + + ] + }, + "deps": [ + + ] + } + ] + } + ] + }, + { + "id": "scala3-library_3", + "version": { + "major": 3, + "minor": 2, + "patch": 2, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "scala-library", + "version": { + "major": 2, + "minor": 13, + "patch": 10, + "suffix": [ + + ] + }, + "deps": [ + + ] + } + ] + } + ] + }, + { + "id": "json-common_3", + "version": { + "major": 4, + "minor": 0, + "patch": 0, + "suffix": [ + "M1" + ] + }, + "deps": [ + { + "id": "core_3", + "version": { + "major": 4, + "minor": 0, + "patch": 0, + "suffix": [ + "M1" + ] + }, + "deps": [ + { + "id": "core_3", + "version": { + "major": 1, + "minor": 5, + "patch": 5, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "scala3-library_3", + "version": { + "major": 3, + "minor": 2, + "patch": 2, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "scala-library", + "version": { + "major": 2, + "minor": 13, + "patch": 10, + "suffix": [ + + ] + }, + "deps": [ + + ] + } + ] + } + ] + }, + { + "id": "core_3", + "version": { + "major": 1, + "minor": 3, + "patch": 13, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "scala3-library_3", + "version": { + "major": 3, + "minor": 2, + "patch": 2, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "scala-library", + "version": { + "major": 2, + "minor": 13, + "patch": 10, + "suffix": [ + + ] + }, + "deps": [ + + ] + } + ] + } + ] + }, + { + "id": "ws_3", + "version": { + "major": 1, + "minor": 3, + "patch": 13, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "core_3", + "version": { + "major": 1, + "minor": 5, + "patch": 5, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "scala3-library_3", + "version": { + "major": 3, + "minor": 2, + "patch": 2, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "scala-library", + "version": { + "major": 2, + "minor": 13, + "patch": 10, + "suffix": [ + + ] + }, + "deps": [ + + ] + } + ] + } + ] + }, + { + "id": "core_3", + "version": { + "major": 1, + "minor": 3, + "patch": 13, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "scala3-library_3", + "version": { + "major": 3, + "minor": 2, + "patch": 2, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "scala-library", + "version": { + "major": 2, + "minor": 13, + "patch": 10, + "suffix": [ + + ] + }, + "deps": [ + + ] + } + ] + } + ] + }, + { + "id": "scala3-library_3", + "version": { + "major": 3, + "minor": 2, + "patch": 2, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "scala-library", + "version": { + "major": 2, + "minor": 13, + "patch": 10, + "suffix": [ + + ] + }, + "deps": [ + + ] + } + ] + } + ] + }, + { + "id": "scala3-library_3", + "version": { + "major": 3, + "minor": 2, + "patch": 2, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "scala-library", + "version": { + "major": 2, + "minor": 13, + "patch": 10, + "suffix": [ + + ] + }, + "deps": [ + + ] + } + ] + } + ] + }, + { + "id": "scala3-library_3", + "version": { + "major": 3, + "minor": 2, + "patch": 2, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "scala-library", + "version": { + "major": 2, + "minor": 13, + "patch": 10, + "suffix": [ + + ] + }, + "deps": [ + + ] + } + ] + } + ] + }, + { + "id": "scala3-library_3", + "version": { + "major": 3, + "minor": 2, + "patch": 2, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "scala-library", + "version": { + "major": 2, + "minor": 13, + "patch": 10, + "suffix": [ + + ] + }, + "deps": [ + + ] + } + ] + } + ] + }, + { + "id": "scala3-library_3", + "version": { + "major": 3, + "minor": 3, + "patch": 0, + "suffix": [ + + ] + }, + "deps": [ + { + "id": "scala-library", + "version": { + "major": 2, + "minor": 13, + "patch": 10, + "suffix": [ + + ] + }, + "deps": [ + + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/scripts/savetree.sc b/scripts/savetree.sc new file mode 100755 index 0000000..4c0eb3c --- /dev/null +++ b/scripts/savetree.sc @@ -0,0 +1,40 @@ +#!/usr/bin/env -S scala-cli shebang +//> using scala 3.3 +//> using toolkit 0.1.7 +//> using dep io.get-coursier:coursier_2.13:2.1.4 +//> using file Dependencies.scala +//> using file Config.scala + +import upickle.default.* +import Dependencies.* +import coursier.* +import coursier.given +import coursier.graph.DependencyTree + +Utility.requireCmd("scala-cli") + +val (version, toolkitFile, overwrite) = args match + case Array(VersionString(version), toolkitFile) => (version, toolkitFile, false) + case Array(VersionString(version), toolkitFile, "--overwrite") => (version, toolkitFile, true) + case _ => throw new Exception("Usage: ./scripts/savetree.sc [--overwrite]])") + +println("Publishing locally to validate the dependency tree...") +os.proc("scala-cli", "--power", "publish", "local", "--cross", "--organization", Config.organization, "--version", version.toString, toolkitFile).call() + +Config.crossVersions.foreach(saveTree) + +def saveTree(crossVersion: String): Unit = + val file = os.pwd / "scripts" / "expected" / s"deptree_${crossVersion}.json" + require(!os.exists(file) || overwrite, s"File $file already exists. Use --overwrite to overwrite it.") + + val resolution = Resolve() + .addDependencies(Dependency(Module(Organization(Config.organization), ModuleName(Config.name + "_" + crossVersion)), version.toString)) + .run() + + val head = DependencyTree(resolution).head + val depTree = makeDepTree(head) + val snapshot = Dep(depTree.id, version, depTree.deps) + + println(snapshot) + + os.write.over(file, write(snapshot, 2), createFolders = true)