Merge branch 'develop' of ../sbt-projectmatrix into sbt-projectmatrix

This commit is contained in:
Adrien Piquerez 2024-09-17 16:54:18 +02:00
commit 3dcc6cfcd1
80 changed files with 1855 additions and 0 deletions

4
.github/decodekey.sh vendored Executable file
View File

@ -0,0 +1,4 @@
#!/bin/bash
# echo $PGP_SECRET | base64 --decode | gpg --import --batch --yes --pinentry-mode loopback --passphrase $PGP_PASSPHRASE
echo $PGP_SECRET | base64 --decode | gpg --batch --import

32
.github/workflows/release.txt vendored Normal file
View File

@ -0,0 +1,32 @@
name: Release
on:
push:
tags: ["*"]
jobs:
build:
runs-on: ubuntu-latest
env:
# define Java options for both official sbt and sbt-extras
JAVA_OPTS: -Xms2048M -Xmx2048M -Xss6M -XX:ReservedCodeCacheSize=256M -Dfile.encoding=UTF-8
JVM_OPTS: -Xms2048M -Xmx2048M -Xss6M -XX:ReservedCodeCacheSize=256M -Dfile.encoding=UTF-8
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup JDK
uses: actions/setup-java@v3
with:
distribution: "temurin"
java-version: 8
cache: sbt
- name: Release
env:
SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }}
SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }}
PGP_PASSPHRASE: ${{ secrets.PGP_PASSPHRASE }}
PGP_SECRET: ${{ secrets.PGP_SECRET }}
CI_CLEAN: clean
CI_RELEASE: publishSigned
CI_SONATYPE_RELEASE: version
run: |
sbt ci-release

199
README.markdown Normal file
View File

