From eed5b646304c7251b1416783eef8f8c6f398450e Mon Sep 17 00:00:00 2001 From: Arnout Engelen Date: Sat, 8 Aug 2026 11:48:45 +0200 Subject: [PATCH 1/2] feat: update workflows but keep any action hashes intact This is useful for projects that want to refer to actions by hash instead of tag (and manage that through for example dependabot), but still want to use sbt-github-actions to manage the rest of the workflow. This helps the tools coexist. --- README.md | 1 + .../scala/sbtghactions/GenerativeKeys.scala | 2 + .../scala/sbtghactions/GenerativePlugin.scala | 27 +++++ .../.github/workflows/ci-modified.yml | 101 ++++++++++++++++++ .../allow-hashes/.github/workflows/ci.yml | 2 +- src/sbt-test/sbtghactions/allow-hashes/test | 6 ++ 6 files changed, 138 insertions(+), 1 deletion(-) create mode 100644 src/sbt-test/sbtghactions/allow-hashes/.github/workflows/ci-modified.yml diff --git a/README.md b/README.md index 4102c909..486469dd 100644 --- a/README.md +++ b/README.md @@ -97,6 +97,7 @@ ThisBuild / githubWorkflowPublish := Seq( - `githubWorkflowGenerate` – Generates (and overwrites if extant) **ci.yml** and **clean.yml** workflows according to configuration within sbt. The **clean.yml** workflow is something that GitHub Actions should just do by default: it removes old build artifacts to prevent them from running up your storage usage (it has no effect on currently running builds). This workflow is unconfigurable and is simply drawn from the static contents of the **clean.yml** resource file within this repository. - `githubWorkflowCheck` – Checks to see if the **ci.yml** and **clean.yml** files are equivalent to what would be generated and errors if otherwise. This task is run from within the generated **ci.yml** to ensure that the build and the workflow are kept in sync. As a general rule, any time you change the workflow configuration within sbt, you should regenerate the **ci.yml** and commit the results, but inevitably people forget. This check fails the build if that happens. Note that if you *need* to manually fiddle with the **ci.yml** contents, for whatever reason, you will need to remove the call to this check from within the workflow, otherwise your build will simply fail. +- `githubWorkflowUpdate` – Regenerates **ci.yml** and **clean.yml**, but if action references have been replaced by hashes keeps those intact. If you know you're not changing the action references then `githubWorkflowGenerate` is simpler, but if you're managing action reference hashes separately (e.g. with a tool such as dependabot) then this helps the tools coexist. ## Settings diff --git a/src/main/scala/sbtghactions/GenerativeKeys.scala b/src/main/scala/sbtghactions/GenerativeKeys.scala index 3045de68..561c6927 100644 --- a/src/main/scala/sbtghactions/GenerativeKeys.scala +++ b/src/main/scala/sbtghactions/GenerativeKeys.scala @@ -25,6 +25,8 @@ trait GenerativeKeys { @transient lazy val githubWorkflowGenerate = taskKey[Unit]("Generates (and overwrites if extant) a ci.yml and clean.yml actions description according to configuration") @transient + lazy val githubWorkflowUpdate = taskKey[Unit]("Updates ci.yml and clean.yml keeping existing action hashes intact") + @transient lazy val githubWorkflowCheck = taskKey[Unit]("Checks to see if the ci.yml and clean.yml files are equivalent to what would be generated and errors if otherwise") lazy val githubWorkflowDir = settingKey[File]("Where to place the workflow directory which contains the generated ci.yml and clean.yml files. (default: baseDirectory.value / \".github\")") diff --git a/src/main/scala/sbtghactions/GenerativePlugin.scala b/src/main/scala/sbtghactions/GenerativePlugin.scala index c9af0369..8f03a8ef 100644 --- a/src/main/scala/sbtghactions/GenerativePlugin.scala +++ b/src/main/scala/sbtghactions/GenerativePlugin.scala @@ -921,6 +921,7 @@ ${indent(jobs.map(compileJob(_, sbt)).mkString("\n\n"), 1)} }, githubWorkflowGenerate / aggregate := false, + githubWorkflowUpdate / aggregate := false, githubWorkflowCheck / aggregate := false, githubWorkflowGenerate := { @@ -937,6 +938,32 @@ ${indent(jobs.map(compileJob(_, sbt)).mkString("\n\n"), 1)} IO.write(cleanYml, cleanContents) }, + githubWorkflowUpdate := { + val ciContents = generateCiContents.value + val includeClean = githubWorkflowIncludeClean.value + val cleanContents = generateCleanContents(githubWorkflowOSes.value.head) + + val ciYml = ciYmlFile.value + val cleanYml = cleanYmlFile.value + + def updateAndWrite(yml: File, contents: String) = { + val existingContents = IO.read(yml) + val hashRefPattern = "uses: ([-a-zA-Z0-9]+/[-a-zA-Z0-9]+)@([a-z0-9]{40}.*)".r + val existingHashes = hashRefPattern.findAllMatchIn(existingContents) + val updatedContents = existingHashes.foldLeft(contents)((acc, action) => + acc.replaceAll( + s"uses: ${action.group(1)}@.*", + s"uses: ${action.group(1)}@${action.group(2)}" + ) + ) + IO.write(yml, updatedContents) + } + + updateAndWrite(ciYml, ciContents) + if(includeClean) + updateAndWrite(cleanYml, cleanContents) + }, + githubWorkflowCheck := { val expectedCiContents = generateCiContents.value val includeClean = githubWorkflowIncludeClean.value diff --git a/src/sbt-test/sbtghactions/allow-hashes/.github/workflows/ci-modified.yml b/src/sbt-test/sbtghactions/allow-hashes/.github/workflows/ci-modified.yml new file mode 100644 index 00000000..14937772 --- /dev/null +++ b/src/sbt-test/sbtghactions/allow-hashes/.github/workflows/ci-modified.yml @@ -0,0 +1,101 @@ +# This file was automatically generated by sbt-github-actions using the +# githubWorkflowGenerate task. You should add and commit this file to +# your git repository. It goes without saying that you shouldn't edit +# this file by hand! Instead, if you wish to make changes, you should +# change your sbt build configuration to revise the workflow description +# to meet your needs, then regenerate this file. + +# This file was changed, to test that githubWorkflowUpdate will overwrite +# all such changes *except* references to actions by hash. + +name: Continuous Integration + +on: + pull_request: + branches: ['**'] + push: + branches: ['**'] + +env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + +jobs: + build: + name: Build and Test + strategy: + matrix: + os: [ubuntu-latest] + scala: [2.13.10] + java: [zulu@8] + runs-on: ${{ matrix.os }} + steps: + - name: Checkout current branch (full) + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + fetch-depth: 0 + + - name: Setup Java (zulu@8) + if: matrix.java == 'zulu@8' + uses: actions/setup-java@v5 + with: + distribution: zulu + java-version: 8 + cache: sbt + + - name: Setup sbt + uses: sbt/setup-sbt@v1 + + - name: Check that workflows are up to date + run: sbt '++ ${{ matrix.scala }}; githubWorkflowCheck' + + - name: Build project + run: sbt '++ ${{ matrix.scala }}; test' + + - name: Compress target directories + run: tar cf targets.tar target project/target + + - name: Upload target directories + uses: actions/upload-artifact@v7 + with: + name: target-${{ matrix.os }}-${{ matrix.scala }}-${{ matrix.java }} + path: targets.tar + + publish: + name: Publish Artifacts + needs: [build] + if: github.event_name != 'pull_request' && (github.ref == 'refs/heads/main') + strategy: + matrix: + os: [ubuntu-latest] + scala: [2.13.10] + java: [zulu@8] + runs-on: ${{ matrix.os }} + steps: + - name: Checkout current branch (full) + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + fetch-depth: 0 + + - name: Setup Java (zulu@8) + if: matrix.java == 'zulu@8' + uses: actions/setup-java@v5 + with: + distribution: zulu + java-version: 8 + cache: sbt + + - name: Setup sbt + uses: sbt/setup-sbt@v1 + + - name: Download target directories (2.13.10) + uses: actions/download-artifact@v8 + with: + name: target-${{ matrix.os }}-2.13.10-${{ matrix.java }} + + - name: Inflate target directories (2.13.10) + run: | + tar xf targets.tar + rm targets.tar + + - name: Publish project + run: sbt +publish diff --git a/src/sbt-test/sbtghactions/allow-hashes/.github/workflows/ci.yml b/src/sbt-test/sbtghactions/allow-hashes/.github/workflows/ci.yml index 522e7530..2a336858 100644 --- a/src/sbt-test/sbtghactions/allow-hashes/.github/workflows/ci.yml +++ b/src/sbt-test/sbtghactions/allow-hashes/.github/workflows/ci.yml @@ -69,7 +69,7 @@ jobs: runs-on: ${{ matrix.os }} steps: - name: Checkout current branch (full) - uses: actions/checkout@v7 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: fetch-depth: 0 diff --git a/src/sbt-test/sbtghactions/allow-hashes/test b/src/sbt-test/sbtghactions/allow-hashes/test index d3f3f6b5..cb6f19fa 100644 --- a/src/sbt-test/sbtghactions/allow-hashes/test +++ b/src/sbt-test/sbtghactions/allow-hashes/test @@ -1 +1,7 @@ > githubWorkflowCheck +$ copy-file .github/workflows/ci.yml .github/workflows/ci-ok.yml +$ copy-file .github/workflows/ci-modified.yml .github/workflows/ci.yml +> githubWorkflowUpdate +# The update should overwrite the modifications but keep the +# by-hash references to actions intact +$ must-mirror .github/workflows/ci.yml .github/workflows/ci-ok.yml From 49f0807a5cc97f512b46f1fe19dc2a9823714144 Mon Sep 17 00:00:00 2001 From: Arnout Engelen Date: Sun, 9 Aug 2026 09:55:27 +0200 Subject: [PATCH 2/2] Update README.md Co-authored-by: eugene yokota --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 486469dd..3f870ad7 100644 --- a/README.md +++ b/README.md @@ -97,7 +97,7 @@ ThisBuild / githubWorkflowPublish := Seq( - `githubWorkflowGenerate` – Generates (and overwrites if extant) **ci.yml** and **clean.yml** workflows according to configuration within sbt. The **clean.yml** workflow is something that GitHub Actions should just do by default: it removes old build artifacts to prevent them from running up your storage usage (it has no effect on currently running builds). This workflow is unconfigurable and is simply drawn from the static contents of the **clean.yml** resource file within this repository. - `githubWorkflowCheck` – Checks to see if the **ci.yml** and **clean.yml** files are equivalent to what would be generated and errors if otherwise. This task is run from within the generated **ci.yml** to ensure that the build and the workflow are kept in sync. As a general rule, any time you change the workflow configuration within sbt, you should regenerate the **ci.yml** and commit the results, but inevitably people forget. This check fails the build if that happens. Note that if you *need* to manually fiddle with the **ci.yml** contents, for whatever reason, you will need to remove the call to this check from within the workflow, otherwise your build will simply fail. -- `githubWorkflowUpdate` – Regenerates **ci.yml** and **clean.yml**, but if action references have been replaced by hashes keeps those intact. If you know you're not changing the action references then `githubWorkflowGenerate` is simpler, but if you're managing action reference hashes separately (e.g. with a tool such as dependabot) then this helps the tools coexist. +- `githubWorkflowUpdate` – Regenerates **ci.yml** and **clean.yml** while preserving the action references. This lets us use Dependabot to manage the action dependencies. ## Settings