I have a multi-job workflow where a devcontainer is rebuilt when its Dockerfile changes, and various sub-projects are built and tested if changes are made to them. If changes are made to the devcontainer, I'd like for the devcontainer to finish building before running subsequent jobs. But if no changes are made to the devcontainer, they should still build depending on their rules.
I use needs: [ changes, rebuild-devcontainer ]
for the foo
and bar
jobs.
The changes
job always run and exports variables that are used in a build conditional:
if: needs.changes.outputs.foo == 'true'
The part that doesn't work is adding rebuild-devcontainer
to the needs:
:
foo
and bar
entirely when the devcontainer isn't touchedHow can I ensure order when the devcontainer is rebuilt, but not require rebuilding?
on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
changes:
runs-on: ubuntu-latest
outputs:
devcontainer: ${{ steps.filter.outputs.devcontainer }}
foo: ${{ steps.filter.outputs.foo }}
bar: ${{ steps.filter.outputs.bar }}
steps:
- uses: dorny/paths-filter@4512585405083f25c027a35db413c2b3b9006d50 # v2.11.1, 2022-10-12
id: filter
with:
filters: |
devcontainer:
- '.devcontainer/Dockerfile'
foo:
- 'foo/**'
bar:
- 'bar/**'
rebuild-devcontainer:
name: Rebuild devcontainer
runs-on: ubuntu-latest
needs: changes
if: needs.changes.outputs.devcontainer == 'true'
steps:
- name: Checkout sources
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2, 2023-04-13
- name: Login to GHCR
uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0, 2023-09-12
with:
registry: ghcr.io
username: ${{ secrets.GHCR_USER }}
password: ${{ secrets.GHCR_TOKEN }}
- name: Build and upload devcontainer to GHCR
uses: devcontainers/ci@57eaf0c9b518a76872bc429cdceefd65a912309b # v0.3.1900000329, 2023-04-17
with:
imageName: ghcr.io/${{ secrets.GHCR_USER }}/our-devcontainer
push: always
runCmd: |
# Cache build artifacts in devcontainer
cargo test --workspace --no-run
build-foo:
name: Build foo
runs-on: ubuntu-latest
container:
image: ghcr.io/${{ secrets.GHCR_USER }}/our-devcontainer
credentials:
username: ${{ secrets.GHCR_USER }}
password: ${{ secrets.GHCR_TOKEN }}
needs: [ changes, rebuild-devcontainer ]
if: needs.changes.outputs.foo == 'true'
steps:
- name: Checkout sources
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2, 2023-04-13
- name: Login to GHCR
uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0, 2023-09-12
with:
registry: ghcr.io
username: ${{ secrets.GHCR_USER }}
password: ${{ secrets.GHCR_TOKEN }}
- name: Build foo in devcontainer
working-directory: foo
run: make foo
build-bar:
...
The solution is to add always()
as a condition on the jobs that have needs: rebuild-devcontainer
:
Causes the step to always execute, and returns
true
, even when canceled. Thealways
expression is best used at the step level or on tasks that you expect to run even when a job is canceled. For example, you can usealways
to send logs even when a job is canceled.Warning: Avoid using
always
for any task that could suffer from a critical failure, for example: getting sources, otherwise the workflow may hang until it times out. If you want to run a job or step regardless of its success or failure, use the recommended alternative:if: ${{ !cancelled() }}
The interpretation:
needs: [ ..., rebuild-devcontainer ]
makes the order right, but does not execute when rebuild-devcontainer
does not execute.if: always()
or if always() && ...
executes the step regardless of whether rebuild-devcontainer
executes. This changes the meaning of needs
to "build after".!cancelled()
or !failure()
might be more appropriate.jobs:
...
build-foo:
name: Build foo
runs-on: ubuntu-latest
container:
image: ghcr.io/${{ secrets.GHCR_USER }}/our-devcontainer
credentials:
username: ${{ secrets.GHCR_USER }}
password: ${{ secrets.GHCR_TOKEN }}
needs: [ changes, rebuild-devcontainer ]
# This job already has another `if:` condition, so `always() && ...`:
if: always() && needs.changes.outputs.foo == 'true'
...
build-bar:
name: Build bar
runs-on: ubuntu-latest
container:
image: ghcr.io/${{ secrets.GHCR_USER }}/our-devcontainer
credentials:
username: ${{ secrets.GHCR_USER }}
password: ${{ secrets.GHCR_TOKEN }}
needs: [ changes, rebuild-devcontainer ]
# This job is otherwise unconditioned, so `always()`:
if: always()
...