Skip to content

Commit

Permalink
Merge pull request #98 from povder/timeout
Browse files Browse the repository at this point in the history
Support timeout for jobs and steps
  • Loading branch information
mdedetrich authored Mar 14, 2023
2 parents 2e645d7 + d12d83b commit ba5bcc8
Show file tree
Hide file tree
Showing 9 changed files with 73 additions and 9 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ Any and all settings which affect the behavior of the generative plugin should b
- `githubWorkflowScalaVersions` : `Seq[String]` – A list of Scala versions which will be used to `build` your project. Defaults to `crossScalaVersions` in `build`, and simply `scalaVersion` in `publish`.
- `githubWorkflowOSes` : `Seq[String]` – A list of operating systems, which will be ultimately passed to [the `runs-on:` directive](https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on), on which to `build` your project. Defaults to `ubuntu-latest`. Note that, regardless of the value of this setting, only `ubuntu-latest` will be used for the `publish` job. This setting only affects `build`.
- `githubWorkflowBuildRunsOnExtraLabels` : `Seq[String]` - A list of additional runs-on labels, which will be combined with the matrix.os from `githubWorkflowOSes` above allowing for singling out more specific runners.
- `githubWorkflowBuildTimeout` : `Option[FiniteDuration]` - [The maximum duration](https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions#jobsjob_idtimeout-minutes) to let the build job run before GitHub automatically cancels it. Defaults to `None`.

#### `publish` Job

Expand All @@ -139,6 +140,7 @@ Any and all settings which affect the behavior of the generative plugin should b
- `githubWorkflowPublish` : `Seq[WorkflowStep]` – The steps which will be invoked to publish your project. This defaults to `[sbt +publish]`.
- `githubWorkflowPublishTargetBranches` : `Seq[RefPredicate]` – A list of branch predicates which will be applied to determine whether the `publish` job will run. Defaults to just `== main`. The supports all of the predicate types currently [allowed by GitHub Actions](https://help.github.com/en/actions/reference/context-and-expression-syntax-for-github-actions#functions). This exists because, while you usually want to run the `build` job on *every* branch, `publish` is obviously much more limited in applicability. If this list is empty, then the `publish` job will be omitted entirely from the workflow.
- `githubWorkflowPublishCond` : `Option[String]` – This is an optional added conditional check on the publish branch, which must be defined using [GitHub Actions expression syntax](https://help.github.com/en/actions/reference/context-and-expression-syntax-for-github-actions#about-contexts-and-expressions), which will be conjoined to determine the `if:` predicate on the `publish` job. Defaults to `None`.
- `githubWorkflowPublishTimeout` : `Option[FiniteDuration]` - [The maximum duration](https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions#jobsjob_idtimeout-minutes) to let the publish job run before GitHub automatically cancels it. Defaults to `None`.

#### Windows related

Expand Down
3 changes: 2 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@ pluginCrossBuild / sbtVersion := "1.5.5"

publishMavenStyle := true

scalacOptions += "-Xlint:_,-missing-interpolator"
scalacOptions +=
"-Xlint:_,-missing-interpolator"

libraryDependencies += "org.specs2" %% "specs2-core" % "4.12.12" % Test

Expand Down
4 changes: 4 additions & 0 deletions src/main/scala/sbtghactions/GenerativeKeys.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ package sbtghactions

import sbt._

import scala.concurrent.duration.FiniteDuration

trait GenerativeKeys {

lazy val githubWorkflowGenerate = taskKey[Unit]("Generates (and overwrites if extant) a ci.yml and clean.yml actions description according to configuration")
Expand All @@ -41,6 +43,7 @@ trait GenerativeKeys {
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 githubWorkflowBuildSbtStepPreamble =settingKey[Seq[String]](s"Commands automatically prepended to a WorkflowStep.Sbt (default: ['++$${{ matrix.scala }}'])")
lazy val githubWorkflowBuildTimeout = settingKey[Option[FiniteDuration]]("The maximum duration to let the build job run before GitHub automatically cancels it. (default: None)")
lazy val githubWorkflowBuild = settingKey[Seq[WorkflowStep]]("A sequence of workflow steps which compile and test the project (default: [Sbt(List(\"test\"))])")

lazy val githubWorkflowPublishPreamble = settingKey[Seq[WorkflowStep]]("A list of steps to insert after base setup but before publishing (default: [])")
Expand All @@ -49,6 +52,7 @@ trait GenerativeKeys {
lazy val githubWorkflowPublish = settingKey[Seq[WorkflowStep]]("A sequence workflow steps which publishes the project (default: [Sbt(List(\"+publish\"))])")
lazy val githubWorkflowPublishTargetBranches = settingKey[Seq[RefPredicate]]("A set of branch predicates which will be applied to determine whether the current branch gets a publication stage; if empty, publish will be skipped entirely (default: [== main])")
lazy val githubWorkflowPublishCond = settingKey[Option[String]]("A set of conditionals to apply to the publish job to further restrict its run (default: [])")
lazy val githubWorkflowPublishTimeout = settingKey[Option[FiniteDuration]]("The maximum duration to let the publish job run before GitHub automatically cancels it. (default: None)")

lazy val githubWorkflowJavaVersions = settingKey[Seq[JavaSpec]]("A list of Java versions to be used for the build job. The publish job will use the *first* of these versions. (default: [temurin@11])")
lazy val githubWorkflowScalaVersions = settingKey[Seq[String]]("A list of Scala versions on which to build the project (default: crossScalaVersions.value)")
Expand Down
19 changes: 15 additions & 4 deletions src/main/scala/sbtghactions/GenerativePlugin.scala
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import sbt.Keys._
import sbt._

import java.nio.file.FileSystems
import scala.concurrent.duration.FiniteDuration
import scala.io.Source

object GenerativePlugin extends AutoPlugin {
Expand Down Expand Up @@ -228,6 +229,10 @@ ${indent(rendered.mkString("\n"), 1)}"""
}
}

def compileTimeout(timeout: Option[FiniteDuration], prefix: String = ""): String = {
timeout.map(_.toMinutes.toString).map(s"${prefix}timeout-minutes: " + _ + "\n").getOrElse("")
}

def compileStep(
step: WorkflowStep,
sbt: String,
Expand All @@ -240,14 +245,15 @@ ${indent(rendered.mkString("\n"), 1)}"""
val renderedId = step.id.map(wrap).map("id: " + _ + "\n").getOrElse("")
val renderedCond = step.cond.map(wrap).map("if: " + _ + "\n").getOrElse("")
val renderedShell = if (declareShell) "shell: bash\n" else ""
val renderedTimeout = compileTimeout(step.timeout)

val renderedEnvPre = compileEnv(step.env)
val renderedEnv = if (renderedEnvPre.isEmpty)
""
else
renderedEnvPre + "\n"

val preamblePre = renderedName + renderedId + renderedCond + renderedEnv
val preamblePre = renderedName + renderedId + renderedCond + renderedEnv + renderedTimeout

val preamble = if (preamblePre.isEmpty)
""
Expand Down Expand Up @@ -326,6 +332,7 @@ ${indent(rendered.mkString("\n"), 1)}"""
s"\nneeds: [${job.needs.mkString(", ")}]"

val renderedEnvironment = job.environment.map(compileEnvironment).map("\n" + _).getOrElse("")
val renderedTimeout = compileTimeout(job.timeout, prefix = "\n")

val renderedCond = job.cond.map(wrap).map("\nif: " + _).getOrElse("")

Expand Down Expand Up @@ -459,7 +466,7 @@ strategy:${renderedFailFast}
os:${compileList(job.oses, 3)}
scala:${compileList(job.scalas, 3)}
java:${compileList(job.javas.map(_.render), 3)}${renderedMatrices}
runs-on: ${runsOn}${renderedEnvironment}${renderedContainer}${renderedPerm}${renderedEnv}
runs-on: ${runsOn}${renderedEnvironment}${renderedContainer}${renderedTimeout}${renderedPerm}${renderedEnv}
steps:
${indent(job.steps.map(compileStep(_, sbt, job.sbtStepPreamble, declareShell = declareShell)).mkString("\n\n"), 1)}"""

Expand Down Expand Up @@ -546,6 +553,7 @@ ${indent(jobs.map(compileJob(_, sbt)).mkString("\n\n"), 1)}
githubWorkflowBuildPreamble := Seq(),
githubWorkflowBuildPostamble := Seq(),
githubWorkflowBuildSbtStepPreamble := WorkflowStep.DefaultSbtStepPreamble,
githubWorkflowBuildTimeout := None,
githubWorkflowBuild := Seq(WorkflowStep.Sbt(List("test"), name = Some("Build project"))),

githubWorkflowPublishPreamble := Seq(),
Expand All @@ -554,6 +562,7 @@ ${indent(jobs.map(compileJob(_, sbt)).mkString("\n\n"), 1)}
githubWorkflowPublish := Seq(WorkflowStep.Sbt(List("+publish"), name = Some("Publish project"))),
githubWorkflowPublishTargetBranches := Seq(RefPredicate.Equals(Ref.Branch("main"))),
githubWorkflowPublishCond := None,
githubWorkflowPublishTimeout := None,

githubWorkflowJavaVersions := Seq(JavaSpec.temurin("11")),
githubWorkflowScalaVersions := crossScalaVersions.value,
Expand Down Expand Up @@ -715,7 +724,8 @@ ${indent(jobs.map(compileJob(_, sbt)).mkString("\n\n"), 1)}
cond = Some(s"github.event_name != 'pull_request' && $publicationCond"),
scalas = List(scalaVersion.value),
javas = List(githubWorkflowJavaVersions.value.head),
needs = List("build"))).filter(_ => !githubWorkflowPublishTargetBranches.value.isEmpty)
needs = List("build"),
timeout = githubWorkflowPublishTimeout.value)).filter(_ => !githubWorkflowPublishTargetBranches.value.isEmpty)

Seq(
WorkflowJob(
Expand All @@ -737,7 +747,8 @@ ${indent(jobs.map(compileJob(_, sbt)).mkString("\n\n"), 1)}
matrixAdds = githubWorkflowBuildMatrixAdditions.value,
matrixIncs = githubWorkflowBuildMatrixInclusions.value.toList,
matrixExcs = githubWorkflowBuildMatrixExclusions.value.toList,
runsOnExtraLabels = githubWorkflowBuildRunsOnExtraLabels.value.toList )) ++ publishJobOpt ++ githubWorkflowAddedJobs.value
runsOnExtraLabels = githubWorkflowBuildRunsOnExtraLabels.value.toList,
timeout = githubWorkflowBuildTimeout.value)) ++ publishJobOpt ++ githubWorkflowAddedJobs.value
})