@ -0,0 +1,199 @@
sbt-projectmatrix
=================
cross building using subprojects.
This is an experimental plugin that implements better cross building.
setup
-----
**Requirements**: Requires sbt 1.2.0 or above.
In `project/plugins.sbt`:
```scala
addSbtPlugin("com.eed3si9n" % "sbt-projectmatrix" % "0.10.0")
// add also the following for Scala.js support
addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.10.1")
```
usage
-----
### building against multiple Scala versions
After adding sbt-projectmatrix to your build, here's how you can set up a matrix with two Scala versions.
```scala
ThisBuild / organization := "com.example"
ThisBuild / scalaVersion := "2.13.3"
ThisBuild / version := "0.1.0-SNAPSHOT"
lazy val core = (projectMatrix in file("core"))
.settings(
name := "core"
)
.jvmPlatform(scalaVersions = Seq("2.13.3", "2.12.12"))
```
This will create subprojects `core` and `core2_12`.
Unlike `++` style stateful cross building, these will build in parallel.
### two matrices
It gets more interesting if you have more than one matrix.
```scala
ThisBuild / organization := "com.example"
ThisBuild / scalaVersion := "2.13.3"
ThisBuild / version := "0.1.0-SNAPSHOT"
// uncomment if you want root
// lazy val root = (project in file("."))
// .aggregate(core.projectRefs ++ app.projectRefs: _*)
// .settings(
// )
lazy val core = (projectMatrix in file("core"))
.settings(
name := "core"
)
.jvmPlatform(scalaVersions = Seq("2.13.3", "2.12.12"))
lazy val app = (projectMatrix in file("app"))
.dependsOn(core)
.settings(
name := "app"
)
.jvmPlatform(scalaVersions = Seq("2.13.3"))
```
This is an example where `core` builds against Scala 2.12 and 2.13, but app only builds for one of them.
### Scala.js support
[Scala.js](http://scala-js.org/) support was added in sbt-projectmatrix 0.2.0.
To use this, you need to setup sbt-scalajs as well:
```scala
lazy val core = (projectMatrix in file("core"))
.settings(
name := "core"
)
.jsPlatform(scalaVersions = Seq("2.12.12", "2.11.12"))
```
This will create subprojects `coreJS2_11` and `coreJS2_12`.
### Scala Native support
[Scala Native](http://scala-native.org) support will be added in upcoming release.
To use this, you need to setup sbt-scala-native` as well:
```scala
lazy val core = (projectMatrix in file("core"))
.settings(
name := "core"
)
.nativePlatform(scalaVersions = Seq("2.11.12"))
```
This will create subproject `coreNative2_11`.
### parallel cross-library building
The rows can also be used for parallel cross-library building.
For example, if you want to build against Config 1.2 and Config 1.3, you can do something like this:
In `project/ConfigAxis.scala`:
```scala
import sbt._
case class ConfigAxis(idSuffix: String, directorySuffix: String) extends VirtualAxis.WeakAxis {
}
```
In `build.sbt`:
```scala
ThisBuild / organization := "com.example"
ThisBuild / version := "0.1.0-SNAPSHOT"
lazy val config12 = ConfigAxis("Config1_2", "config1.2")
lazy val config13 = ConfigAxis("Config1_3", "config1.3")
lazy val scala212 = "2.12.10"
lazy val scala211 = "2.11.12"
lazy val app = (projectMatrix in file("app"))
.settings(
name := "app"
)
.customRow(
scalaVersions = Seq(scala212, scala211),
axisValues = Seq(config12, VirtualAxis.jvm),
_.settings(
moduleName := name.value + "_config1.2",
libraryDependencies += "com.typesafe" % "config" % "1.2.1"
)
)
.customRow(
scalaVersions = Seq(scala212, scala211),
axisValues = Seq(config13, VirtualAxis.jvm),
_.settings(
moduleName := name.value + "_config1.3",
libraryDependencies += "com.typesafe" % "config" % "1.3.3"
)
)
```
This will create `appConfig1_22_11`, `appConfig1_22_12`, and `appConfig1_32_12` respectively producing `app_config1.3_2.12`, `app_config1.2_2.11`, and `app_config1.2_2.12` artifacts.
### referencing the generated subprojects
You might want to reference to one of the projects within `build.sbt`.
```scala
lazy val core12 = core.jvm("2.12.8")
lazy val appConfig12_212 = app.finder(config13, VirtualAxis.jvm)("2.12.8")
```
In the above `core12` returns `Project` type.
### accessing axes from subprojects
Each generated subproject can access the values for all the axes using `virtualAxes` key:
```scala
lazy val platformTest = settingKey[String]("")
lazy val core = (projectMatrix in file("core"))
.settings(
name := "core"
)
.jsPlatform(scalaVersions = Seq("2.12.12", "2.11.12"))
.jvmPlatform(scalaVersion = Seq("2.12.12", "2.13.3"))
.settings(
platformTest := {
if(virtualAxes.value.contains(VirtualAxis.jvm))
"JVM project"
else
"JS project"
}
)
```
credits
-------
- The idea of representing cross build using subproject was pionieered by Tobias Schlatter's work on Scala.js plugin, which was later expanded to [ sbt-crossproject](https://github.com/portable-scala/sbt-crossproject). However, this only addresses the platform (JVM, JS, Native) cross building.
- [sbt-cross](https://github.com/lucidsoftware/sbt-cross) written by Paul Draper in 2015 implements cross building across Scala versions.
license
-------
MIT License

View File

@ -0,0 +1,118 @@
package sbt
import sbt.librarymanagement.CrossVersion.{ binaryScalaVersion, partialVersion }
/** Virtual Axis represents a parameter to a project matrix row. */
sealed abstract class VirtualAxis {
def directorySuffix: String
def idSuffix: String
/* The order to sort the suffixes if there were multiple axes. */
def suffixOrder: Int = 50
}
object VirtualAxis {
/**
* WeakAxis allows a row to depend on another row with Zero value.
* For example, Scala version can be Zero for Java project, and it's ok.
*/
abstract class WeakAxis extends VirtualAxis
/** StrongAxis requires a row to depend on another row with the same selected value. */
abstract class StrongAxis extends VirtualAxis
def isMatch(lhs: Seq[VirtualAxis], rhs: Seq[VirtualAxis]): Boolean =
lhs.forall(isStronglyCompatible(_, rhs)) && rhs.forall(isStronglyCompatible(_, lhs))
private[sbt] def isStronglyCompatible(v: VirtualAxis, stack: Seq[VirtualAxis]): Boolean =
v match {
case v: WeakAxis =>
val clazz = v.getClass
stack.contains(v) || !stack.exists(_.getClass == clazz)
case v: StrongAxis =>
stack.contains(v)
}
def isSecondaryMatch(lhs: Seq[VirtualAxis], rhs: Seq[VirtualAxis]): Boolean =
lhs.forall(isSecondaryCompatible(_, rhs)) && rhs.forall(isSecondaryCompatible(_, lhs))
def isSecondaryCompatible(v: VirtualAxis, stack: Seq[VirtualAxis]): Boolean =
v match {
case v: ScalaVersionAxis =>
val thatSVOpt = (stack collect {
case x: ScalaVersionAxis => x
}).headOption
thatSVOpt match {
case Some(ScalaVersionAxis(sv, _)) =>
(v.scalaVersion == sv) ||
isScala2Scala3Sandwich(partialVersion(v.scalaVersion), partialVersion(sv))
case _ => true
}
case _ =>
isStronglyCompatible(v, stack)
}
private[sbt] def isScala2Scala3Sandwich(sbv1: Option[(Long, Long)], sbv2: Option[(Long, Long)]): Boolean = {
def str(x: Option[(Long, Long)]): String =
x match {
case Some((a, b)) => s"$a.$b"
case _ => "0.0"
}
isScala2Scala3Sandwich(str(sbv1), str(sbv2))
}
private[sbt] def isScala2Scala3Sandwich(sbv1: String, sbv2: String): Boolean = {
def compare(a: String, b: String): Boolean =
a == "2.13" && (b.startsWith("0.") || b.startsWith("3."))
compare(sbv1, sbv2) || compare(sbv2, sbv1)
}
// This admits partial Scala version
private[sbt] def isPartialVersionEquals(ax1: VirtualAxis, ax2: VirtualAxis): Boolean = {
(ax1, ax2) match {
case (ax1: ScalaVersionAxis, ax2: ScalaVersionAxis) =>
(ax1 == ax2) || (ax1.value == ax2.value)
case _ => ax1 == ax2
}
}
case class ScalaVersionAxis(scalaVersion: String, value: String) extends WeakAxis {
override def idSuffix: String = directorySuffix.replaceAll("""\W+""", "_")
override val suffixOrder: Int = 100
override def directorySuffix: String = value
// use only the scalaVersion field for equality
override def equals(obj: Any): Boolean = {
if (obj.isInstanceOf[AnyRef] && (this eq obj.asInstanceOf[AnyRef])) true
else if (!obj.isInstanceOf[ScalaVersionAxis]) false
else {
val o = obj.asInstanceOf[ScalaVersionAxis]
this.scalaVersion == o.scalaVersion
}
}
override def hashCode: Int = {
37 * (17 + "sbt.ScalaVersionAxis".hashCode()) + scalaVersion.hashCode()
}
}
case class PlatformAxis(value: String, idSuffix: String, directorySuffix: String) extends StrongAxis {
override val suffixOrder: Int = 80
}
def scalaPartialVersion(scalaVersion: String): ScalaVersionAxis =
partialVersion(scalaVersion) match {
case Some((m, n)) => scalaVersionAxis(scalaVersion, s"$m.$n")
case _ => scalaVersionAxis(scalaVersion, scalaVersion)
}
def scalaABIVersion(scalaVersion: String): ScalaVersionAxis =
scalaVersionAxis(scalaVersion, binaryScalaVersion(scalaVersion))
def scalaVersionAxis(scalaVersion: String, value: String) =
ScalaVersionAxis(scalaVersion, value)
val jvm: PlatformAxis = PlatformAxis("jvm", "JVM", "jvm")
val js: PlatformAxis = PlatformAxis("js", "JS", "js")
val native: PlatformAxis = PlatformAxis("native", "Native", "native")
}

View File

@ -0,0 +1,633 @@
package sbt
package internal
import java.util.Locale
import scala.collection.immutable.ListMap
import scala.collection.mutable
import Keys._
import scala.util.Try
import sbt.internal.inc.ReflectUtilities
import sbtprojectmatrix.ProjectMatrixKeys
/**
* A project matrix is an implementation of a composite project
* that represents cross building across some axis (such as platform)
* and Scala version.
*
* {{{
* lazy val core = (projectMatrix in file("core"))
* .settings(
* name := "core"
* )
* .jvmPlatform(Seq("2.12.6", "2.11.12"))
* }}}
*/
sealed trait ProjectMatrix extends CompositeProject {
def id: String
/** The base directory for the project matrix.*/
def base: sbt.File
def withId(id: String): ProjectMatrix
/** Sets the base directory for this project matrix.*/
def in(dir: sbt.File): ProjectMatrix
/** Adds new configurations directly to this project. To override an existing configuration, use `overrideConfigs`. */
def configs(cs: Configuration*): ProjectMatrix
/** Adds classpath dependencies on internal or external projects. */
def dependsOn(deps: MatrixClasspathDep[ProjectMatrixReference]*): ProjectMatrix
/** Adds classpath dependencies on internal or external non-matrix projects. */
def dependsOn(deps: ClasspathDep[ProjectReference]*)(implicit dummyImplicit: DummyImplicit): ProjectMatrix
/**
* Adds projects to be aggregated. When a user requests a task to run on this project from the command line,
* the task will also be run in aggregated projects.
*/
def aggregate(refs: ProjectMatrixReference*): ProjectMatrix
/**
* Allows non-matrix projects to be aggregated in a matrix project.
*/
def aggregate(refs: ProjectReference*)(implicit dummyImplicit: DummyImplicit): ProjectMatrix
/** Appends settings to the current settings sequence for this project. */
def settings(ss: Def.SettingsDefinition*): ProjectMatrix
/**
* Sets the [[sbt.AutoPlugin]]s of this project.
* An [[sbt.AutoPlugin]] is a common label that is used by plugins to determine what settings, if any, to enable on a project.
*/
def enablePlugins(ns: Plugins*): ProjectMatrix
/** Disable the given plugins on this project. */
def disablePlugins(ps: AutoPlugin*): ProjectMatrix
/**
* Applies the given functions to this Project.
* The second function is applied to the result of applying the first to this Project and so on.
* The intended use is a convenience for applying default configuration provided by a plugin.
*/
def configure(transforms: (Project => Project)*): ProjectMatrix
/**
* If autoScalaLibrary is false, add non-Scala row.
* Otherwise, add custom rows for each scalaVersions.
*/
def customRow(
autoScalaLibrary: Boolean,
scalaVersions: Seq[String],
axisValues: Seq[VirtualAxis],
process: Project => Project
): ProjectMatrix
def customRow(
scalaVersions: Seq[String],
axisValues: Seq[VirtualAxis],
process: Project => Project
): ProjectMatrix
def customRow(
autoScalaLibrary: Boolean,
axisValues: Seq[VirtualAxis],
process: Project => Project
): ProjectMatrix
def customRow(
scalaVersions: Seq[String],
axisValues: Seq[VirtualAxis],
settings: Seq[Setting[_]]
): ProjectMatrix
def customRow(
autoScalaLibrary: Boolean,
axisValues: Seq[VirtualAxis],
settings: Seq[Setting[_]]
): ProjectMatrix
def jvmPlatform(scalaVersions: Seq[String]): ProjectMatrix
def jvmPlatform(autoScalaLibrary: Boolean): ProjectMatrix
def jvmPlatform(scalaVersions: Seq[String], settings: Seq[Setting[_]]): ProjectMatrix
def jvmPlatform(scalaVersions: Seq[String], axisValues: Seq[VirtualAxis], settings: Seq[Setting[_]]): ProjectMatrix
def jvmPlatform(scalaVersions: Seq[String], axisValues: Seq[VirtualAxis], configure: Project => Project): ProjectMatrix
def jvmPlatform(autoScalaLibrary: Boolean, scalaVersions: Seq[String], settings: Seq[Setting[_]]): ProjectMatrix
def jvm: ProjectFinder
def jsPlatform(scalaVersions: Seq[String]): ProjectMatrix
def jsPlatform(scalaVersions: Seq[String], settings: Seq[Setting[_]]): ProjectMatrix
def jsPlatform(scalaVersions: Seq[String], axisValues: Seq[VirtualAxis], settings: Seq[Setting[_]]): ProjectMatrix
def jsPlatform(scalaVersions: Seq[String], axisValues: Seq[VirtualAxis], configure: Project => Project): ProjectMatrix
def js: ProjectFinder
def nativePlatform(scalaVersions: Seq[String]): ProjectMatrix
def nativePlatform(scalaVersions: Seq[String], settings: Seq[Setting[_]]): ProjectMatrix
def nativePlatform(scalaVersions: Seq[String], axisValues: Seq[VirtualAxis], settings: Seq[Setting[_]]): ProjectMatrix
def nativePlatform(scalaVersions: Seq[String], axisValues: Seq[VirtualAxis], configure: Project => Project): ProjectMatrix
def native: ProjectFinder
def defaultAxes(axes: VirtualAxis*): ProjectMatrix
def projectRefs: Seq[ProjectReference]
def filterProjects(axisValues: Seq[VirtualAxis]): Seq[Project]
def filterProjects(autoScalaLibrary: Boolean, axisValues: Seq[VirtualAxis]): Seq[Project]
def finder(axisValues: VirtualAxis*): ProjectFinder
def allProjects(): Seq[(Project, Seq[VirtualAxis])]
// resolve to the closest match for the given row
private[sbt] def resolveMatch(thatRow: ProjectMatrix.ProjectRow): ProjectReference
}
/** Represents a reference to a project matrix with an optional configuration string.
*/
sealed trait MatrixClasspathDep[MR <: ProjectMatrixReference] {
def matrix: MR; def configuration: Option[String]
}
trait ProjectFinder {
def apply(scalaVersion: String): Project
def apply(autoScalaLibrary: Boolean): Project
def get: Seq[Project]
}
object ProjectMatrix {
import sbt.io.syntax._
val jvmIdSuffix: String = "JVM"
val jvmDirectorySuffix: String = "-jvm"
val jsIdSuffix: String = "JS"
val jsDirectorySuffix: String = "-js"
val nativeIdSuffix: String = "Native"
val nativeDirectorySuffix: String = "-native"
private[sbt] val allMatrices: mutable.Map[String, ProjectMatrix] = mutable.Map.empty
/** A row in the project matrix, typically representing a platform + Scala version.
*/
final class ProjectRow(
val autoScalaLibrary: Boolean,
val axisValues: Seq[VirtualAxis],
val process: Project => Project
) {
def scalaVersionOpt: Option[String] =
if (autoScalaLibrary)
(axisValues collect {
case sv: VirtualAxis.ScalaVersionAxis => sv.scalaVersion
}).headOption
else None
def isMatch(that: ProjectRow): Boolean =
VirtualAxis.isMatch(this.axisValues, that.axisValues)
def isSecondaryMatch(that: ProjectRow): Boolean =
VirtualAxis.isSecondaryMatch(this.axisValues, that.axisValues)
override def toString: String = s"ProjectRow($autoScalaLibrary, $axisValues)"
}
final class ProjectMatrixReferenceSyntax(m: ProjectMatrixReference) {
def %(conf: String): ProjectMatrix.MatrixClasspathDependency =
ProjectMatrix.MatrixClasspathDependency(m, Some(conf))
def %(conf: Configuration): ProjectMatrix.MatrixClasspathDependency =
ProjectMatrix.MatrixClasspathDependency(m, Some(conf.name))
}
final case class MatrixClasspathDependency(
matrix: ProjectMatrixReference,
configuration: Option[String]
) extends MatrixClasspathDep[ProjectMatrixReference]
private final class ProjectMatrixDef(
val id: String,
val base: sbt.File,
val scalaVersions: Seq[String],
val rows: Seq[ProjectRow],
val aggregate: Seq[ProjectMatrixReference],
val nonMatrixAggregate: Seq[ProjectReference],
val dependencies: Seq[MatrixClasspathDep[ProjectMatrixReference]],
val nonMatrixDependencies: Seq[ClasspathDep[ProjectReference]],
val settings: Seq[Def.Setting[_]],
val configurations: Seq[Configuration],
val plugins: Plugins,
val transforms: Seq[Project => Project],
val defAxes: Seq[VirtualAxis],
) extends ProjectMatrix { self =>
lazy val resolvedMappings: ListMap[ProjectRow, Project] = resolveMappings
private def resolveProjectIds: Map[ProjectRow, String] = {
Map((for {
r <- rows
} yield {
val axes = r.axisValues.sortBy(_.suffixOrder)
.filterNot(isSortOfDefaultAxis)
val idSuffix = axes.map(_.idSuffix).mkString("")
val childId = self.id + idSuffix
r -> childId
}): _*)
}
private def isSortOfDefaultAxis(a: VirtualAxis): Boolean =
defAxes exists { da => VirtualAxis.isPartialVersionEquals(da, a) }
private def resolveMappings: ListMap[ProjectRow, Project] = {
val projectIds = resolveProjectIds
ListMap((for {
r <- rows
} yield {
val axes = r.axisValues.sortBy(_.suffixOrder)
val svDirSuffix = axes.map(_.directorySuffix).mkString("-")
val nonScalaDirSuffix = (axes filter {
case _: VirtualAxis.ScalaVersionAxis => false
case _ => true
}).map(_.directorySuffix).mkString("-")
val platform = (axes collect {
case pa: VirtualAxis.PlatformAxis => pa
}).headOption.getOrElse(sys.error(s"platform axis is missing in $axes"))
val childId = projectIds(r)
val deps = dependencies.map { resolveMatrixDependency(_, r) } ++ nonMatrixDependencies
val aggs = aggregate.map {
case ref: LocalProjectMatrix =>
val other = lookupMatrix(ref)
resolveMatrixAggregate(other, r)
} ++ nonMatrixAggregate
val dotSbtMatrix = new java.io.File(".sbt") / "matrix"
IO.createDirectory(dotSbtMatrix)
val p = Project(childId, dotSbtMatrix / childId)
.dependsOn(deps: _*)
.aggregate(aggs: _*)
.setPlugins(plugins)
.configs(configurations: _*)
.settings(
name := self.id
)
.settings(
r.scalaVersionOpt match {
case Some(sv) =>
List(Keys.scalaVersion := sv)
case _ =>
List(Keys.autoScalaLibrary := false, Keys.crossPaths := false)
}
)
.settings(
target := base.getAbsoluteFile / "target" / svDirSuffix.dropWhile(_ == '-'),
crossTarget := Keys.target.value,
sourceDirectory := base.getAbsoluteFile / "src",
unmanagedBase := base.getAbsoluteFile / "lib",
inConfig(Compile)(makeSources(nonScalaDirSuffix, svDirSuffix)),
inConfig(Test)(makeSources(nonScalaDirSuffix, svDirSuffix)),
projectDependencies := projectDependenciesTask.value,
ProjectMatrixKeys.virtualAxes := axes,
ProjectMatrixKeys.projectMatrixBaseDirectory := base,
)
.settings(self.settings)
.configure(transforms: _*)
r -> r.process(p)
}): _*)
}
// backport of https://github.com/sbt/sbt/pull/5767
def projectDependenciesTask: Def.Initialize[Task[Seq[ModuleID]]] =
Def.task {
val orig = projectDependencies.value
val sbv = scalaBinaryVersion.value
val ref = thisProjectRef.value
val data = settingsData.value
val deps = buildDependencies.value
deps.classpath(ref) flatMap { dep =>
for {
depProjId <- (dep.project / projectID).get(data)
depSBV <- (dep.project / scalaBinaryVersion).get(data)
depCross <- (dep.project / crossVersion).get(data)
} yield {
depCross match {
case b: CrossVersion.Binary if VirtualAxis.isScala2Scala3Sandwich(sbv, depSBV) =>
depProjId
.withCrossVersion(CrossVersion.constant(depSBV))
.withConfigurations(dep.configuration)
.withExplicitArtifacts(Vector.empty)
case _ =>
depProjId.withConfigurations(dep.configuration).withExplicitArtifacts(Vector.empty)
}
}
}
}
override lazy val componentProjects: Seq[Project] = resolvedMappings.values.toList
private def resolveMatrixAggregate(
other: ProjectMatrix,
thisRow: ProjectRow,
): ProjectReference = other.resolveMatch(thisRow)
private def resolveMatrixDependency(
dep: MatrixClasspathDep[ProjectMatrixReference],
thisRow: ProjectRow
): ClasspathDep[ProjectReference] =
dep match {
case MatrixClasspathDependency(matrix0: LocalProjectMatrix, configuration) =>
val other = lookupMatrix(matrix0)
ClasspathDependency(other.resolveMatch(thisRow), configuration)
}
// resolve to the closest match for the given row
private[sbt] def resolveMatch(thatRow: ProjectRow): ProjectReference =
(rows.find(r => r.isMatch(thatRow)) orElse
rows.find(r => r.isSecondaryMatch(thatRow))) match {
case Some(r) => LocalProject(resolveProjectIds(r))
case _ => sys.error(s"no rows were found in $id matching $thatRow: $rows")
}
private def makeSources(dirSuffix: String, svDirSuffix: String): Setting[_] = {
unmanagedSourceDirectories ++= Seq(
scalaSource.value.getParentFile / s"scala${dirSuffix}",
scalaSource.value.getParentFile / s"scala$svDirSuffix",
javaSource.value.getParentFile / s"java${dirSuffix}"
)
}
override def withId(id: String): ProjectMatrix = copy(id = id)
override def in(dir: sbt.File): ProjectMatrix = copy(base = dir)
override def configs(cs: Configuration*): ProjectMatrix =
copy(configurations = configurations ++ cs)
override def aggregate(refs: ProjectMatrixReference*): ProjectMatrix =
copy(aggregate = (aggregate: Seq[ProjectMatrixReference]) ++ refs)
override def aggregate(refs: ProjectReference*)(implicit dummyImplicit: DummyImplicit): ProjectMatrix =
copy(nonMatrixAggregate = (nonMatrixAggregate: Seq[ProjectReference]) ++ refs)
override def dependsOn(deps: MatrixClasspathDep[ProjectMatrixReference]*): ProjectMatrix =
copy(dependencies = dependencies ++ deps)
override def dependsOn(deps: ClasspathDep[ProjectReference]*)(implicit dummyImplicit: DummyImplicit) =
copy(nonMatrixDependencies = nonMatrixDependencies ++ deps)
/** Appends settings to the current settings sequence for this project. */
override def settings(ss: Def.SettingsDefinition*): ProjectMatrix =
copy(settings = (settings: Seq[Def.Setting[_]]) ++ Def.settings(ss: _*))
override def enablePlugins(ns: Plugins*): ProjectMatrix =
setPlugins(ns.foldLeft(plugins)(Plugins.and))
override def disablePlugins(ps: AutoPlugin*): ProjectMatrix =
setPlugins(Plugins.and(plugins, Plugins.And(ps.map(p => Plugins.Exclude(p)).toList)))
override def configure(ts: (Project => Project)*): ProjectMatrix =
copy(transforms = transforms ++ ts)
def setPlugins(ns: Plugins): ProjectMatrix = copy(plugins = ns)
override def jvmPlatform(scalaVersions: Seq[String]): ProjectMatrix =
jvmPlatform(scalaVersions, Nil)
override def jvmPlatform(autoScalaLibrary: Boolean): ProjectMatrix =
jvmPlatform(autoScalaLibrary, Nil, Nil)
override def jvmPlatform(scalaVersions: Seq[String], settings: Seq[Setting[_]]): ProjectMatrix =
jvmPlatform(true, scalaVersions, settings)
override def jvmPlatform(autoScalaLibrary: Boolean, scalaVersions: Seq[String], settings: Seq[Setting[_]]): ProjectMatrix =
customRow(autoScalaLibrary, scalaVersions, Seq(VirtualAxis.jvm), { _.settings(settings) })
override def jvmPlatform(scalaVersions: Seq[String], axisValues: Seq[VirtualAxis], settings: Seq[Setting[_]]): ProjectMatrix =
customRow(true, scalaVersions, VirtualAxis.jvm +: axisValues, {_.settings(settings)})
override def jvmPlatform(scalaVersions: Seq[String], axisValues: Seq[VirtualAxis], configure: Project => Project): ProjectMatrix =
customRow(true, scalaVersions, VirtualAxis.jvm +: axisValues, configure)
override def jvm: ProjectFinder = new AxisBaseProjectFinder(Seq(VirtualAxis.jvm))
override def jsPlatform(scalaVersions: Seq[String]): ProjectMatrix =
jsPlatform(scalaVersions, Nil)
private def enableScalaJSPlugin(project: Project): Project =
project.enablePlugins(scalajsPlugin(this.getClass.getClassLoader).getOrElse(
sys.error("""Scala.js plugin was not found. Add the sbt-scalajs plugin into project/plugins.sbt:
| addSbtPlugin("org.scala-js" % "sbt-scalajs" % "x.y.z")
|""".stripMargin)
))
override def jsPlatform(scalaVersions: Seq[String], settings: Seq[Setting[_]]): ProjectMatrix =
customRow(true, scalaVersions, Seq(VirtualAxis.js),
project => enableScalaJSPlugin(project).settings(settings))
override def jsPlatform(scalaVersions: Seq[String], axisValues: Seq[VirtualAxis], settings: Seq[Setting[_]]): ProjectMatrix =
customRow(true, scalaVersions, VirtualAxis.js +: axisValues,
project => enableScalaJSPlugin(project).settings(settings))
override def jsPlatform(scalaVersions: Seq[String], axisValues: Seq[VirtualAxis], configure: Project => Project): ProjectMatrix =
customRow(true, scalaVersions, VirtualAxis.js +: axisValues,
project => configure(enableScalaJSPlugin(project)))
override def defaultAxes(axes: VirtualAxis*): ProjectMatrix =
copy(defAxes = axes.toSeq)
def scalajsPlugin(classLoader: ClassLoader): Try[AutoPlugin] = {
import sbtprojectmatrix.ReflectionUtil._
withContextClassloader(classLoader) { loader =>
getSingletonObject[AutoPlugin](loader, "org.scalajs.sbtplugin.ScalaJSPlugin$")
}
}
override def js: ProjectFinder = new AxisBaseProjectFinder(Seq(VirtualAxis.js))
override def native: ProjectFinder = new AxisBaseProjectFinder(Seq(VirtualAxis.native))
override def nativePlatform(scalaVersions: Seq[String]): ProjectMatrix =
nativePlatform(scalaVersions, Nil)
private def enableScalaNativePlugin(project: Project): Project =
project.enablePlugins(nativePlugin(this.getClass.getClassLoader).getOrElse(
sys.error("""Scala Native plugin was not found. Add the sbt-scala-native plugin into project/plugins.sbt:
| addSbtPlugin("org.scala-native" % "sbt-scala-native" % "x.y.z")
|""".stripMargin)
))
override def nativePlatform(scalaVersions: Seq[String], settings: Seq[Setting[_]]): ProjectMatrix =
customRow(true, scalaVersions, Seq(VirtualAxis.native), project => enableScalaNativePlugin(project).settings(settings))
override def nativePlatform(scalaVersions: Seq[String], axisValues: Seq[VirtualAxis], settings: Seq[Setting[_]]): ProjectMatrix =
customRow(true, scalaVersions, VirtualAxis.native +: axisValues, project => enableScalaNativePlugin(project).settings(settings))
override def nativePlatform(scalaVersions: Seq[String], axisValues: Seq[VirtualAxis], configure: Project => Project): ProjectMatrix =
customRow(true, scalaVersions, VirtualAxis.native +: axisValues, project => configure(enableScalaNativePlugin(project)))
def nativePlugin(classLoader: ClassLoader): Try[AutoPlugin] = {
import sbtprojectmatrix.ReflectionUtil._
withContextClassloader(classLoader) { loader =>
getSingletonObject[AutoPlugin](loader, "scala.scalanative.sbtplugin.ScalaNativePlugin$")
}
}
override def projectRefs: Seq[ProjectReference] =
componentProjects map { case p => (p: ProjectReference) }
override def filterProjects(axisValues: Seq[VirtualAxis]): Seq[Project] =
resolvedMappings.toSeq collect {
case (r, p) if axisValues.forall(v => r.axisValues.contains(v)) => p
}
override def filterProjects(autoScalaLibrary: Boolean, axisValues: Seq[VirtualAxis]): Seq[Project] =
resolvedMappings.toSeq collect {
case (r, p) if r.autoScalaLibrary == autoScalaLibrary && axisValues.forall(v => r.axisValues.contains(v)) => p
}
private final class AxisBaseProjectFinder(axisValues: Seq[VirtualAxis]) extends ProjectFinder {
def get: Seq[Project] = filterProjects(axisValues)
def apply(sv: String): Project =
filterProjects(true, axisValues ++ Seq(VirtualAxis.scalaABIVersion(sv))).headOption
.getOrElse(sys.error(s"project matching $axisValues and $sv was not found"))
def apply(autoScalaLibrary: Boolean): Project =
filterProjects(autoScalaLibrary, axisValues).headOption
.getOrElse(sys.error(s"project matching $axisValues and $autoScalaLibrary was not found"))
}
override def customRow(
scalaVersions: Seq[String],
axisValues: Seq[VirtualAxis],
settings: Seq[Setting[_]]
): ProjectMatrix = customRow(true, scalaVersions, axisValues, { _.settings(settings) })
override def customRow(
autoScalaLibrary: Boolean,
axisValues: Seq[VirtualAxis],
settings: Seq[Setting[_]]
): ProjectMatrix = customRow(autoScalaLibrary, Nil, axisValues, { _.settings(settings) })
override def customRow(
scalaVersions: Seq[String],
axisValues: Seq[VirtualAxis],
process: Project => Project
): ProjectMatrix = customRow(true, scalaVersions, axisValues, process)
override def customRow(
autoScalaLibrary: Boolean,
scalaVersions: Seq[String],
axisValues: Seq[VirtualAxis],
process: Project => Project
): ProjectMatrix =
if (autoScalaLibrary) {
scalaVersions.foldLeft(this: ProjectMatrix) { (acc, sv) =>
acc.customRow(autoScalaLibrary, axisValues ++ Seq(VirtualAxis.scalaABIVersion(sv)), process)
}
} else {
customRow(autoScalaLibrary, axisValues ++ Seq(VirtualAxis.jvm), process)
}
override def customRow(
autoScalaLibrary: Boolean,
axisValues: Seq[VirtualAxis],
process: Project => Project
): ProjectMatrix = {
val newRow: ProjectRow = new ProjectRow(autoScalaLibrary, axisValues, process)
copy(rows = this.rows :+ newRow)
}
override def finder(axisValues: VirtualAxis*): ProjectFinder =
new AxisBaseProjectFinder(axisValues.toSeq)
override def allProjects(): Seq[(Project, Seq[VirtualAxis])] =
resolvedMappings.map { case(row, project) =>
project -> row.axisValues
}.toSeq
def copy(
id: String = id,
base: sbt.File = base,
scalaVersions: Seq[String] = scalaVersions,
rows: Seq[ProjectRow] = rows,
aggregate: Seq[ProjectMatrixReference] = aggregate,
nonMatrixAggregate: Seq[ProjectReference] = nonMatrixAggregate,
dependencies: Seq[MatrixClasspathDep[ProjectMatrixReference]] = dependencies,
nonMatrixDependencies: Seq[ClasspathDep[ProjectReference]] = nonMatrixDependencies,
settings: Seq[Setting[_]] = settings,
configurations: Seq[Configuration] = configurations,
plugins: Plugins = plugins,
transforms: Seq[Project => Project] = transforms,
defAxes: Seq[VirtualAxis] = defAxes,
): ProjectMatrix = {
val matrix = unresolved(
id,
base,
scalaVersions,
rows,
aggregate,
nonMatrixAggregate,
dependencies,
nonMatrixDependencies,
settings,
configurations,
plugins,
transforms,
defAxes,
)
allMatrices(id) = matrix
matrix
}
}
// called by macro
def apply(id: String, base: sbt.File): ProjectMatrix = {
val defaultDefAxes = Seq(VirtualAxis.jvm, VirtualAxis.scalaABIVersion("2.13.3"))
val matrix = unresolved(id, base, Nil, Nil, Nil, Nil, Nil, Nil, Nil, Nil, Plugins.Empty, Nil, defaultDefAxes)
allMatrices(id) = matrix
matrix
}
private[sbt] def unresolved(
id: String,
base: sbt.File,
scalaVersions: Seq[String],
rows: Seq[ProjectRow],
aggregate: Seq[ProjectMatrixReference],
nonMatrixAggregate: Seq[ProjectReference],
dependencies: Seq[MatrixClasspathDep[ProjectMatrixReference]],
nonMatrixDependencies: Seq[ClasspathDep[ProjectReference]],
settings: Seq[Def.Setting[_]],
configurations: Seq[Configuration],
plugins: Plugins,
transforms: Seq[Project => Project],
defAxes: Seq[VirtualAxis],
): ProjectMatrix =
new ProjectMatrixDef(
id,
base,
scalaVersions,
rows,
aggregate,
nonMatrixAggregate,
dependencies,
nonMatrixDependencies,
settings,
configurations,
plugins,
transforms,
defAxes,
)
def lookupMatrix(local: LocalProjectMatrix): ProjectMatrix = {
allMatrices.getOrElse(local.id, sys.error(s"${local.id} was not found"))
}
implicit def projectMatrixToLocalProjectMatrix(m: ProjectMatrix): LocalProjectMatrix =
LocalProjectMatrix(m.id)
import scala.reflect.macros._
def projectMatrixMacroImpl(c: blackbox.Context): c.Expr[ProjectMatrix] = {
import c.universe._
val enclosingValName = std.KeyMacro.definingValName(
c,
methodName =>
s"""$methodName must be directly assigned to a val, such as `val x = $methodName`. Alternatively, you can use `sbt.ProjectMatrix.apply`"""
)
val name = c.Expr[String](Literal(Constant(enclosingValName)))
reify { ProjectMatrix(name.splice, new sbt.File(name.splice)) }
}
}

View File

@ -0,0 +1,8 @@
package sbt
package internal
/** Identifies a project matrix. */
sealed trait ProjectMatrixReference
/** Identifies a project in the current build context. */
final case class LocalProjectMatrix(id: String) extends ProjectMatrixReference

View File

@ -0,0 +1,31 @@
package sbtprojectmatrix
import sbt._
import internal._
import java.util.concurrent.atomic.AtomicBoolean
import scala.language.experimental.macros
trait ProjectMatrixKeys {
val virtualAxes = settingKey[Seq[VirtualAxis]]("Virtual axes for the project")
val projectMatrixBaseDirectory = settingKey[File]("Base directory of the current project matrix")
}
object ProjectMatrixKeys extends ProjectMatrixKeys
object ProjectMatrixPlugin extends AutoPlugin {
override val requires = sbt.plugins.CorePlugin
override val trigger = allRequirements
object autoImport extends ProjectMatrixKeys {
def projectMatrix: ProjectMatrix = macro ProjectMatrix.projectMatrixMacroImpl
implicit def matrixClasspathDependency[T](
m: T
)(implicit ev: T => ProjectMatrixReference): ProjectMatrix.MatrixClasspathDependency =
ProjectMatrix.MatrixClasspathDependency(m, None)
implicit def matrixReferenceSyntax[T](
m: T
)(implicit ev: T => ProjectMatrixReference): ProjectMatrix.ProjectMatrixReferenceSyntax =
new ProjectMatrix.ProjectMatrixReferenceSyntax(m)
}
}

View File

@ -0,0 +1,36 @@
package sbtprojectmatrix
import java.lang.reflect.InvocationTargetException
import scala.reflect.ClassTag
import scala.util.Try
object ReflectionUtil {
def getSingletonObject[A: ClassTag](classLoader: ClassLoader, className: String): Try[A] =
Try {
val clazz = classLoader.loadClass(className)
val t = implicitly[ClassTag[A]].runtimeClass
Option(clazz.getField("MODULE$").get(null)) match {
case None => throw new ClassNotFoundException(s"Unable to find $className using classloader: $classLoader")
case Some(c) if !t.isInstance(c) => throw new ClassCastException(s"${clazz.getName} is not a subtype of $t")
case Some(c: A) => c
}
}
.recover {
case i: InvocationTargetException if i.getTargetException != null => throw i.getTargetException
}
def objectExists(classLoader: ClassLoader, className: String): Boolean =
try {
classLoader.loadClass(className).getField("MODULE$").get(null) != null
} catch {
case _: Throwable => false
}
def withContextClassloader[A](loader: ClassLoader)(body: ClassLoader => A): A = {
val current = Thread.currentThread().getContextClassLoader
try {
Thread.currentThread().setContextClassLoader(loader)
body(loader)
} finally Thread.currentThread().setContextClassLoader(current)
}
}

View File

@ -0,0 +1,5 @@
package example
object B {
def b: Int = A.a
}

View File

@ -0,0 +1,46 @@
ThisBuild / organization := "com.example"
ThisBuild / version := "0.1.0-SNAPSHOT"
ThisBuild / publishMavenStyle := true
ThisBuild / ivyPaths := {
val base = (ThisBuild / baseDirectory).value
IvyPaths(base, Some(base / "ivy-cache"))
}
publish / skip := true
lazy val config12 = ConfigAxis("Config1_2", "config1.2")
lazy val config13 = ConfigAxis("Config1_3", "config1.3")
lazy val scala212 = "2.12.10"
lazy val scala211 = "2.11.12"
lazy val core = (projectMatrix in file("core"))
.jvmPlatform(scalaVersions = Seq(scala212, scala211))
lazy val app = (projectMatrix in file("app"))
.dependsOn(core)
.settings(
name := "app",
ivyPaths := (ThisBuild / ivyPaths).value
)
.customRow(
scalaVersions = Seq(scala212, scala211),
axisValues = Seq(config12, VirtualAxis.jvm),
_.settings(
moduleName := name.value + "_config1.2",
libraryDependencies += "com.typesafe" % "config" % "1.2.1"
)
)
.customRow(
scalaVersions = Seq(scala212, scala211),
axisValues = Seq(config13, VirtualAxis.jvm),
_.settings(
moduleName := name.value + "_config1.3",
libraryDependencies += "com.typesafe" % "config" % "1.3.3"
)
)
lazy val appConfig12_212 = app.finder(config13, VirtualAxis.jvm)(scala212)
.settings(
publishMavenStyle := true
)

View File

@ -0,0 +1,5 @@
package example
object A {
def a: Int = 1
}

View File

@ -0,0 +1,5 @@
import sbt._
case class ConfigAxis(idSuffix: String, directorySuffix: String) extends VirtualAxis.WeakAxis {
}

View File

@ -0,0 +1,5 @@
sys.props.get("plugin.version") match {
case Some(x) => addSbtPlugin("com.eed3si9n" % "sbt-projectmatrix" % x)
case _ => sys.error("""|The system property 'plugin.version' is not defined.
|Specify this property using the scriptedLaunchOpts -D.""".stripMargin)
}

View File

@ -0,0 +1,6 @@
> publishLocal
$ exists ivy-cache/local/com.example/app_config1.2_2.11/0.1.0-SNAPSHOT/poms/app_config1.2_2.11.pom
$ exists ivy-cache/local/com.example/app_config1.2_2.12/0.1.0-SNAPSHOT/poms/app_config1.2_2.12.pom
$ exists ivy-cache/local/com.example/app_config1.3_2.11/0.1.0-SNAPSHOT/poms/app_config1.3_2.11.pom
$ exists ivy-cache/local/com.example/app_config1.3_2.12/0.1.0-SNAPSHOT/poms/app_config1.3_2.12.pom

View File

@ -0,0 +1,45 @@
lazy val scala213 = "2.13.3"
lazy val scala212 = "2.12.12"
lazy val check = taskKey[Unit]("")
lazy val config12 = ConfigAxis("Config1_2", "config1.2")
lazy val config13 = ConfigAxis("Config1_3", "config1.3")
lazy val root = (project in file("."))
.aggregate((core.projectRefs ++ custom.projectRefs):_*)
lazy val core = (projectMatrix in file("core"))
.jvmPlatform(scalaVersions = Seq(scala213, scala212))
.jsPlatform(scalaVersions = Seq(scala212))
lazy val custom =
(projectMatrix in file("custom"))
.customRow(
scalaVersions = Seq(scala212),
axisValues = Seq(config13, VirtualAxis.jvm),
_.settings()
)
check := {
val coreResults: Map[Project, Set[VirtualAxis]] = core.allProjects().toMap.mapValues(_.toSet)
val customResults: Map[Project, Set[VirtualAxis]] = custom.allProjects().toMap.mapValues(_.toSet)
val isJvm = VirtualAxis.jvm
val isJs = VirtualAxis.js
val is213 = VirtualAxis.scalaPartialVersion(scala213)
val is212 = VirtualAxis.scalaPartialVersion(scala212)
val coreSubProjects = Set(
core.jvm(scala213), core.jvm(scala212),
core.js(scala212)
)
assert(coreResults.keySet == coreSubProjects)
assert(coreResults(core.jvm(scala213)) == Set(isJvm, is213))
assert(coreResults(core.jvm(scala212)) == Set(isJvm, is212))
assert(coreResults(core.js(scala212)) == Set(isJs, is212))
assert(customResults.keySet == Set(custom.jvm(scala212)))
assert(customResults(custom.jvm(scala212)) == Set(isJvm, is212, config13))
}

View File

@ -0,0 +1,3 @@
import sbt._
case class ConfigAxis(idSuffix: String, directorySuffix: String) extends VirtualAxis.WeakAxis

View File

@ -0,0 +1,7 @@
sys.props.get("plugin.version") match {
case Some(x) => addSbtPlugin("com.eed3si9n" % "sbt-projectmatrix" % x)
case _ => sys.error("""|The system property 'plugin.version' is not defined.
|Specify this property using the scriptedLaunchOpts -D.""".stripMargin)
}
addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.3.0")

View File

@ -0,0 +1,2 @@
> compile
> check

View File

@ -0,0 +1,9 @@
package com.config;
public class MyClass {
public static String configValue() {
return "1.2";
}
}

View File

@ -0,0 +1,9 @@
package com.config;
public class MyClass {
public static String configValue() {
return "1.3";
}
}

View File

@ -0,0 +1,5 @@
package com.config
object Main extends App {
println(s"Version: ${MyClass.configValue()}")
}

View File

@ -0,0 +1,41 @@
ThisBuild / organization := "com.example"
ThisBuild / version := "0.1.0-SNAPSHOT"
ThisBuild / publishMavenStyle := true
ThisBuild / ivyPaths := {
val base = (ThisBuild / baseDirectory).value
IvyPaths(base, Some(base / "ivy-cache"))
}
publish / skip := true
lazy val config12 = ConfigAxis("Config1_2", "-config1.2")
lazy val config13 = ConfigAxis("Config1_3", "-config1.3")
lazy val scala212 = "2.12.10"
lazy val app = (projectMatrix in file("app"))
.settings(
name := "app",
ivyPaths := (ThisBuild / ivyPaths).value
)
.customRow(
scalaVersions = Seq(scala212),
axisValues = Seq(config12, VirtualAxis.jvm),
_.settings(
moduleName := name.value + "_config1.2",
libraryDependencies += "com.typesafe" % "config" % "1.2.1"
)
)
.customRow(
scalaVersions = Seq(scala212),
axisValues = Seq(config13, VirtualAxis.jvm),
_.settings(
moduleName := name.value + "_config1.3",
libraryDependencies += "com.typesafe" % "config" % "1.3.3"
)
)
lazy val appConfig12_212 = app.finder(config13, VirtualAxis.jvm)(scala212)
.settings(
publishMavenStyle := true
)

View File

@ -0,0 +1,5 @@
import sbt._
case class ConfigAxis(idSuffix: String, directorySuffix: String) extends VirtualAxis.WeakAxis {
}

View File

@ -0,0 +1,5 @@
sys.props.get("plugin.version") match {
case Some(x) => addSbtPlugin("com.eed3si9n" % "sbt-projectmatrix" % x)
case _ => sys.error("""|The system property 'plugin.version' is not defined.
|Specify this property using the scriptedLaunchOpts -D.""".stripMargin)
}

View File

@ -0,0 +1,4 @@
> publishLocal
$ exists ivy-cache/local/com.example/app_config1.2_2.12/0.1.0-SNAPSHOT/poms/app_config1.2_2.12.pom
$ exists ivy-cache/local/com.example/app_config1.3_2.12/0.1.0-SNAPSHOT/poms/app_config1.3_2.12.pom

View File

@ -0,0 +1,17 @@
// lazy val root = (project in file("."))
// .aggregate(core.projectRefs ++ app.projectRefs: _*)
// .settings(
// )
lazy val core = (projectMatrix in file("core"))
.settings(
name := "core"
)
.jsPlatform(scalaVersions = Seq("2.12.8", "2.11.12"))
lazy val app = (projectMatrix in file("app"))
.dependsOn(core)
.settings(
name := "app"
)
.jsPlatform(scalaVersions = Seq("2.12.8"))

View File

@ -0,0 +1,6 @@
package a
class Core {
}
object Core extends Core

View File

@ -0,0 +1,6 @@
sys.props.get("plugin.version") match {
case Some(x) => addSbtPlugin("com.eed3si9n" % "sbt-projectmatrix" % x)
case _ => sys.error("""|The system property 'plugin.version' is not defined.
|Specify this property using the scriptedLaunchOpts -D.""".stripMargin)
}
addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.27")

View File

@ -0,0 +1,5 @@
> fastOptJS
$ exists app/target/js-2.12/app-fastopt.js
$ exists core/target/js-2.12/core-fastopt.js
$ exists core/target/js-2.11/core-fastopt.js

View File

@ -0,0 +1,5 @@
package example
object D {
val x = C.x
}

View File

@ -0,0 +1,5 @@
package example
object C {
val x = 1
}

View File

@ -0,0 +1,5 @@
package example
object F {
val x = E.x
}

View File

@ -0,0 +1,5 @@
package example
object E {
val x = 1
}

View File

@ -0,0 +1,57 @@
lazy val check = taskKey[Unit]("")
lazy val scala3M1 = "3.0.0-M1"
lazy val scala3M2 = "3.0.0-M2"
lazy val scala213 = "2.13.4"
lazy val fooApp = (projectMatrix in file("foo-app"))
.dependsOn(fooCore)
.settings(
name := "foo app",
)
.jvmPlatform(scalaVersions = Seq(scala3M1, scala3M2))
lazy val fooApp3 = fooApp.jvm(scala3M1)
.settings(
test := { () },
)
lazy val fooCore = (projectMatrix in file("foo-core"))
.settings(
name := "foo core",
)
.jvmPlatform(scalaVersions = Seq(scala213, "2.12.12"))
lazy val barApp = (projectMatrix in file("bar-app"))
.dependsOn(barCore)
.settings(
name := "bar app",
)
.jvmPlatform(scalaVersions = Seq(scala213))
lazy val barCore = (projectMatrix in file("bar-core"))
.settings(
name := "bar core",
)
.jvmPlatform(scalaVersions = Seq(scala3M2))
// choose 2.13 when bazCore offers both 2.13 and Dotty
lazy val bazApp = (projectMatrix in file("baz-app"))
.dependsOn(bazCore)
.settings(
name := "baz app",
check := {
val cp = (Compile / fullClasspath).value
.map(_.data.getName)
assert(cp.contains("baz-core_2.13-0.1.0-SNAPSHOT.jar"), s"$cp")
assert(!cp.contains("baz-core_3.0.0-M1-0.1.0-SNAPSHOT.jar"), s"$cp")
},
)
.jvmPlatform(scalaVersions = Seq(scala213))
lazy val bazCore = (projectMatrix in file("baz-core"))
.settings(
name := "baz core",
exportJars := true,
)
.jvmPlatform(scalaVersions = Seq(scala213, scala3M1))

View File

@ -0,0 +1,5 @@
package example
object B {
val x = A.x
}

View File

@ -0,0 +1,5 @@
package example
object A {
val x = 1
}

View File

@ -0,0 +1 @@
sbt.version=1.4.2

View File

@ -0,0 +1,6 @@
sys.props.get("plugin.version") match {
case Some(x) => addSbtPlugin("com.eed3si9n" % "sbt-projectmatrix" % x)
case _ => sys.error("""|The system property 'plugin.version' is not defined.
|Specify this property using the scriptedLaunchOpts -D.""".stripMargin)
}
addSbtPlugin("ch.epfl.lamp" % "sbt-dotty" % "0.4.5")

View File

@ -0,0 +1,7 @@
> projects
> fooApp3_0_0_M1/compile
> barApp/compile
> bazApp/check

View File

@ -0,0 +1,5 @@
package example
object D {
val x = C.x
}

View File

@ -0,0 +1,5 @@
package example
object C {
val x = 1
}

View File

@ -0,0 +1,5 @@
package example
object F {
val x = E.x
}

View File

@ -0,0 +1,5 @@
package example
object E {
val x = 1
}

View File

@ -0,0 +1,54 @@
lazy val check = taskKey[Unit]("")
lazy val scala3M1 = "3.0.0-M1"
lazy val scala3M2 = "3.0.0-M2"
lazy val scala213 = "2.13.4"
lazy val fooApp = (projectMatrix in file("foo-app"))
.dependsOn(fooCore)
.settings(
name := "foo app",
)
.jvmPlatform(scalaVersions = Seq(scala3M1, scala3M2))
lazy val fooCore = (projectMatrix in file("foo-core"))
.settings(
name := "foo core",
)
.jvmPlatform(scalaVersions = Seq(scala213, "2.12.12"))
lazy val barApp = (projectMatrix in file("bar-app"))
.dependsOn(barCore)
.settings(
name := "bar app",
)
.jvmPlatform(scalaVersions = Seq(scala213))
lazy val barCore = (projectMatrix in file("bar-core"))
.settings(
name := "bar core",
)
.jvmPlatform(scalaVersions = Seq(scala3M1))
// choose 2.13 when bazCore offers both 2.13 and Dotty
lazy val bazApp = (projectMatrix in file("baz-app"))
.dependsOn(bazCore)
.settings(
name := "baz app",
check := {
val cp = (Compile / fullClasspath).value
.map(_.data.getName)
streams.value.log.info(cp.toString)
assert(cp.contains("baz-core_2.13-0.1.0-SNAPSHOT.jar"), s"$cp")
assert(!cp.contains("baz-core_3.0.0-M1-0.1.0-SNAPSHOT.jar"), s"$cp")
assert(projectMatrixBaseDirectory.value == file("baz-app"))
},
)
.jvmPlatform(scalaVersions = Seq(scala213))
lazy val bazCore = (projectMatrix in file("baz-core"))
.settings(
name := "baz core",
exportJars := true,
)
.jvmPlatform(scalaVersions = Seq(scala213, scala3M1, scala3M2))

View File

@ -0,0 +1,5 @@
package example
object B {
val x = A.x
}

View File

@ -0,0 +1,5 @@
package example
object A {
val x = 1
}

View File

@ -0,0 +1 @@
sbt.version=1.4.2

View File

@ -0,0 +1,6 @@
sys.props.get("plugin.version") match {
case Some(x) => addSbtPlugin("com.eed3si9n" % "sbt-projectmatrix" % x)
case _ => sys.error("""|The system property 'plugin.version' is not defined.
|Specify this property using the scriptedLaunchOpts -D.""".stripMargin)
}
addSbtPlugin("ch.epfl.lamp" % "sbt-dotty" % "0.4.5")

View File

@ -0,0 +1,7 @@
> projects
> fooApp3_0_0_M1/compile
> barApp/compile
> bazApp/check

View File

@ -0,0 +1,53 @@
import sbt.internal.ProjectMatrix
ThisBuild / organization := "com.example"
ThisBuild / version := "0.1.0-SNAPSHOT"
ThisBuild / publishMavenStyle := true
ThisBuild / ivyPaths := {
val base = (ThisBuild / baseDirectory).value
IvyPaths(base, Some(base / "ivy-cache"))
}
publish / skip := true
lazy val scala212 = "2.12.10"
lazy val check = taskKey[Unit]("")
lazy val config12 = ConfigAxis("Config1_2", "-config1.2")
lazy val config13 = ConfigAxis("Config1_3", "-config1.3")
check := {
val app12 = app.finder(config12).apply(autoScalaLibrary = false)
assert(app12.id == "appConfig1_2", s"app12.id is ${app12.id}")
val app13 = app.finder(config13).apply(autoScalaLibrary = false)
assert(app13.id == "appConfig1_3", s"app13.id is ${app13.id}")
}
lazy val app: ProjectMatrix = (projectMatrix in file("app"))
.settings(
name := "app",
ivyPaths := (ThisBuild / ivyPaths).value
)
.aggregate(domain)
.dependsOn(domain)
.customRow(
autoScalaLibrary = false,
scalaVersions = Seq(scala212),
axisValues = Seq(config12, VirtualAxis.jvm),
_.settings(
libraryDependencies += "com.typesafe" % "config" % "1.2.1"
)
)
.customRow(
autoScalaLibrary = false,
scalaVersions = Seq(scala212),
axisValues = Seq(config13, VirtualAxis.jvm),
_.settings(
libraryDependencies += "com.typesafe" % "config" % "1.3.3"
)
)
lazy val domain = (project in file("domain"))
.settings(
scalaVersion := scala212
)

View File

@ -0,0 +1,3 @@
package a
case class DataType()

View File

@ -0,0 +1,3 @@
import sbt._
case class ConfigAxis(idSuffix: String, directorySuffix: String) extends VirtualAxis.WeakAxis

View File

@ -0,0 +1,5 @@
sys.props.get("plugin.version") match {
case Some(x) => addSbtPlugin("com.eed3si9n" % "sbt-projectmatrix" % x)
case _ => sys.error("""|The system property 'plugin.version' is not defined.
|Specify this property using the scriptedLaunchOpts -D.""".stripMargin)
}

View File

@ -0,0 +1,5 @@
> compile
$ exists domain/target/scala-2.12/classes/a/DataType.class
> check

View File

@ -0,0 +1,5 @@
package a
object Main extends App {
val core = Core
}

View File

@ -0,0 +1,44 @@
lazy val scala213 = "2.13.3"
lazy val scala212 = "2.12.12"
lazy val check = taskKey[Unit]("")
lazy val root = (project in file("."))
.aggregate(core.projectRefs ++ app.projectRefs: _*)
.settings(
)
lazy val app = (projectMatrix in file("app"))
.aggregate(core, intf)
.dependsOn(core % Compile, intf % "compile->compile;test->test")
.settings(
name := "app"
)
.jvmPlatform(scalaVersions = Seq(scala213))
lazy val core = (projectMatrix in file("core"))
.settings(
check := {
assert(moduleName.value == "core", s"moduleName is ${moduleName.value}")
val directs = libraryDependencies.value
assert(directs.size == 2, s"$directs")
},
)
.jvmPlatform(scalaVersions = Seq(scala213, scala212))
.configure(addStuff)
lazy val intf = (projectMatrix in file("intf"))
.settings(
check := {
assert(moduleName.value == "intf", s"moduleName is ${moduleName.value}")
},
)
.jvmPlatform(autoScalaLibrary = false)
lazy val core213 = core.jvm(scala213)
def addStuff(p: Project): Project = {
p.settings(
libraryDependencies += "junit" % "junit" % "4.12" % Test
)
}

View File

@ -0,0 +1,6 @@
package a
class Core {
}
object Core extends Core

View File

@ -0,0 +1,5 @@
sys.props.get("plugin.version") match {
case Some(x) => addSbtPlugin("com.eed3si9n" % "sbt-projectmatrix" % x)
case _ => sys.error("""|The system property 'plugin.version' is not defined.
|Specify this property using the scriptedLaunchOpts -D.""".stripMargin)
}

View File

@ -0,0 +1,6 @@
> compile
$ exists core/target/jvm-2.13/classes/a/Core.class
$ exists core/target/jvm-2.12/classes/a/Core.class
> core/check

View File

@ -0,0 +1,36 @@
lazy val scala213 = "2.13.3"
lazy val scala212 = "2.12.12"
lazy val check = taskKey[Unit]("")
lazy val root = (project in file("."))
.aggregate(core.projectRefs ++ app.projectRefs: _*)
.settings(
)
lazy val app = (projectMatrix in file("app"))
.aggregate(core, intf)
.dependsOn(core, intf)
.settings(
name := "app"
)
.jvmPlatform(scalaVersions = Seq(scala213))
lazy val core = (projectMatrix in file("core"))
.settings(
check := {
assert(moduleName.value == "core", s"moduleName is ${moduleName.value}")
assert(projectMatrixBaseDirectory.value == file("core"))
},
)
.jvmPlatform(scalaVersions = Seq(scala213, scala212))
lazy val intf = (projectMatrix in file("intf"))
.settings(
check := {
assert(moduleName.value == "intf", s"moduleName is ${moduleName.value}")
assert(projectMatrixBaseDirectory.value == file("intf"))
},
)
.jvmPlatform(autoScalaLibrary = false)
lazy val core213 = core.jvm(scala213)

View File

@ -0,0 +1,6 @@
package a
class Core {
}
object Core extends Core

View File

@ -0,0 +1,5 @@
sys.props.get("plugin.version") match {
case Some(x) => addSbtPlugin("com.eed3si9n" % "sbt-projectmatrix" % x)
case _ => sys.error("""|The system property 'plugin.version' is not defined.
|Specify this property using the scriptedLaunchOpts -D.""".stripMargin)
}

View File

@ -0,0 +1,6 @@
> compile
$ exists core/target/jvm-2.13/classes/a/Core.class
$ exists core/target/jvm-2.12/classes/a/Core.class
> core/check

View File

@ -0,0 +1,15 @@
ThisBuild / version := "0.1.0-SNAPSHOT"
lazy val util = projectMatrix
.jvmPlatform(scalaVersions = Seq("2.12.19", "2.13.13"))
lazy val root = (projectMatrix in file("."))
.dependsOn(util)
.jvmPlatform(scalaVersions = Seq("2.12.19"))
// ss is second system
lazy val ss = projectMatrix
.dependsOn(util)
.jvmPlatform(scalaVersions = Seq("2.13.13"))
lazy val strayJar = project

View File

@ -0,0 +1,5 @@
sys.props.get("plugin.version") match {
case Some(x) => addSbtPlugin("com.eed3si9n" % "sbt-projectmatrix" % x)
case _ => sys.error("""|The system property 'plugin.version' is not defined.
|Specify this property using the scriptedLaunchOpts -D.""".stripMargin)
}

View File

@ -0,0 +1,3 @@
object Main extends App {
println(com.example.Lib.getMessage)
}

View File

@ -0,0 +1,3 @@
object Main extends App {
println(com.example.Lib.getMessage)
}

View File

@ -0,0 +1,7 @@
package com.example;
public class Lib {
public static String getMessage() {
return "Hello, World!";
}
}

View File

@ -0,0 +1,15 @@
# debug
#> unmanagedBase
# make jar
> strayJar/package
# check root
$ mkdir lib
$ copy-file strayJar/target/scala-2.12/strayjar_2.12-0.1.0-SNAPSHOT.jar lib/strayJar.jar
> root2_12/compile
# check ss
$ mkdir ss/lib
$ copy-file strayJar/target/scala-2.12/strayjar_2.12-0.1.0-SNAPSHOT.jar ss/lib/strayJar.jar
> ss/compile

View File

@ -0,0 +1,8 @@
package a
object App {
def main(args: Array[String]): Unit = {
val a = new Core
println(s"Hello, world! ${a}")
}
}

View File

@ -0,0 +1,19 @@
// lazy val root = (project in file("."))
// .aggregate(core.projectRefs ++ app.projectRefs: _*)
// .settings(
// )
lazy val core = (projectMatrix in file("core"))
.settings(
name := "core",
mainClass in (Compile, run) := Some("a.CoreMain")
)
.nativePlatform(scalaVersions = Seq("2.11.12"))
lazy val app = (projectMatrix in file("app"))
.dependsOn(core)
.settings(
name := "app",
mainClass in (Compile, run) := Some("a.App")
)
.nativePlatform(scalaVersions = Seq("2.11.12"))

View File

@ -0,0 +1,6 @@
package a
class Core {
}
object Core extends Core

View File

@ -0,0 +1,6 @@
package a
object CoreMain {
def main(args: Array[String]): Unit = {
}
}

View File

@ -0,0 +1 @@
sbt.version = 1.4.3

View File

@ -0,0 +1,6 @@
sys.props.get("plugin.version") match {
case Some(x) => addSbtPlugin("com.eed3si9n" % "sbt-projectmatrix" % x)
case _ => sys.error("""|The system property 'plugin.version' is not defined.
|Specify this property using the scriptedLaunchOpts -D.""".stripMargin)
}
addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.4.0")

View File

@ -0,0 +1,4 @@
> nativeLink
$ exists app/target/native-2.11/app-out
$ exists core/target/native-2.11/core-out

View File

@ -0,0 +1,56 @@
lazy val scala213 = "2.13.3"
lazy val scala212 = "2.12.12"
lazy val check = taskKey[Unit]("")
lazy val platformTest = settingKey[String]("")
lazy val configTest = settingKey[String]("")
lazy val config12 = ConfigAxis("Config1_2", "config1.2")
lazy val config13 = ConfigAxis("Config1_3", "config1.3")
lazy val root = (project in file("."))
.aggregate((core.projectRefs ++ custom.projectRefs):_*)
lazy val core = (projectMatrix in file("core"))
.settings(
check := {
assert(platformTest.value.endsWith("-platform"))
assert(projectMatrixBaseDirectory.value == file("core"))
},
)
.jvmPlatform(scalaVersions = Seq(scala213, scala212))
.jsPlatform(scalaVersions = Seq(scala212))
.settings(platformSettings)
lazy val custom =
(projectMatrix in file("custom"))
.customRow(
scalaVersions = Seq(scala212),
axisValues = Seq(config13, VirtualAxis.jvm),
_.settings()
)
.settings(platformSettings)
.settings(customSettings)
.settings(
check := {
assert(platformTest.value.endsWith("-platform"))
assert(configTest.value.startsWith("config for"))
}
)
lazy val platformSettings = Seq[Def.Setting[_]](
platformTest := {
if(virtualAxes.value.contains(sbt.VirtualAxis.js)) "js-platform"
else if(virtualAxes.value.contains(sbt.VirtualAxis.jvm)) "jvm-platform"
else throw new RuntimeException(s"Something must be wrong (built-in platforms test) - virtualAxes value is ${virtualAxes.value}")
}
)
lazy val customSettings = Seq[Def.Setting[_]](
configTest := {
if(virtualAxes.value.contains(config12)) "config for 1.2"
else if (virtualAxes.value.contains(config13)) "config for 1.3"
else throw new RuntimeException(s"Something must be wrong (custom axis test ) - virtualAxes value is ${virtualAxes.value}")
}
)

View File

@ -0,0 +1,3 @@
import sbt._
case class ConfigAxis(idSuffix: String, directorySuffix: String) extends VirtualAxis.WeakAxis

View File

@ -0,0 +1,7 @@
sys.props.get("plugin.version") match {
case Some(x) => addSbtPlugin("com.eed3si9n" % "sbt-projectmatrix" % x)
case _ => sys.error("""|The system property 'plugin.version' is not defined.
|Specify this property using the scriptedLaunchOpts -D.""".stripMargin)
}
addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.3.0")

View File

@ -0,0 +1,2 @@
> compile
> check