Skip to content

Commit

Permalink
Added preliminary support for matrix include/exclude
Browse files Browse the repository at this point in the history
  • Loading branch information
djspiewak committed Jun 22, 2020
1 parent 3c40ebb commit 7fe4eda
Show file tree
Hide file tree
Showing 9 changed files with 248 additions and 5 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ Any and all settings which affect the behavior of the generative plugin should b
#### `build` Job

- `githubWorkflowBuildMatrixAdditions` : `Map[String, List[String]]` – Contains a map of additional `matrix:` dimensions which will be added to the `build` job (on top of the auto-generated ones for Scala/Java/JVM version). As an example, this can be used to manually achieve additional matrix expansion for ScalaJS compilation. Matrix variables can be referenced in the conventional way within steps by using the `${{ matrix.keynamehere }}` syntax. Defaults to empty.
- `githubWorkflowBuildMatrixInclusions` : `Seq[MatrixInclude]` – A list of [matrix *inclusions*](https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#example-including-additional-values-into-combinations). This is useful for when you have a specific matrix job which needs to do extra work, or wish to add an individual matrix job to the configuration set. The matching keys and values are verified against the known matrix configuration. Defaults to empty.
- `githubWorkflowBuildMatrixExclusions` : `Seq[MatrixExclude]` – A list of [matrix *exclusions*](https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#example-excluding-configurations-from-a-matrix). This is useful for when there is a matrix expansion (or set of expansions) which you wish to filter out of the set. Note that exclusions are applied *before* inclusions, allowing you to subtract jobs before re-adding them. Also – and the documentation isn't clear on this point – it is possible that the matching must cover the full set of matrix keys and cannot contain partial values. Defaults to empty.
- `githubWorkflowBuildPreamble` : `Seq[WorkflowStep]` – Contains a list of steps which will be inserted into the `build` job in the **ci.yml** workflow *after* setup but *before* the `sbt test` invocation. Defaults to empty.
- `githubWorkflowBuildPostamble` : `Seq[WorkflowStep]` – Similar to the `Preamble` variant, this contains a list of steps which will be added to the `build` job *after* the `sbt test` invocation but before cleanup. Defaults to empty.
- `githubWorkflowBuild` : `Seq[WorkflowStep]` – The steps which invoke sbt (or whatever else you want) to build and test your project. This defaults to just `[sbt test]`, but can be overridden to anything. For example, sbt plugin projects probably want to redefine this to be `Seq(WorkflowStep.Sbt(List("test", "scripted")))`, which would run the `test` and `scripted` sbt tasks, in order. Note that all uses of `WorkflowStep.Sbt` are compiled using the configured `githubWorkflowSbtCommand` invocation, and properly configured with respect to the build matrix-selected Scala version.
Expand Down
2 changes: 1 addition & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

name := "sbt-github-actions"

ThisBuild / baseVersion := "0.7"
ThisBuild / baseVersion := "0.8"

ThisBuild / organization := "com.codecommit"
ThisBuild / publishGithubUser := "djspiewak"
Expand Down
3 changes: 3 additions & 0 deletions src/main/scala/sbtghactions/GenerativeKeys.scala
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ trait GenerativeKeys {
lazy val githubWorkflowSbtCommand = settingKey[String]("The command which invokes sbt (default: sbt")

lazy val githubWorkflowBuildMatrixAdditions = settingKey[Map[String, List[String]]]("A map of additional matrix dimensions for the build job. Each list should be non-empty. (default: {})")
lazy val githubWorkflowBuildMatrixInclusions = settingKey[Seq[MatrixInclude]]("A list of matrix inclusions (default: [])")
lazy val githubWorkflowBuildMatrixExclusions = settingKey[Seq[MatrixExclude]]("A list of matrix exclusions (default: [])")

lazy val githubWorkflowBuildPreamble = settingKey[Seq[WorkflowStep]]("A list of steps to insert after base setup but before compiling and testing (default: [])")
lazy val githubWorkflowBuildPostamble = settingKey[Seq[WorkflowStep]]("A list of steps to insert after comping and testing but before the end of the build job (default: [])")
lazy val githubWorkflowBuild = settingKey[Seq[WorkflowStep]]("A sequence of workflow steps which compile and test the project (default: [Sbt(List(\"test\"))])")
Expand Down
78 changes: 75 additions & 3 deletions src/main/scala/sbtghactions/GenerativePlugin.scala
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ object GenerativePlugin extends AutoPlugin {

type PREventType = sbtghactions.PREventType
val PREventType = sbtghactions.PREventType

type MatrixInclude = sbtghactions.MatrixInclude
val MatrixInclude = sbtghactions.MatrixInclude

type MatrixExclude = sbtghactions.MatrixExclude
val MatrixExclude = sbtghactions.MatrixExclude
}

import autoImport._
Expand Down Expand Up @@ -88,6 +94,15 @@ object GenerativePlugin extends AutoPlugin {
"\n" + indent(rendered.map("- " + _).mkString("\n"), level)
}

def compileListOfSimpleDicts(items: List[Map[String, String]]): String =
items map { dict =>
val rendered = dict map {
case (key, value) => s"$key: $value"
} mkString "\n"

"-" + indent(rendered, 1).substring(1)
} mkString "\n"

def compilePREventType(tpe: PREventType): String = {
import PREventType._

Expand Down Expand Up @@ -211,14 +226,66 @@ ${indent(rendered.mkString("\n"), 1)}"""
else
"\n" + renderedEnvPre

List("include", "exclude") foreach { key =>
if (job.matrixAdds.contains(key)) {
sys.error(s"key `$key` is reserved and cannot be used in an Actions matrix definition")
}
}

val renderedMatricesPre = job.matrixAdds map {
case (key, values) => s"$key: ${values.map(wrap).mkString("[", ", ", "]")}"
} mkString "\n"

val renderedMatrices = if (renderedMatricesPre.isEmpty)
// TODO refactor all of this stuff to use whitelist instead
val whitelist = Map("os" -> job.oses, "scala" -> job.scalas, "java" -> job.javas) ++ job.matrixAdds

def checkMatching(matching: Map[String, String]): Unit = {
matching foreach {
case (key, value) =>
if (!whitelist.contains(key)) {
sys.error(s"inclusion key `$key` was not found in matrix")
}

if (!whitelist(key).contains(value)) {
sys.error(s"inclusion key `$key` was present in matrix, but value `$value` was not in ${whitelist(key)}")
}
}
}

val renderedIncludesPre = if (job.matrixIncs.isEmpty) {
renderedMatricesPre
} else {
job.matrixIncs.foreach(inc => checkMatching(inc.matching))

val rendered = compileListOfSimpleDicts(job.matrixIncs.map(i => i.matching ++ i.additions))

val renderedMatrices = if (renderedMatricesPre.isEmpty)
""
else
renderedMatricesPre + "\n"

s"${renderedMatrices}include:\n${indent(rendered, 1)}"
}

val renderedExcludesPre = if (job.matrixExcs.isEmpty) {
renderedIncludesPre
} else {
job.matrixExcs.foreach(exc => checkMatching(exc.matching))

val rendered = compileListOfSimpleDicts(job.matrixExcs.map(_.matching))

val renderedIncludes = if (renderedIncludesPre.isEmpty)
""
else
renderedIncludesPre + "\n"

s"${renderedIncludes}exclude:\n${indent(rendered, 1)}"
}

val renderedMatrices = if (renderedExcludesPre.isEmpty)
""
else
"\n" + indent(renderedMatricesPre, 2)
"\n" + indent(renderedExcludesPre, 2)

val declareShell = job.oses.exists(_.contains("windows"))

Expand Down Expand Up @@ -279,6 +346,9 @@ ${indent(jobs.map(compileJob(_, sbt)).mkString("\n\n"), 1)}"""
githubWorkflowSbtCommand := "sbt",

githubWorkflowBuildMatrixAdditions := Map(),
githubWorkflowBuildMatrixInclusions := Seq(),
githubWorkflowBuildMatrixExclusions := Seq(),

githubWorkflowBuildPreamble := Seq(),
githubWorkflowBuildPostamble := Seq(),
githubWorkflowBuild := Seq(WorkflowStep.Sbt(List("test"), name = Some("Build project"))),
Expand Down Expand Up @@ -558,7 +628,9 @@ git config --global alias.rm-symlink '!git rm-symlinks' # for back-compat."""
oses = githubWorkflowOSes.value.toList,
scalas = crossScalaVersions.value.toList,
javas = githubWorkflowJavaVersions.value.toList,
matrixAdds = githubWorkflowBuildMatrixAdditions.value)) ++ publishJobOpt ++ githubWorkflowAddedJobs.value
matrixAdds = githubWorkflowBuildMatrixAdditions.value,
matrixIncs = githubWorkflowBuildMatrixInclusions.value.toList,
matrixExcs = githubWorkflowBuildMatrixExclusions.value.toList)) ++ publishJobOpt ++ githubWorkflowAddedJobs.value
})