private val generateCiContents = Def task {
Expand Down
5 changes: 4 additions & 1 deletion src/main/scala/sbtghactions/WorkflowJob.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

package sbtghactions

import scala.concurrent.duration.FiniteDuration

final case class WorkflowJob(
id: String,
name: String,
Expand All @@ -34,4 +36,5 @@ final case class WorkflowJob(
matrixExcs: List[MatrixExclude] = List(),
runsOnExtraLabels: List[String] = List(),
container: Option[JobContainer] = None,
environment: Option[JobEnvironment] = None)
environment: Option[JobEnvironment] = None,
timeout: Option[FiniteDuration] = None)
9 changes: 6 additions & 3 deletions src/main/scala/sbtghactions/WorkflowStep.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,14 @@ package sbtghactions

import scala.collection.immutable.ListMap

import scala.concurrent.duration.FiniteDuration

sealed trait WorkflowStep extends Product with Serializable {
def id: Option[String]
def name: Option[String]
def cond: Option[String]
def env: Map[String, String]
def timeout: Option[FiniteDuration]
}

object WorkflowStep {
Expand Down Expand Up @@ -73,7 +76,7 @@ object WorkflowStep {
List("echo \"$(" + cmd + ")\" >> $GITHUB_PATH"),
name = Some(s"Prepend $$PATH using $cmd"))

final case class Run(commands: List[String], id: Option[String] = None, name: Option[String] = None, cond: Option[String] = None, env: Map[String, String] = Map(), params: Map[String, String] = Map()) extends WorkflowStep
final case class Sbt(commands: List[String], id: Option[String] = None, name: Option[String] = None, cond: Option[String] = None, env: Map[String, String] = Map(), params: Map[String, String] = Map()) extends WorkflowStep
final case class Use(ref: UseRef, params: Map[String, String] = Map(), id: Option[String] = None, name: Option[String] = None, cond: Option[String] = None, env: Map[String, String] = Map()) extends WorkflowStep
final case class Run(commands: List[String], id: Option[String] = None, name: Option[String] = None, cond: Option[String] = None, env: Map[String, String] = Map(), params: Map[String, String] = Map(), timeout: Option[FiniteDuration] = None) extends WorkflowStep
final case class Sbt(commands: List[String], id: Option[String] = None, name: Option[String] = None, cond: Option[String] = None, env: Map[String, String] = Map(), params: Map[String, String] = Map(), timeout: Option[FiniteDuration] = None) extends WorkflowStep
final case class Use(ref: UseRef, params: Map[String, String] = Map(), id: Option[String] = None, name: Option[String] = None, cond: Option[String] = None, env: Map[String, String] = Map(), timeout: Option[FiniteDuration] = None) extends WorkflowStep
}
5 changes: 5 additions & 0 deletions src/sbt-test/sbtghactions/check-and-regenerate/build.sbt
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import scala.concurrent.duration._

organization := "com.codecommit"
version := "0.0.1"

Expand Down Expand Up @@ -28,3 +30,6 @@ ThisBuild / githubWorkflowPublish :=
),
WorkflowStep.Run(List("echo sup")),
)
ThisBuild / githubWorkflowBuildTimeout := Some(2.hours)

