sbt / sbt-github-actions

An sbt plugin which makes it easier to build with GitHub Actions
Apache License 2.0
194 stars 59 forks source link

sbt-github-actions

sbt-github-actions is an sbt plugin for assisting in building sbt projects using GitHub Actions.

Usage

Add the following to your plugins.sbt:

addSbtPlugin("com.github.sbt" % "sbt-github-actions" % <latest>)

To use the generative functionality, run sbt githubWorkflowGenerate and commit the results. If your sbt build is ever changed such that the generated workflow is no longer in sync, the workflow run in GitHub Actions will begin failing and you will need to re-run this task (and commit the results).

General Plugin

The GitHubActionsPlugin provides general functionality, giving builds the ability to introspect on their host workflow and whether or not they are running in GitHub Actions. This latter functionality, exposed by the githubIsWorkflowBuild global setting, is the most commonly used functionality of this plugin. If you need behavior within your build which is conditional on whether or not the build is running in CI, this is the setting you should branch on.

githubWorkflowName and githubWorkflowDefinition are designed to allow introspection on the exact definition of the workflow which is running the current build, if any. This kind of introspection is not common, but seems like it could be useful.

Generative Plugin

sbt-github-actions provides a mechanism for generating GitHub Actions workflows from the sbt build definition. With sbt-github-actions, the scala: entry in the job matrix: is populated from the ThisBuild / crossScalaVersions key in your build.sbt.

The GenerativePlugin is designed to make it easier to maintain GitHub Actions builds for sbt projects by generating ci.yml and clean.yml workflow definition files, and then forcibly failing the build if these files ever fall out of step with the build itself. The ci.yml workflow, by default, contains both build and publish jobs, though you will likely need to add extra steps to the githubWorkflowPublishPreamble and/or githubWorkflowEnv (e.g. decrypting and importing a GPG signing key) in order for publication to actually work.

If a publish job is not desired, simply set githubWorkflowPublishTargetBranches to Seq(). By default, publish is restricted to run on main, and additional restrictions may be configured within the build.

sbt and Coursier caching are all handled by the generated ci.yml by default, as well as standard things like Git checkout, Scala setup (using excellent setup-java action), and more. The matrix for the build job will be generated from crossScalaVersions and has additional support for multiple JVMs and OSes. Additionally, compiled artifacts are properly uploaded so that jobs which are dependent on build can avoid redundant work (most notably, publish). Thus, publication is guaranteed to be based on binary files that were generated and tested by the build job, rather than re-generated by publish. (NB: due to what appear to be issues in Zinc, this isn't quite working yet; expect it to be fixed in a coming release of sbt-github-actions)

clean.yml is generated based on a static description because it should just be the default in all GitHub Actions projects. This is basically a hack to work around the fact that artifacts produced by GitHub Actions workflows count against personal and organization storage limits, but those artifacts also are retained indefinitely up until 2 GB. This is entirely unnecessary and egregious, since artifacts are transient and only useful for passing state between jobs within the same workflow. To make matters more complicated, artifacts from a given workflow are invisible to the GitHub API until that workflow is finished, which is why clean.yml has to be a separate workflow rather than part of ci.yml. It runs on every push to the repository.

This plugin is quite prescriptive in that it forcibly manages the contents of the ci.yml and clean.yml files. By default, ci.yml will contain a step which verifies that its contents (and the contents of clean.yml) correspond precisely to the most up-to-date generated version of themselves. If this is not the case, then the build is failed. However, there is no restriction in adding other workflows not named ci.yml or clean.yml. These will be ignored entirely by the plugin.

JDK settings

We recommend you set the following setting in build.sbt:

// sbt-github-actions defaults to using JDK 8 for testing and publishing.
// The following adds JDK 17 for testing.
ThisBuild / githubWorkflowJavaVersions += JavaSpec.temurin("17")

Integration with sbt-ci-release

Integrating with sbt-ci-release is a relatively straightforward process, and the plugins are quite complementary. First, follow all of the setup instructions in sbt-ci-release's readme. Once this is complete, add the following to your build.sbt:

ThisBuild / githubWorkflowTargetTags ++= Seq("v*")
ThisBuild / githubWorkflowPublishTargetBranches :=
  Seq(RefPredicate.StartsWith(Ref.Tag("v")))

ThisBuild / githubWorkflowPublish := Seq(
  WorkflowStep.Sbt(
    commands = List("ci-release"),
    name = Some("Publish project"),
  )
)

This is assuming that you only wish to publish tags. If you also wish to publish snapshots upon successful main builds, use the following githubWorkflowPublishTargetBranches declaration:

ThisBuild / githubWorkflowPublishTargetBranches :=
  Seq(
    RefPredicate.StartsWith(Ref.Tag("v")),
    RefPredicate.Equals(Ref.Branch("main"))
  )

Note the use of += rather than :=.

ThisBuild / githubWorkflowPublish := Seq(
  WorkflowStep.Sbt(
    commands = List("ci-release"),
    name = Some("Publish project"),
    env = Map(
      "PGP_PASSPHRASE" -> "${{ secrets.PGP_PASSPHRASE }}",
      "PGP_SECRET" -> "${{ secrets.PGP_SECRET }}",
      "SONATYPE_PASSWORD" -> "${{ secrets.SONATYPE_PASSWORD }}",
      "SONATYPE_USERNAME" -> "${{ secrets.SONATYPE_USERNAME }}"
    )
  )
)

Tasks

Generative

Settings

General

Generative

Any and all settings which affect the behavior of the generative plugin should be set in the ThisBuild scope (for example, ThisBuild / crossScalaVersions := rather than just crossScalaVersions :=). This is important because GitHub Actions workflows are global across the entire build, regardless of how individual projects are configured. A corollary of this is that it is not possible (yet) to have specific subprojects which build with different Scala versions, Java versions, or OSes. This is theoretically possible but it's very complicated. For now, I'm going to be lazy and wait for someone to say "pretty please" before implementing it.

General

build Job

publish Job

Windows related