private val generateCiContents = Def task {
Expand Down
4 changes: 3 additions & 1 deletion src/main/scala/sbtghactions/WorkflowJob.scala
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,6 @@ final case class WorkflowJob(
scalas: List[String] = List("2.13.1"),
javas: List[String] = List("[email protected]"),
needs: List[String] = Nil,
matrixAdds: Map[String, List[String]] = Map())
matrixAdds: Map[String, List[String]] = Map(),
matrixIncs: List[MatrixInclude] = List(),
matrixExcs: List[MatrixExclude] = List())
23 changes: 23 additions & 0 deletions src/main/scala/sbtghactions/matrix.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Copyright 2020 Daniel Spiewak
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package sbtghactions

final case class MatrixInclude(
matching: Map[String, String],
additions: Map[String, String])

final case class MatrixExclude(matching: Map[String, String])
7 changes: 7 additions & 0 deletions src/sbt-test/sbtghactions/check-and-regenerate/build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,12 @@ ThisBuild / githubWorkflowPublishTargetBranches += RefPredicate.Equals(Ref.Tag("

ThisBuild / githubWorkflowBuildMatrixAdditions += "test" -> List("this", "is")

ThisBuild / githubWorkflowBuildMatrixInclusions += MatrixInclude(
Map("test" -> "this"),
Map("extra" -> "sparta"))

ThisBuild / githubWorkflowBuildMatrixExclusions +=
MatrixExclude(Map("scala" -> "2.12.10", "test" -> "is"))

ThisBuild / githubWorkflowBuild += WorkflowStep.Run(List("echo yo"))
ThisBuild / githubWorkflowPublish += WorkflowStep.Run(List("echo sup"))
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ jobs:
scala: [2.13.1, 2.12.10]
java: [[email protected], [email protected]]
test: [this, is]
include:
- test: this
extra: sparta
exclude:
- scala: 2.12.10
test: is
runs-on: ${{ matrix.os }}
steps:
- name: Checkout current branch (fast)
Expand Down
128 changes: 128 additions & 0 deletions src/test/scala/sbtghactions/GenerativePluginSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,134 @@ class GenerativePluginSpec extends Specification {
uses: actions/checkout@v2"""
}

"produce an error when compiling a job with `include` key in matrix" in {
compileJob(
WorkflowJob(
"bippy",
"Bippity Bop Around the Clock",
List(),
matrixAdds = Map("include" -> List("1", "2"))),
"") must throwA[RuntimeException]
}

"produce an error when compiling a job with `exclude` key in matrix" in {
compileJob(
WorkflowJob(
"bippy",
"Bippity Bop Around the Clock",
List(),
matrixAdds = Map("exclude" -> List("1", "2"))),
"") must throwA[RuntimeException]
}

"compile a job with a simple matching inclusion" in {
val results = compileJob(
WorkflowJob(
"bippy",
"Bippity Bop Around the Clock",
List(
WorkflowStep.Run(List("echo ${{ matrix.scala }}"))),
matrixIncs = List(
MatrixInclude(
Map("scala" -> "2.13.1"),
Map("foo" -> "bar")))),
"")

results mustEqual s"""bippy:
name: Bippity Bop Around the Clock
strategy:
matrix:
os: [ubuntu-latest]
scala: [2.13.1]
java: [[email protected]]
include:
- scala: 2.13.1
foo: bar
runs-on: $${{ matrix.os }}
steps:
- run: echo $${{ matrix.scala }}"""
}

"produce an error with a non-matching inclusion key" in {
compileJob(
WorkflowJob(
"bippy",
"Bippity Bop Around the Clock",
List(
WorkflowStep.Run(List("echo ${{ matrix.scala }}"))),
matrixIncs = List(
MatrixInclude(
Map("scalanot" -> "2.13.1"),
Map("foo" -> "bar")))),
"") must throwA[RuntimeException]
}

"produce an error with a non-matching inclusion value" in {
compileJob(
WorkflowJob(
"bippy",
"Bippity Bop Around the Clock",
List(
WorkflowStep.Run(List("echo ${{ matrix.scala }}"))),
matrixIncs = List(
MatrixInclude(
Map("scala" -> "0.12.1"),
Map("foo" -> "bar")))),
"") must throwA[RuntimeException]
}

"compile a job with a simple matching exclusion" in {
val results = compileJob(
WorkflowJob(
"bippy",
"Bippity Bop Around the Clock",
List(
WorkflowStep.Run(List("echo ${{ matrix.scala }}"))),
matrixExcs = List(
MatrixExclude(
Map("scala" -> "2.13.1")))),
"")

results mustEqual s"""bippy:
name: Bippity Bop Around the Clock
strategy:
matrix:
os: [ubuntu-latest]
scala: [2.13.1]
java: [[email protected]]
exclude:
- scala: 2.13.1
runs-on: $${{ matrix.os }}
steps:
- run: echo $${{ matrix.scala }}"""
}

"produce an error with a non-matching exclusion key" in {
compileJob(
WorkflowJob(
"bippy",
"Bippity Bop Around the Clock",
List(
WorkflowStep.Run(List("echo ${{ matrix.scala }}"))),
matrixExcs = List(
MatrixExclude(
Map("scalanot" -> "2.13.1")))),
"") must throwA[RuntimeException]
}

"produce an error with a non-matching exclusion value" in {
compileJob(
WorkflowJob(
"bippy",
"Bippity Bop Around the Clock",
List(
WorkflowStep.Run(List("echo ${{ matrix.scala }}"))),
matrixExcs = List(
MatrixExclude(
Map("scala" -> "0.12.1")))),
"") must throwA[RuntimeException]
}

"compile a job with illegal characters in the JVM" in {
val results = compileJob(
WorkflowJob(
Expand Down

0 comments on commit 7fe4eda

Please sign in to comment.