ThisBuild / githubWorkflowPublishTimeout := Some(1.hour)
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ jobs:
- scala: 2.12.15
test: is
runs-on: ${{ matrix.os }}
timeout-minutes: 120

steps:
- name: Checkout current branch (full)
uses: actions/checkout@v3
Expand Down Expand Up @@ -84,6 +86,8 @@ jobs:
scala: [2.13.6]
java: [temurin@11]
runs-on: ${{ matrix.os }}
timeout-minutes: 60

steps:
- name: Checkout current branch (full)
uses: actions/checkout@v3
Expand Down
31 changes: 31 additions & 0 deletions src/test/scala/sbtghactions/GenerativePluginSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package sbtghactions
import org.specs2.mutable.Specification

import java.net.URL
import scala.concurrent.duration.DurationInt

class GenerativePluginSpec extends Specification {
import GenerativePlugin._
Expand Down Expand Up @@ -468,6 +469,12 @@ class GenerativePluginSpec extends Specification {
| abc: def
| cafe: '@42'""".stripMargin
}

"compile a run step with a timeout" in {
compileStep(
Run(List("users"), timeout = Some(1.hour)),
"") mustEqual "- timeout-minutes: 60\n run: users"
}
}

"job compilation" should {
Expand Down Expand Up @@ -737,6 +744,30 @@ class GenerativePluginSpec extends Specification {
- run: echo hello"""
}

"compile a job with a timeout" in {
val results = compileJob(
WorkflowJob(
"publish",
"Publish Release",
List(
WorkflowStep.Sbt(List("ci-release"))),
timeout = Some(1.hour)),
"csbt")

results mustEqual s"""publish:
name: Publish Release
strategy:
matrix:
os: [ubuntu-latest]
scala: [2.13.6]
java: [temurin@11]
runs-on: $${{ matrix.os }}
timeout-minutes: 60

steps:
- run: csbt ci-release"""
}

"produce an error when compiling a job with `include` key in matrix" in {
compileJob(
WorkflowJob(
Expand Down

0 comments on commit ba5bcc8

Please sign in to comment.