diff --git a/README.md b/README.md index 4102c90..3f870ad 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** while preserving the action references. This lets us use Dependabot to manage the action dependencies. ## Settings diff --git a/src/main/scala/sbtghactions/GenerativeKeys.scala b/src/main/scala/sbtghactions/GenerativeKeys.scala index 3045de6..561c692 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 c9af036..8f03a8e 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 0000000..1493777 --- /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 522e753..2a33685 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 d3f3f6b..cb6f19f 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