-
Notifications
You must be signed in to change notification settings - Fork 49
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add CI command alias derived from the sbt-github-actions matrix #210
base: series/0.4
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||
---|---|---|---|---|
|
@@ -16,11 +16,11 @@ | |||
|
||||
package org.typelevel.sbt | ||||
|
||||
import sbt._ | ||||
import com.typesafe.tools.mima.plugin.MimaPlugin | ||||
import org.typelevel.sbt.gha.GenerativePlugin | ||||
import org.typelevel.sbt.gha.GitHubActionsPlugin | ||||
import org.typelevel.sbt.gha.GenerativePlugin.autoImport._ | ||||
import com.typesafe.tools.mima.plugin.MimaPlugin | ||||
import org.typelevel.sbt.gha.GitHubActionsPlugin | ||||
import sbt._ | ||||
|
||||
object TypelevelCiPlugin extends AutoPlugin { | ||||
|
||||
|
@@ -46,12 +46,101 @@ object TypelevelCiPlugin extends AutoPlugin { | |||
cond = Some(primaryJavaCond.value) | ||||
) | ||||
), | ||||
githubWorkflowJavaVersions := Seq(JavaSpec.temurin("8")) | ||||
githubWorkflowJavaVersions := Seq(JavaSpec.temurin("8")), | ||||
GlobalScope / Keys.onLoad := { | ||||
val oses = githubWorkflowOSes.value.toList | ||||
val javas = githubWorkflowJavaVersions.value.toList | ||||
val scalas = githubWorkflowScalaVersions.value.toList | ||||
val additions = githubWorkflowBuildMatrixAdditions.value | ||||
val inclusions = githubWorkflowBuildMatrixInclusions.value.toList | ||||
val exclusions = githubWorkflowBuildMatrixExclusions.value.toList | ||||
val stepPreamble = githubWorkflowBuildSbtStepPreamble.value.toList | ||||
|
||||
(GlobalScope / Keys.onLoad).value.compose { (state: State) => | ||||
addCiAlias( | ||||
state, | ||||
oses, | ||||
javas, | ||||
scalas, | ||||
additions, | ||||
inclusions, | ||||
exclusions, | ||||
stepPreamble, | ||||
githubWorkflowBuild.value | ||||
) | ||||
} | ||||
}, | ||||
GlobalScope / Keys.onUnload := { | ||||
(GlobalScope / Keys.onUnload) | ||||
.value | ||||
.compose((state: State) => BasicCommands.removeAlias(state, "ci")) | ||||
} | ||||
) | ||||
|
||||
private val primaryJavaCond = Def.setting { | ||||
val java = githubWorkflowJavaVersions.value.head | ||||
s"matrix.java == '${java.render}'" | ||||
} | ||||
|
||||
private def addCiAlias( | ||||
state: State, | ||||
oses: List[String], | ||||
javaVersions: List[JavaSpec], | ||||
scalaVersions: List[String], | ||||
matrixAdditions: Map[String, List[String]], | ||||
matrixInclusions: List[MatrixInclude], | ||||
matrixExclusions: List[MatrixExclude], | ||||
sbtStepPreamble: List[String], | ||||
workflowSteps: Seq[WorkflowStep]) = { | ||||
|
||||
val buildMatrix = GenerativePlugin.expandMatrix( | ||||
// Cannot meaningfully iterate OS or Java version here | ||||
oses = oses.take(1), | ||||
javas = javaVersions.take(1), | ||||
scalas = scalaVersions, | ||||
matrixAdds = matrixAdditions, | ||||
includes = matrixInclusions, | ||||
excludes = matrixExclusions | ||||
) | ||||
|
||||
val keys = "os" :: "scala" :: "java" :: matrixAdditions.keys.toList.sorted | ||||
|
||||
val commands = for { | ||||
matrixRow <- buildMatrix | ||||
matrixValues = keys.zip(matrixRow).toMap | ||||
step <- workflowSteps.collect { | ||||
case sbt: WorkflowStep.Sbt if matchingCondition(sbt, matrixValues) => sbt | ||||
} | ||||
command <- sbtStepPreamble ++ step.commands | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. so one of the annoying things is that sbt is stateful. So I wonder if we need to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm I think that should be OK - from what I understand There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For sure! Except when these steps run in CI it's not part of a single interactive session—each workflow step reboots sbt essentially. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ahhhh I see, so you are concerned about potential session value leakage in some of the existing commands. FWIW I think interspersing There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Remind me not to try replying to comments on my phone, who knows how this ended up here! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Oh, absolutely agree! But the if the goal is to faithfully replicate CI then I really don't see another way. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm, what if we do There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh yeah, that's probably just as good. Not sure what |
||||
} yield replaceMatrixVars(command, matrixValues) | ||||
|
||||
val commandAlias = TypelevelKernelPlugin.mkCommand(commands) | ||||
|
||||
BasicCommands.addAlias( | ||||
state, | ||||
"ci", | ||||
commandAlias | ||||
) | ||||
} | ||||
|
||||
private def matchingCondition( | ||||
command: WorkflowStep.Sbt, | ||||
matrixValues: Map[String, String]) = { | ||||
// For all matrix values | ||||
matrixValues.forall { | ||||
case (k, v) => | ||||
val renderedCond = s"matrix.$k == '$v'" | ||||
|
||||
command.cond.forall { cond => | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it likely that the condition itself might refer to a matrix variable? Perhaps There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, that's very likely. In fact we do it from sbt-typelevel itself.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah that should work OK - what I was worried about was things like Some("something == '${{ matrix.project }}'") In that scenario we would need to make sure to replace any Github Actions There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm, I might be confused but in conditions you should assume everything is wrapped in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. https://docs.github.com/en/actions/learn-github-actions/expressions
|
||||
// If the condition starts with this matrix variable, whole condition must be equal | ||||
if (cond.startsWith(s"matrix.$k")) cond == renderedCond else true | ||||
} | ||||
} | ||||
} | ||||
|
||||
private def replaceMatrixVars(command: String, matrixValues: Map[String, String]): String = | ||||
matrixValues.foldLeft(command) { | ||||
case (cmd, (matrixVar, matrixVarValue)) => | ||||
cmd.replaceAll(s"\\$$\\{\\{ matrix.${matrixVar} \\}\\}", matrixVarValue) | ||||
} | ||||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I actually don't know what these
GlobalScope / Keys.onLoad
incantations are for? :)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Those are sbt's global load hooks - they add the alias when all of the project's keys have loaded and remove it when unloading the projects. Setting up these specific hooks is what
addCommandAlias
does under the hood. If you try to useonLoad
in the default scope you get 'reference to undefined setting' unfortunately.